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,1724 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../compile_error"
|
|
4
|
+
require_relative "../ir/ir"
|
|
5
|
+
|
|
6
|
+
module Rubycc
|
|
7
|
+
module Backend
|
|
8
|
+
# Raised when a backend is handed an IR construct it does not yet lower.
|
|
9
|
+
# It is a user-facing error rather than an internal one: the program is
|
|
10
|
+
# valid C, this target simply cannot compile it yet, so the driver reports
|
|
11
|
+
# it as a diagnostic instead of letting it surface as a Ruby crash.
|
|
12
|
+
#
|
|
13
|
+
# No backend raises it at the moment — the AArch64 one lowered its last gap
|
|
14
|
+
# (alloca) in m4/aarch64-alloca-bitscan-2. The class and the driver's
|
|
15
|
+
# handling of it stay because this is the contract a *new* target is written
|
|
16
|
+
# against: a backend under construction refuses what it cannot yet lower
|
|
17
|
+
# with `raise UnsupportedError, "<target>: not yet supported: <feature>"`
|
|
18
|
+
# rather than emitting something plausible-looking.
|
|
19
|
+
class UnsupportedError < Rubycc::Error; end
|
|
20
|
+
|
|
21
|
+
# AArch64 (ARM64) code generator, the second backend behind the same
|
|
22
|
+
# IR::Function -> Result contract the x86_64 one honors. It keeps the
|
|
23
|
+
# spill-everything strategy — every virtual register owns an 8-byte stack
|
|
24
|
+
# slot and each IR instruction loads its operands into scratch registers,
|
|
25
|
+
# computes, and stores the result back — but the machine underneath is
|
|
26
|
+
# entirely different: fixed-length 32-bit instructions, a flat register
|
|
27
|
+
# file, and load/store addressing that shapes the frame layout.
|
|
28
|
+
#
|
|
29
|
+
# Frame layout (frame-base-relative, positive offsets). Unlike x86_64's
|
|
30
|
+
# rbp-negative displacements, every slot is addressed as [base + off] with a
|
|
31
|
+
# non-negative off, because AArch64's ldr/str unsigned-offset form scales a
|
|
32
|
+
# 12-bit immediate by the access size (reaching 0..32760 for a 64-bit load)
|
|
33
|
+
# while the signed form is only a 9-bit unscaled window (-256..255) that a
|
|
34
|
+
# modest frame overruns at once. From the base upward the frame holds the
|
|
35
|
+
# outgoing argument area, the saved frame record (x29/x30), the vreg slots
|
|
36
|
+
# and the stack objects. A slot whose offset still overflows the scaled
|
|
37
|
+
# immediate is reached by composing its address into a scratch register with
|
|
38
|
+
# add-immediate(s) — the path is built in from the start rather than bolted
|
|
39
|
+
# on for large frames.
|
|
40
|
+
#
|
|
41
|
+
# The frame base is sp itself in an ordinary function, which costs nothing
|
|
42
|
+
# and leaves x29 free. It is x29 in a function containing :alloca, because
|
|
43
|
+
# there sp moves during the body and a slot named against it would change
|
|
44
|
+
# address under the program's feet: the prologue copies sp into x29 once the
|
|
45
|
+
# frame is set up and every fixed-frame access goes through
|
|
46
|
+
# #frame_base_register from then on (see #emit_alloca).
|
|
47
|
+
#
|
|
48
|
+
# The outgoing argument area sits at the very bottom because AAPCS64 places
|
|
49
|
+
# a call's stack arguments starting at the caller's sp, and it is reserved
|
|
50
|
+
# once by the prologue — sized for the widest call in the function — rather
|
|
51
|
+
# than pushed per call. That is the whole reason it exists: every value in
|
|
52
|
+
# this backend is named as [sp + off], so moving sp to push arguments would
|
|
53
|
+
# invalidate the offset of every slot at once, including the ones holding
|
|
54
|
+
# the arguments still to be placed. Reserving the area up front leaves sp
|
|
55
|
+
# fixed for the function's whole body, keeps it 16-aligned at the call (the
|
|
56
|
+
# area's size is rounded to 16, and AAPCS64 requires sp 16-aligned at a
|
|
57
|
+
# public interface), and costs nothing at run time. A function that makes no
|
|
58
|
+
# call with stack arguments reserves nothing, so its frame is unchanged.
|
|
59
|
+
#
|
|
60
|
+
# A function containing :alloca is the exception, and it is the exception in
|
|
61
|
+
# both directions: it reserves no static area (sp no longer names the bottom
|
|
62
|
+
# of the fixed frame once a block has been allocated, so the area would be
|
|
63
|
+
# unreachable) and instead lowers sp around each call, below the allocated
|
|
64
|
+
# blocks. That is affordable precisely because the objection above no longer
|
|
65
|
+
# applies — the slots are named against x29 there, so moving sp disturbs
|
|
66
|
+
# nothing.
|
|
67
|
+
#
|
|
68
|
+
# Value representation is identical to the x86_64 backend's slot discipline
|
|
69
|
+
# (see backend/x86_64.rb): a slot is always read and written 64 bits at a
|
|
70
|
+
# time (ldr/str of an X register) so pointers survive intact, a value
|
|
71
|
+
# narrower than 8 bytes lives sign/zero-extended in the low 32 bits with the
|
|
72
|
+
# high half indeterminate, and 32-bit arithmetic runs on the W view of a
|
|
73
|
+
# register — a W-register write zeroes the upper 32 bits, giving C's
|
|
74
|
+
# wrap-around for `int` exactly as x86's eax does.
|
|
75
|
+
#
|
|
76
|
+
# AAPCS64 calling convention: the first eight integer/pointer arguments
|
|
77
|
+
# arrive in x0..x7 and the result comes back in x0; the first eight
|
|
78
|
+
# floating arguments arrive in v0..v7, allocated from a counter of their
|
|
79
|
+
# own, and a floating result comes back in v0. The two sequences are
|
|
80
|
+
# independent, exactly as System V's integer and xmm sequences are, which is
|
|
81
|
+
# why the IR's :gp/:sse4/:sse8 tags carry over unchanged. The generator
|
|
82
|
+
# classifies against this target's register budget (IR::CallConvention),
|
|
83
|
+
# so a :gp tag really does mean one of the eight and a :mem tag really does
|
|
84
|
+
# mean the stack — no seventh integer argument is spilled here that AAPCS64
|
|
85
|
+
# would have kept in a register.
|
|
86
|
+
#
|
|
87
|
+
# A stack argument occupies eight bytes, whatever its type: AAPCS64 6.4.2
|
|
88
|
+
# rounds each argument's size on the stack up to a multiple of eight and
|
|
89
|
+
# aligns it to at least eight, so the IR's eightbyte view of the overflow
|
|
90
|
+
# area is exactly the ABI's. A stack-passed `float` therefore travels as a
|
|
91
|
+
# whole eightbyte whose low four bytes carry the value, which is what the
|
|
92
|
+
# slot discipline already promises.
|
|
93
|
+
#
|
|
94
|
+
# Aggregates arrive here already cut into the pieces AAPCS64 6.4.2 moves
|
|
95
|
+
# them in, the generator having classified them against this target's rules
|
|
96
|
+
# rather than System V's (IR::CallConvention). What that leaves for this
|
|
97
|
+
# backend is placing the pieces: an HFA's members each take a vector
|
|
98
|
+
# register of their own, so struct { float a, b; } really does travel in s0
|
|
99
|
+
# and s1 rather than packed into one — the one shape whose System V reading
|
|
100
|
+
# would have been silently wrong rather than merely unsupported. A smaller
|
|
101
|
+
# non-HFA aggregate's eightbytes take consecutive x registers, and one too
|
|
102
|
+
# large for either is passed by reference, which the generator has already
|
|
103
|
+
# reduced to an ordinary pointer argument.
|
|
104
|
+
#
|
|
105
|
+
# The one aggregate mechanism that needs a register no other kind names is
|
|
106
|
+
# the indirect result: a result too large for registers is written through a
|
|
107
|
+
# buffer address the caller puts in x8, tagged :indirect_result so it can be
|
|
108
|
+
# kept clear of both argument sequences (see INDIRECT_RESULT_REGISTER).
|
|
109
|
+
#
|
|
110
|
+
# Under AAPCS64 a variadic call on this platform places its arguments in the
|
|
111
|
+
# same registers a fixed call would, so the `fixed` half of a call's size
|
|
112
|
+
# pair needs no action here — unlike System V, where it drives the count of
|
|
113
|
+
# vector registers written to al.
|
|
114
|
+
#
|
|
115
|
+
# This backend covers the A2 core — control flow, integer arithmetic, local
|
|
116
|
+
# variables, pointers to locals and direct calls — plus the A3 memory-access
|
|
117
|
+
# layer (the addresses of global variables, string literals and functions,
|
|
118
|
+
# formed with an adrp/add pair, and their -fPIC counterpart read from the
|
|
119
|
+
# GOT with an adrp/ldr pair) and most of A4: indirect calls through a
|
|
120
|
+
# function pointer, floating-point arithmetic, comparison, conversion,
|
|
121
|
+
# argument passing and return, whole-object copies, aggregates passed and
|
|
122
|
+
# returned by value, and variadic function definitions (a register-save-area
|
|
123
|
+
# prologue and the AAPCS64 :va_start seed __builtin_va_arg walks), plus the
|
|
124
|
+
# unsigned 64x64->128 multiply's high half (a single `umulh`) that a
|
|
125
|
+
# synthesized 128-bit multiply needs, and the bit-scan builtins (`clz`, and
|
|
126
|
+
# `rbit` before it for the trailing-zero direction). The atomic ops are
|
|
127
|
+
# covered too, built from the armv8-a baseline load-acquire / store-release
|
|
128
|
+
# exclusive pair (see the atomics section below), and dynamic stack
|
|
129
|
+
# allocation, which moves sp below the fixed frame while x29 keeps that
|
|
130
|
+
# frame addressable (see #emit_alloca). Every IR op the generator can hand
|
|
131
|
+
# this backend is now lowered; there is nothing left it refuses.
|
|
132
|
+
class AArch64
|
|
133
|
+
# Result of compiling one function, the same shape the x86_64 backend
|
|
134
|
+
# returns: `bytes` the machine code, `symbols` an array of
|
|
135
|
+
# { name:, offset:, size: }, and `relocations` an array of kind-tagged
|
|
136
|
+
# records. The kinds emitted are :call (a `bl` site the linker fills in
|
|
137
|
+
# with an R_AARCH64_CALL26) and the four address-forming kinds :string,
|
|
138
|
+
# :global, :func and :got. Each of the latter records a single offset —
|
|
139
|
+
# that of the leading `adrp` — even though it needs two ELF relocations;
|
|
140
|
+
# splitting one record into the pair is the object writer's job, since how
|
|
141
|
+
# many relocations an address costs is a property of the machine.
|
|
142
|
+
Result = Data.define(:bytes, :symbols, :relocations)
|
|
143
|
+
|
|
144
|
+
# Integer argument / result registers, in AAPCS64 order. Every argument
|
|
145
|
+
# the IR classifies :gp lands here in order; an argument past the eighth
|
|
146
|
+
# register, or one already classified onto the stack, is refused (see the
|
|
147
|
+
# class comment).
|
|
148
|
+
ARG_REGISTERS = [0, 1, 2, 3, 4, 5, 6, 7].freeze
|
|
149
|
+
|
|
150
|
+
# Floating argument / result registers, v0..v7, allocated from a counter
|
|
151
|
+
# independent of the integer one. They share the numbering of the integer
|
|
152
|
+
# argument registers but not the register file: v0 and x0 are different
|
|
153
|
+
# registers, so a call passing both an int and a double writes each once.
|
|
154
|
+
FP_ARG_REGISTERS = [0, 1, 2, 3, 4, 5, 6, 7].freeze
|
|
155
|
+
|
|
156
|
+
# x8, the indirect result register. AAPCS64 6.4.1 reserves it for the
|
|
157
|
+
# address of the buffer a result too large for registers is written into,
|
|
158
|
+
# which is what makes an aggregate return differ from System V's: there the
|
|
159
|
+
# same pointer is an ordinary leading integer argument that eats x0's
|
|
160
|
+
# equivalent, here it rides a register of its own and every real argument
|
|
161
|
+
# keeps the place it would have had.
|
|
162
|
+
INDIRECT_RESULT_REGISTER = 8
|
|
163
|
+
|
|
164
|
+
# The registers an aggregate result comes back in, in piece order. An
|
|
165
|
+
# integer piece fills x0 then x1 (an aggregate reaching here is 16 bytes or
|
|
166
|
+
# less, so two is all it can need) and a floating one v0..v3 (an HFA has at
|
|
167
|
+
# most four members). Which file a piece draws on follows its kind, so a
|
|
168
|
+
# struct of two floats returns in s0/s1 while a struct of two longs returns
|
|
169
|
+
# in x0/x1.
|
|
170
|
+
RESULT_GP_REGISTERS = [0, 1].freeze
|
|
171
|
+
RESULT_FP_REGISTERS = [0, 1, 2, 3].freeze
|
|
172
|
+
|
|
173
|
+
# Scratch (temporary, caller-saved) registers used to evaluate one
|
|
174
|
+
# instruction. A/B hold the two operands, C an extra working value (the
|
|
175
|
+
# quotient a remainder needs), and ADDR composes a slot address when the
|
|
176
|
+
# offset overruns the scaled load/store immediate. None is an argument
|
|
177
|
+
# register, so spilling arguments never disturbs an address computation.
|
|
178
|
+
A = 9
|
|
179
|
+
B = 10
|
|
180
|
+
C = 11
|
|
181
|
+
ADDR = 12
|
|
182
|
+
|
|
183
|
+
# Three further scratch registers, used only by the atomic sequences (see
|
|
184
|
+
# #emit_atomic_cas), which are the one place a single IR instruction has
|
|
185
|
+
# more values in flight than A/B/C hold: a compare-exchange juggles the
|
|
186
|
+
# object address, the expected pointer, the expected value, the desired
|
|
187
|
+
# value, the value actually read and the store-exclusive status at once.
|
|
188
|
+
# x13..x15 are caller-saved temporaries like x9..x12 and are clear of
|
|
189
|
+
# ADDR, so an address composed for a distant slot never collides with one
|
|
190
|
+
# of them. (x16/x17 are deliberately skipped: the linker may insert a
|
|
191
|
+
# veneer that clobbers them at any call site.)
|
|
192
|
+
D = 13
|
|
193
|
+
E = 14
|
|
194
|
+
F = 15
|
|
195
|
+
|
|
196
|
+
# The floating counterparts of A/B, holding the operands of one floating
|
|
197
|
+
# instruction. v16..v31 are caller-saved like x9..x15 (v8..v15 are the
|
|
198
|
+
# callee-saved vector registers, so they are avoided), and being clear of
|
|
199
|
+
# v0..v7 means evaluating a floating value never disturbs an argument
|
|
200
|
+
# already placed.
|
|
201
|
+
FA = 16
|
|
202
|
+
FB = 17
|
|
203
|
+
|
|
204
|
+
# Special register numbers. In the load/store, add/sub-immediate and
|
|
205
|
+
# stp/ldp encodings a register field of 31 denotes the stack pointer; in
|
|
206
|
+
# the data-processing (arithmetic/logical shifted-register) encodings the
|
|
207
|
+
# same 31 denotes the zero register, which is how `neg` (sub from xzr) and
|
|
208
|
+
# `cmp` (subs into xzr) are formed.
|
|
209
|
+
SP = 31
|
|
210
|
+
XZR = 31
|
|
211
|
+
FP = 29
|
|
212
|
+
LR = 30
|
|
213
|
+
|
|
214
|
+
# The saved frame record (x29, x30) occupies the lowest 16 bytes of the
|
|
215
|
+
# frame; the first vreg slot sits just above it.
|
|
216
|
+
SAVE_AREA_SIZE = 16
|
|
217
|
+
|
|
218
|
+
# The largest byte offset the 64-bit scaled ldr/str immediate can name
|
|
219
|
+
# (a 12-bit field scaled by 8). A slot beyond this is reached through a
|
|
220
|
+
# composed address instead.
|
|
221
|
+
MAX_SCALED_OFFSET = 4095 * 8
|
|
222
|
+
|
|
223
|
+
# The largest byte offset the stp/ldp immediate can name (a 7-bit signed
|
|
224
|
+
# field scaled by 8). The saved record is reached through a composed
|
|
225
|
+
# address past this.
|
|
226
|
+
MAX_PAIR_OFFSET = 63 * 8
|
|
227
|
+
|
|
228
|
+
# IR comparison op -> AArch64 condition code applied to the flags left by
|
|
229
|
+
# `cmp a, b` (a - b). The signed forms use the N/V-based conditions
|
|
230
|
+
# (lt/le/gt/ge), the unsigned ones the carry-based conditions
|
|
231
|
+
# (lo/ls/hi/hs), which is what an unsigned or pointer comparison needs.
|
|
232
|
+
CONDITIONS = {
|
|
233
|
+
eq: 0, ne: 1,
|
|
234
|
+
lt: 11, le: 13, gt: 12, ge: 10,
|
|
235
|
+
ult: 3, ule: 9, ugt: 8, uge: 2
|
|
236
|
+
}.freeze
|
|
237
|
+
|
|
238
|
+
# IR floating comparison -> the condition code applied to the flags left by
|
|
239
|
+
# `fcmp a, b`. FCMP reports an unordered compare (either operand NaN) as
|
|
240
|
+
# N=0 Z=0 C=1 V=1, a combination no ordered result produces, and the four
|
|
241
|
+
# conditions below are chosen so every one of them reads false there — which
|
|
242
|
+
# is what C requires of <, <=, > and >= against a NaN:
|
|
243
|
+
#
|
|
244
|
+
# :flt -> MI (N set) only a strictly-less compare sets N
|
|
245
|
+
# :fle -> LS (C clear or Z) less clears C, equal sets Z; unordered sets C
|
|
246
|
+
# and clears Z
|
|
247
|
+
# :fgt -> GT (Z clear, N=V) unordered has N=0, V=1, so N != V
|
|
248
|
+
# :fge -> GE (N=V) likewise false when unordered
|
|
249
|
+
#
|
|
250
|
+
# Equality needs no combining pair the way x86's ucomis does: FCMP leaves Z
|
|
251
|
+
# clear for an unordered compare (where x86 sets ZF), so plain EQ is already
|
|
252
|
+
# false on NaN and plain NE already true.
|
|
253
|
+
FLOAT_CONDITIONS = {
|
|
254
|
+
feq: 0, fne: 1,
|
|
255
|
+
flt: 4, fle: 9, fgt: 12, fge: 10
|
|
256
|
+
}.freeze
|
|
257
|
+
|
|
258
|
+
def compile(ir_func)
|
|
259
|
+
@code = +"".b
|
|
260
|
+
# @labels maps a label id to its resolved byte offset; @fixups collects
|
|
261
|
+
# [patch_offset, label_id, kind] for each forward/backward branch whose
|
|
262
|
+
# immediate is written once every label offset is known.
|
|
263
|
+
@labels = {}
|
|
264
|
+
@fixups = []
|
|
265
|
+
@relocations = []
|
|
266
|
+
|
|
267
|
+
# Kept for :va_start, which reads the named parameters' register classes
|
|
268
|
+
# to seed __gr_offs / __vr_offs past the registers the fixed arguments
|
|
269
|
+
# consumed.
|
|
270
|
+
@param_kinds = ir_func.param_kinds
|
|
271
|
+
layout_frame(ir_func.vreg_count, ir_func.stack_objects, ir_func.insts, ir_func.variadic)
|
|
272
|
+
emit_prologue(ir_func.param_kinds, ir_func.variadic)
|
|
273
|
+
ir_func.insts.each { |inst| emit_instruction(inst) }
|
|
274
|
+
resolve_fixups
|
|
275
|
+
|
|
276
|
+
Result.new(
|
|
277
|
+
bytes: @code,
|
|
278
|
+
symbols: [{ name: ir_func.name, offset: 0, size: @code.bytesize }],
|
|
279
|
+
relocations: @relocations
|
|
280
|
+
)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private
|
|
284
|
+
|
|
285
|
+
# Computes the frame's size and every object's base offset. From sp
|
|
286
|
+
# upward: the outgoing argument area (empty unless some call in this
|
|
287
|
+
# function passes an argument on the stack), the 16-byte saved record,
|
|
288
|
+
# the vreg slots (8 bytes each, the region rounded to 16 so the objects
|
|
289
|
+
# stay 16-aligned), then each stack object at a 16-byte-aligned size above
|
|
290
|
+
# the previous. All offsets are non-negative displacements from the frame
|
|
291
|
+
# base, which this method also decides (see #frame_base_register).
|
|
292
|
+
def layout_frame(vreg_count, stack_objects, insts, variadic)
|
|
293
|
+
# Whether this function allocates dynamically decides two things at
|
|
294
|
+
# once: which register names the fixed frame (see #frame_base_register)
|
|
295
|
+
# and whether a static outgoing argument area is worth reserving. It is
|
|
296
|
+
# a property of the whole function, not of a path through it, because a
|
|
297
|
+
# slot's address must not depend on which branch reached it.
|
|
298
|
+
@uses_alloca = insts.any? { |inst| inst.op == :alloca }
|
|
299
|
+
# An alloca function reserves no static outgoing area: sp stops naming
|
|
300
|
+
# the bottom of the fixed frame the moment a block is allocated, so the
|
|
301
|
+
# area would be unreachable at the one moment it is needed. Each call
|
|
302
|
+
# lowers sp for its own area instead (see #place_arguments).
|
|
303
|
+
@outgoing_size = @uses_alloca ? 0 : outgoing_argument_bytes(insts)
|
|
304
|
+
@save_offset = @outgoing_size
|
|
305
|
+
vreg_region = align16(vreg_count * 8)
|
|
306
|
+
running = @save_offset + SAVE_AREA_SIZE + vreg_region
|
|
307
|
+
@object_offsets = []
|
|
308
|
+
stack_objects.each do |object_size|
|
|
309
|
+
@object_offsets << running
|
|
310
|
+
running += align16(object_size)
|
|
311
|
+
end
|
|
312
|
+
# A variadic function reserves the argument register-save area at the
|
|
313
|
+
# very top of the frame — just below the caller's sp, exactly where the
|
|
314
|
+
# AArch64 C library expects __gr_top/__vr_top to point. The vector area
|
|
315
|
+
# (eight 16-byte slots) sits below the integer one (eight 8-byte slots),
|
|
316
|
+
# and both tops (the ends of each) are the addresses :va_start writes.
|
|
317
|
+
# The whole block is 192 bytes, a multiple of 16, so the frame stays
|
|
318
|
+
# 16-aligned; a non-variadic function reserves nothing here.
|
|
319
|
+
@vr_save_offset = @gr_save_offset = @gr_top_offset = @vr_top_offset = nil
|
|
320
|
+
if variadic
|
|
321
|
+
@vr_save_offset = running
|
|
322
|
+
running += FP_ARG_REGISTERS.size * 16
|
|
323
|
+
@vr_top_offset = running
|
|
324
|
+
@gr_save_offset = running
|
|
325
|
+
running += ARG_REGISTERS.size * 8
|
|
326
|
+
@gr_top_offset = running
|
|
327
|
+
end
|
|
328
|
+
@frame_size = align16(running)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# The size of the outgoing argument area: eight bytes per stack argument
|
|
332
|
+
# of the call in this function that passes the most of them, rounded to 16
|
|
333
|
+
# so the saved record above it — and sp itself at every call — stays
|
|
334
|
+
# 16-aligned. Sizing it for the widest call lets every call share the one
|
|
335
|
+
# area, since only one call is in flight at a time.
|
|
336
|
+
def outgoing_argument_bytes(insts)
|
|
337
|
+
widest = 0
|
|
338
|
+
insts.each do |inst|
|
|
339
|
+
next unless inst.op == :call || inst.op == :call_indirect
|
|
340
|
+
|
|
341
|
+
count = inst.b.count { |_vreg, kind| kind == :mem }
|
|
342
|
+
widest = count if count > widest
|
|
343
|
+
end
|
|
344
|
+
align16(widest * 8)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# sp + 8*n above the saved record: the byte offset of vreg n's slot.
|
|
348
|
+
def slot_offset(vreg)
|
|
349
|
+
@save_offset + SAVE_AREA_SIZE + 8 * vreg
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# The byte offset of incoming stack argument `index`. The caller laid its
|
|
353
|
+
# stack arguments out from its own sp upward, and this function's prologue
|
|
354
|
+
# lowered sp by the whole frame, so the caller's sp is [sp + @frame_size]
|
|
355
|
+
# here. Nothing sits between the two — AArch64 keeps the return address in
|
|
356
|
+
# x30 rather than pushing it — so the first stack argument is at exactly
|
|
357
|
+
# that address.
|
|
358
|
+
def incoming_stack_offset(index)
|
|
359
|
+
@frame_size + 8 * index
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Lowers sp by the frame size, saves x29/x30 into the record just above
|
|
363
|
+
# the outgoing argument area, and spills the incoming arguments to their
|
|
364
|
+
# parameter slots. In an ordinary function x29 is not used as a frame
|
|
365
|
+
# pointer (every slot is sp-relative), but the pair is saved and restored
|
|
366
|
+
# so the callee-saved x29 and the return address in x30 round-trip across
|
|
367
|
+
# any call this function makes.
|
|
368
|
+
#
|
|
369
|
+
# An alloca function additionally anchors the fixed frame in x29. The
|
|
370
|
+
# order matters and is forced: the caller's x29 has to reach the saved
|
|
371
|
+
# record before it is overwritten, and the copy has to happen before the
|
|
372
|
+
# parameters are spilled, since those stores already go through the frame
|
|
373
|
+
# base. The record itself is therefore stored against sp explicitly rather
|
|
374
|
+
# than through #frame_base_register, which at that instant would name a
|
|
375
|
+
# register holding the caller's value.
|
|
376
|
+
def emit_prologue(param_kinds, variadic)
|
|
377
|
+
adjust_sp(@frame_size, sub: true)
|
|
378
|
+
emit_save_record(store: true, base: SP)
|
|
379
|
+
emit_add_imm(FP, SP, 0, shift12: false) if @uses_alloca # mov x29, sp
|
|
380
|
+
spill_parameters(param_kinds)
|
|
381
|
+
save_argument_registers if variadic
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Spills every argument register into the variadic save area so a later
|
|
385
|
+
# :va_start can hand __builtin_va_arg a pointer to each. The eight integer
|
|
386
|
+
# registers go into the 8-byte slots at the top of the frame and the eight
|
|
387
|
+
# vector ones into the 16-byte slots below them; parameter spilling above
|
|
388
|
+
# only read the argument registers, so each still holds its incoming value
|
|
389
|
+
# here. A vector register is saved at its low 8 bytes (the double a
|
|
390
|
+
# va_arg(double) reads back) rather than the full 16 — like the x86_64
|
|
391
|
+
# backend's movsd, this subset never fetches a wider vector argument — which
|
|
392
|
+
# keeps the store to the ordinary 64-bit form. All eight of each file are
|
|
393
|
+
# saved unconditionally, keeping the prologue's shape fixed regardless of
|
|
394
|
+
# how many arguments the fixed part named.
|
|
395
|
+
def save_argument_registers
|
|
396
|
+
ARG_REGISTERS.each_with_index do |reg, i|
|
|
397
|
+
store_frame_at(reg, @gr_save_offset + 8 * i)
|
|
398
|
+
end
|
|
399
|
+
FP_ARG_REGISTERS.each_with_index do |reg, i|
|
|
400
|
+
emit_fp_slot_access(reg, @vr_save_offset + 16 * i, 8, load: false)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# :va_start — fills the five AAPCS64 va_list fields at the address in
|
|
405
|
+
# `ap_vreg`'s slot, seeding __builtin_va_arg's walk of the register-save
|
|
406
|
+
# area the prologue laid down. The named parameters' classes (from
|
|
407
|
+
# @param_kinds, the generator having resolved register-vs-stack placement)
|
|
408
|
+
# count how far the fixed part consumed each register file: `named_gp` of
|
|
409
|
+
# the eight integer registers and `named_fp` of the eight vector ones, the
|
|
410
|
+
# rest being where the variable part begins. The fields (their byte offsets
|
|
411
|
+
# fixed by AArch64VaListTag):
|
|
412
|
+
# [A+0] __stack = sp + frame_size + 8*named_stack (the first stacked
|
|
413
|
+
# variable argument, past any named parameter that
|
|
414
|
+
# itself spilled onto the caller's stack)
|
|
415
|
+
# [A+8] __gr_top = sp + gr_top_offset (the end of the integer area)
|
|
416
|
+
# [A+16] __vr_top = sp + vr_top_offset (the end of the vector area)
|
|
417
|
+
# [A+24] __gr_offs = -(8 - named_gp) * 8 (negative, climbs to zero)
|
|
418
|
+
# [A+28] __vr_offs = -(8 - named_fp) * 16
|
|
419
|
+
def emit_va_start(ap_vreg)
|
|
420
|
+
# An alignment pad consumes a register/stack slot as much as a real
|
|
421
|
+
# named argument does, so it counts toward where the variable part begins.
|
|
422
|
+
named_gp = @param_kinds.count(:gp) + @param_kinds.count(:pad)
|
|
423
|
+
named_fp = @param_kinds.count(:sse4) + @param_kinds.count(:sse8)
|
|
424
|
+
named_stack = @param_kinds.count(:mem) + @param_kinds.count(:pad_stack)
|
|
425
|
+
gr_offs = -(ARG_REGISTERS.size - named_gp) * 8
|
|
426
|
+
vr_offs = -(FP_ARG_REGISTERS.size - named_fp) * 16
|
|
427
|
+
|
|
428
|
+
load_reg(A, ap_vreg) # A = &__va_list
|
|
429
|
+
emit_slot_address(B, incoming_stack_offset(named_stack))
|
|
430
|
+
emit_piece_access(B, A, 0, 8, load: false, fp: false)
|
|
431
|
+
emit_slot_address(B, @gr_top_offset)
|
|
432
|
+
emit_piece_access(B, A, 8, 8, load: false, fp: false)
|
|
433
|
+
emit_slot_address(B, @vr_top_offset)
|
|
434
|
+
emit_piece_access(B, A, 16, 8, load: false, fp: false)
|
|
435
|
+
materialize(B, gr_offs, 32)
|
|
436
|
+
emit_piece_access(B, A, 24, 4, load: false, fp: false)
|
|
437
|
+
materialize(B, vr_offs, 32)
|
|
438
|
+
emit_piece_access(B, A, 28, 4, load: false, fp: false)
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Restores x29/x30, raises sp back, and returns. Emitted at every :ret.
|
|
442
|
+
#
|
|
443
|
+
# In an alloca function sp is wherever the last allocation left it, so it
|
|
444
|
+
# is first brought back to the fixed frame from the anchor in x29. That
|
|
445
|
+
# single instruction releases every block allocated in the body at once —
|
|
446
|
+
# the storage lives until the function returns, as C requires, not until
|
|
447
|
+
# the end of the scope that allocated it. The record is then reloaded and
|
|
448
|
+
# the fixed frame released exactly as in an ordinary function (sp and x29
|
|
449
|
+
# are equal by then, so the reload needs no special base).
|
|
450
|
+
def emit_epilogue
|
|
451
|
+
emit_add_imm(SP, FP, 0, shift12: false) if @uses_alloca # mov sp, x29
|
|
452
|
+
emit_save_record(store: false)
|
|
453
|
+
adjust_sp(@frame_size, sub: false)
|
|
454
|
+
emit_word(0xD65F03C0) # ret (branch to x30)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# Stores or reloads the x29/x30 pair at [base + @save_offset], `base`
|
|
458
|
+
# defaulting to the frame base. The stp/ldp immediate is a 7-bit signed
|
|
459
|
+
# field scaled by 8, so it reaches 504 bytes; an outgoing argument area
|
|
460
|
+
# wider than that (a call with 64 or more stack arguments) is addressed
|
|
461
|
+
# through the ADDR scratch instead, which holds no live value in either
|
|
462
|
+
# the prologue or the epilogue.
|
|
463
|
+
def emit_save_record(store:, base: nil)
|
|
464
|
+
base ||= frame_base_register
|
|
465
|
+
if @save_offset <= MAX_PAIR_OFFSET
|
|
466
|
+
store ? emit_stp(FP, LR, base, @save_offset) : emit_ldp(FP, LR, base, @save_offset)
|
|
467
|
+
else
|
|
468
|
+
emit_base_address(ADDR, base, @save_offset)
|
|
469
|
+
store ? emit_stp(FP, LR, ADDR, 0) : emit_ldp(FP, LR, ADDR, 0)
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Brings each incoming argument into its parameter slot so it reads back
|
|
474
|
+
# like any other vreg. Integer/pointer parameters come out of x0..x7 and
|
|
475
|
+
# floating ones out of v0..v7, each sequence advancing its own counter; a
|
|
476
|
+
# :mem parameter is copied down from the caller's stack argument area
|
|
477
|
+
# (through the A scratch, which is not an argument register, so the copies
|
|
478
|
+
# never disturb an argument still to be spilled) as a whole eightbyte,
|
|
479
|
+
# which is right for a narrow or floating value too since the slot
|
|
480
|
+
# discipline only promises its low bytes. A kind that would overrun its
|
|
481
|
+
# register file is a generator contract violation and raises.
|
|
482
|
+
def spill_parameters(param_kinds)
|
|
483
|
+
next_gp = 0
|
|
484
|
+
next_fp = 0
|
|
485
|
+
next_stack = 0
|
|
486
|
+
param_kinds.each_with_index do |kind, i|
|
|
487
|
+
case kind
|
|
488
|
+
when :gp
|
|
489
|
+
raise "parameter :gp overruns the integer registers" if next_gp >= ARG_REGISTERS.size
|
|
490
|
+
|
|
491
|
+
store_reg(ARG_REGISTERS[next_gp], i)
|
|
492
|
+
next_gp += 1
|
|
493
|
+
when :mem
|
|
494
|
+
load_frame_at(A, incoming_stack_offset(next_stack))
|
|
495
|
+
store_reg(A, i)
|
|
496
|
+
next_stack += 1
|
|
497
|
+
when :pad
|
|
498
|
+
# An even-pair alignment pad consumes one integer register but binds
|
|
499
|
+
# no parameter, so its slot is left unwritten and only the counter moves.
|
|
500
|
+
next_gp += 1
|
|
501
|
+
when :pad_stack
|
|
502
|
+
# A stack alignment pad likewise consumes one incoming stack eightbyte
|
|
503
|
+
# with no bound parameter.
|
|
504
|
+
next_stack += 1
|
|
505
|
+
when :sse4, :sse8
|
|
506
|
+
raise "parameter #{kind} overruns the vector registers" if next_fp >= FP_ARG_REGISTERS.size
|
|
507
|
+
|
|
508
|
+
store_fp(FP_ARG_REGISTERS[next_fp], i, kind == :sse8 ? 8 : 4)
|
|
509
|
+
next_fp += 1
|
|
510
|
+
when :indirect_result
|
|
511
|
+
# The caller's result-buffer address arrives in x8, which is
|
|
512
|
+
# caller-saved and would not survive the first call this function
|
|
513
|
+
# makes; spilling it here, before any of them, is what lets the
|
|
514
|
+
# eventual :ret still write through it.
|
|
515
|
+
store_reg(INDIRECT_RESULT_REGISTER, i)
|
|
516
|
+
else
|
|
517
|
+
raise "unknown parameter kind #{kind.inspect}"
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def emit_instruction(inst)
|
|
523
|
+
case inst.op
|
|
524
|
+
when :const then emit_const(inst.dst, inst.a, inst.size)
|
|
525
|
+
when :copy then emit_copy(inst.dst, inst.a)
|
|
526
|
+
when :add then emit_arith(inst, ADD_SHIFTED)
|
|
527
|
+
when :sub then emit_arith(inst, SUB_SHIFTED)
|
|
528
|
+
when :and then emit_arith(inst, AND_SHIFTED)
|
|
529
|
+
when :or then emit_arith(inst, ORR_SHIFTED)
|
|
530
|
+
when :xor then emit_arith(inst, EOR_SHIFTED)
|
|
531
|
+
when :mul then emit_mul(inst.dst, inst.a, inst.b, inst.size)
|
|
532
|
+
when :div then emit_divmod(inst.dst, inst.a, inst.b, inst.size, signed: true, remainder: false)
|
|
533
|
+
when :mod then emit_divmod(inst.dst, inst.a, inst.b, inst.size, signed: true, remainder: true)
|
|
534
|
+
when :udiv then emit_divmod(inst.dst, inst.a, inst.b, inst.size, signed: false, remainder: false)
|
|
535
|
+
when :umod then emit_divmod(inst.dst, inst.a, inst.b, inst.size, signed: false, remainder: true)
|
|
536
|
+
when :shl then emit_shift(inst.dst, inst.a, inst.b, inst.size, LSLV)
|
|
537
|
+
when :sar then emit_shift(inst.dst, inst.a, inst.b, inst.size, ASRV)
|
|
538
|
+
when :shr then emit_shift(inst.dst, inst.a, inst.b, inst.size, LSRV)
|
|
539
|
+
when :neg then emit_neg(inst.dst, inst.a, inst.size)
|
|
540
|
+
when :eq, :ne, :lt, :le, :gt, :ge, :ult, :ule, :ugt, :uge
|
|
541
|
+
emit_comparison(inst.dst, inst.a, inst.b, CONDITIONS.fetch(inst.op), inst.size)
|
|
542
|
+
when :sext then emit_sext(inst.dst, inst.a, inst.size)
|
|
543
|
+
when :zext then emit_zext(inst.dst, inst.a, inst.size)
|
|
544
|
+
when :label then @labels[inst.a] = @code.bytesize
|
|
545
|
+
when :jump then emit_b(inst.a)
|
|
546
|
+
when :jump_if_zero then emit_jump_if_zero(inst.a, inst.b)
|
|
547
|
+
when :call then emit_call(inst.dst, inst.a, inst.b, inst.size)
|
|
548
|
+
when :addr_of then emit_slot_address_to(inst.dst, slot_offset(inst.a))
|
|
549
|
+
when :object_addr then emit_slot_address_to(inst.dst, @object_offsets[inst.a])
|
|
550
|
+
when :load then emit_load(inst.dst, inst.a, inst.size, signed: true)
|
|
551
|
+
when :uload then emit_load(inst.dst, inst.a, inst.size, signed: false)
|
|
552
|
+
when :store then emit_store(inst.a, inst.b, inst.size)
|
|
553
|
+
when :ret then emit_ret(inst.a, inst.size)
|
|
554
|
+
when :fadd then emit_float_binary(inst, FADD)
|
|
555
|
+
when :fsub then emit_float_binary(inst, FSUB)
|
|
556
|
+
when :fmul then emit_float_binary(inst, FMUL)
|
|
557
|
+
when :fdiv then emit_float_binary(inst, FDIV)
|
|
558
|
+
when :feq, :fne, :flt, :fle, :fgt, :fge
|
|
559
|
+
emit_float_comparison(inst.dst, inst.a, inst.b, FLOAT_CONDITIONS.fetch(inst.op), inst.size)
|
|
560
|
+
when :itof then emit_itof(inst.dst, inst.a, inst.b, inst.size)
|
|
561
|
+
when :ftoi then emit_ftoi(inst.dst, inst.a, inst.b, inst.size)
|
|
562
|
+
when :ftof then emit_ftof(inst.dst, inst.a, inst.size)
|
|
563
|
+
when :call_indirect then emit_call_indirect(inst.dst, inst.a, inst.b, inst.size)
|
|
564
|
+
when :mulhi then emit_mulhi(inst.dst, inst.a, inst.b)
|
|
565
|
+
when :bit_scan then emit_bit_scan(inst.dst, inst.a, inst.b, inst.size)
|
|
566
|
+
when :string_addr then emit_symbol_address(inst.dst, kind: :string, string_id: inst.a)
|
|
567
|
+
when :global_addr then emit_symbol_address(inst.dst, kind: :global, symbol: inst.a)
|
|
568
|
+
when :func_addr then emit_symbol_address(inst.dst, kind: :func, symbol: inst.a)
|
|
569
|
+
when :got_addr then emit_got_address(inst.dst, inst.a)
|
|
570
|
+
when :memcpy then emit_memcpy(inst.a, inst.b, inst.size)
|
|
571
|
+
when :atomic_load then emit_atomic_load(inst.dst, inst.a, inst.size)
|
|
572
|
+
when :atomic_store then emit_atomic_store(inst.a, inst.b, inst.size)
|
|
573
|
+
when :atomic_rmw then emit_atomic_rmw(inst.dst, inst.a, inst.b[0], inst.b[1], inst.size)
|
|
574
|
+
when :atomic_cas then emit_atomic_cas(inst.dst, inst.a, inst.b[0], inst.b[1], inst.size)
|
|
575
|
+
when :atomic_fence then emit_atomic_fence
|
|
576
|
+
when :va_start then emit_va_start(inst.a)
|
|
577
|
+
when :alloca then emit_alloca(inst.dst, inst.a)
|
|
578
|
+
else
|
|
579
|
+
raise "aarch64: unsupported IR op: #{inst.op}"
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# :const — materializes an immediate into the destination slot. A size-8
|
|
584
|
+
# constant is built as a full 64-bit value (a long/pointer); otherwise a
|
|
585
|
+
# 32-bit value whose W-register move zeroes the slot's high half, matching
|
|
586
|
+
# the x86_64 backend's "mov eax, imm32" behavior.
|
|
587
|
+
def emit_const(dst, value, size)
|
|
588
|
+
materialize(A, value, size == 8 ? 64 : 32)
|
|
589
|
+
store_reg(A, dst)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# :copy — a 64-bit slot-to-slot transfer.
|
|
593
|
+
def emit_copy(dst, src)
|
|
594
|
+
load_reg(A, src)
|
|
595
|
+
store_reg(A, dst)
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
# An arithmetic/logical binary op. The operands are loaded into A/B and
|
|
599
|
+
# combined with a shifted-register instruction whose width follows the IR
|
|
600
|
+
# size (64-bit for size 8, otherwise a 32-bit W-register op whose upper
|
|
601
|
+
# half is zeroed for free).
|
|
602
|
+
def emit_arith(inst, base_table)
|
|
603
|
+
load_reg(A, inst.a)
|
|
604
|
+
load_reg(B, inst.b)
|
|
605
|
+
emit_word(base_table[width(inst.size)] | (B << 16) | (A << 5) | A)
|
|
606
|
+
store_reg(A, inst.dst)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# :mul — Rd = Rn * Rm, encoded as `madd Rd, Rn, Rm, xzr`.
|
|
610
|
+
def emit_mul(dst, a, b, size)
|
|
611
|
+
load_reg(A, a)
|
|
612
|
+
load_reg(B, b)
|
|
613
|
+
base = size == 8 ? 0x9B007C00 : 0x1B007C00 # madd with Ra = xzr
|
|
614
|
+
emit_word(base | (B << 16) | (A << 5) | A)
|
|
615
|
+
store_reg(A, dst)
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# :mulhi — the unsigned high 64 bits of a 64x64 product, encoded as
|
|
619
|
+
# `umulh Rd, Rn, Rm`. It is a "data-processing (3 source)" instruction
|
|
620
|
+
# like madd/msub, but with op31 = 110 rather than 000 and Ra fixed to
|
|
621
|
+
# xzr (the field the format still carries but this variant ignores);
|
|
622
|
+
# unlike :mul it has no 32-bit operand form (there is no 16-bit-high-
|
|
623
|
+
# of-a-32x32-product need for it here), so this always runs on the X
|
|
624
|
+
# registers regardless of the IR size.
|
|
625
|
+
def emit_mulhi(dst, a, b)
|
|
626
|
+
load_reg(A, a)
|
|
627
|
+
load_reg(B, b)
|
|
628
|
+
emit_word(UMULH | (B << 16) | (A << 5) | A)
|
|
629
|
+
store_reg(A, dst)
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# :bit_scan — count the zero bits of a's value (__builtin_ctz/clz and
|
|
633
|
+
# their "ll" forms). AArch64 has a count-leading-zeros instruction but no
|
|
634
|
+
# trailing-zeros one, which decides the shape of both directions:
|
|
635
|
+
#
|
|
636
|
+
# :reverse (clz) is CLZ on its own — the instruction *is* the leading
|
|
637
|
+
# zero count, so unlike x86-64 (where `bsr` yields the index of the
|
|
638
|
+
# highest set bit and the count is recovered by xor-ing with width-1)
|
|
639
|
+
# nothing follows it.
|
|
640
|
+
# :forward (ctz) is RBIT then CLZ. RBIT reverses the bit order within
|
|
641
|
+
# the register, so the operand's lowest set bit becomes the reversal's
|
|
642
|
+
# highest: the leading zeros of the reversed value are exactly the
|
|
643
|
+
# trailing zeros of the original.
|
|
644
|
+
#
|
|
645
|
+
# Both are "Data-processing (1 source)" instructions (ARM DDI 0487,
|
|
646
|
+
# C4.1.2) differing only in their opcode field, and both come in a 32-bit
|
|
647
|
+
# (W) and a 64-bit (X) form selected by sf — which is what makes the IR's
|
|
648
|
+
# size 4 count within `unsigned int` and size 8 within `unsigned long`
|
|
649
|
+
# rather than over a slot's indeterminate high half. A zero operand is
|
|
650
|
+
# undefined behavior (as in gcc), so no guard is emitted; note that the
|
|
651
|
+
# hardware would define it (CLZ of zero is the register width) but the IR
|
|
652
|
+
# promises nothing there and the x86-64 backend cannot match it anyway.
|
|
653
|
+
def emit_bit_scan(dst, src_vreg, direction, size)
|
|
654
|
+
w = width(size)
|
|
655
|
+
load_reg(A, src_vreg)
|
|
656
|
+
emit_word(RBIT[w] | (A << 5) | A) if direction == :forward
|
|
657
|
+
emit_word(CLZ[w] | (A << 5) | A)
|
|
658
|
+
store_reg(A, dst)
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
# :alloca — dynamic stack allocation (__builtin_alloca). The requested
|
|
662
|
+
# byte count is rounded up to a multiple of 16 (add 15, then clear the low
|
|
663
|
+
# four bits), sp is lowered by that much, and the resulting sp — the
|
|
664
|
+
# lowest address of the block, the stack growing down — is the value the
|
|
665
|
+
# destination slot receives.
|
|
666
|
+
#
|
|
667
|
+
# Rounding to 16 is what keeps sp 16-aligned, which AAPCS64 requires of it
|
|
668
|
+
# at every public interface and which incidentally gives the block the
|
|
669
|
+
# 16-byte alignment gcc's __builtin_alloca promises. The mask is a single
|
|
670
|
+
# AND (immediate): 0xFFFF_FFFF_FFFF_FFF0 is a run of 60 ones, so it is
|
|
671
|
+
# expressible as a bitmask immediate (N = 1, immr = 60, imms = 59) rather
|
|
672
|
+
# than needing the four-instruction movz/movk sequence a general 64-bit
|
|
673
|
+
# constant costs.
|
|
674
|
+
#
|
|
675
|
+
# The subtraction has to be the *extended-register* form: the ordinary
|
|
676
|
+
# shifted-register sub reads register 31 as the zero register, and only
|
|
677
|
+
# the extended form (and add/sub-immediate) reads it as sp.
|
|
678
|
+
#
|
|
679
|
+
# Nothing else in the function has to change position for this to be safe,
|
|
680
|
+
# because #layout_frame has already switched every fixed-frame access to
|
|
681
|
+
# x29 (see #frame_base_register) and every call to reserving its outgoing
|
|
682
|
+
# argument area below the allocated blocks (see #place_arguments). The
|
|
683
|
+
# blocks are released wholesale by the epilogue's "mov sp, x29".
|
|
684
|
+
def emit_alloca(dst, size_vreg)
|
|
685
|
+
load_reg(A, size_vreg) # A = requested byte count
|
|
686
|
+
emit_add_imm(A, A, 15, shift12: false) # add A, A, #15
|
|
687
|
+
emit_word(AND_NOT15 | (A << 5) | A) # and A, A, #-16
|
|
688
|
+
emit_word(SUB_EXTENDED | (A << 16) | (SP << 5) | SP) # sub sp, sp, A
|
|
689
|
+
emit_add_imm(A, SP, 0, shift12: false) # mov A, sp (the block base)
|
|
690
|
+
store_reg(A, dst)
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
# :div/:mod/:udiv/:umod. The quotient is `sdiv`/`udiv` of A by B; a
|
|
694
|
+
# remainder is then A - quotient*B via `msub`, since AArch64 has no direct
|
|
695
|
+
# remainder instruction. The signed/unsigned split mirrors the IR's own.
|
|
696
|
+
def emit_divmod(dst, a, b, size, signed:, remainder:)
|
|
697
|
+
load_reg(A, a)
|
|
698
|
+
load_reg(B, b)
|
|
699
|
+
w = width(size)
|
|
700
|
+
div_base = signed ? (w == 64 ? 0x9AC00C00 : 0x1AC00C00) : (w == 64 ? 0x9AC00800 : 0x1AC00800)
|
|
701
|
+
emit_word(div_base | (B << 16) | (A << 5) | C) # sdiv/udiv C, A, B
|
|
702
|
+
if remainder
|
|
703
|
+
msub_base = w == 64 ? 0x9B008000 : 0x1B008000
|
|
704
|
+
emit_word(msub_base | (B << 16) | (A << 10) | (C << 5) | A) # msub A, C, B, A
|
|
705
|
+
store_reg(A, dst)
|
|
706
|
+
else
|
|
707
|
+
store_reg(C, dst)
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
# :shl/:sar/:shr — a variable shift by B's low bits (masked to 5 bits for
|
|
712
|
+
# a 32-bit operand, 6 for a 64-bit one by the hardware, matching C).
|
|
713
|
+
def emit_shift(dst, a, b, size, base_table)
|
|
714
|
+
load_reg(A, a)
|
|
715
|
+
load_reg(B, b)
|
|
716
|
+
emit_word(base_table[width(size)] | (B << 16) | (A << 5) | A)
|
|
717
|
+
store_reg(A, dst)
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# :neg — Rd = -a, encoded as `sub Rd, xzr, a`.
|
|
721
|
+
def emit_neg(dst, src, size)
|
|
722
|
+
load_reg(A, src)
|
|
723
|
+
emit_word(SUB_SHIFTED[width(size)] | (A << 16) | (XZR << 5) | A)
|
|
724
|
+
store_reg(A, dst)
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
# A comparison materialized into the destination as an int 0/1. `cmp`
|
|
728
|
+
# sets the flags; `cset` then writes 1 when the condition holds. A size-8
|
|
729
|
+
# comparison uses the 64-bit X view (full pointer values), otherwise the
|
|
730
|
+
# 32-bit W view.
|
|
731
|
+
def emit_comparison(dst, a, b, condition, size)
|
|
732
|
+
load_reg(A, a)
|
|
733
|
+
load_reg(B, b)
|
|
734
|
+
emit_word(SUBS_SHIFTED[width(size)] | (B << 16) | (A << 5) | XZR) # cmp A, B (subs xzr, A, B)
|
|
735
|
+
emit_cset(A, condition)
|
|
736
|
+
store_reg(A, dst)
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# :sext — sign-extend a's low `size` bytes to the full 64-bit register,
|
|
740
|
+
# so a subsequent 64-bit use (pointer-offset scaling) sees a correct,
|
|
741
|
+
# possibly negative, value.
|
|
742
|
+
def emit_sext(dst, src, size)
|
|
743
|
+
load_reg(A, src)
|
|
744
|
+
word =
|
|
745
|
+
case size
|
|
746
|
+
when 1 then 0x93401C00 # sxtb x, w
|
|
747
|
+
when 2 then 0x93403C00 # sxth x, w
|
|
748
|
+
else 0x93407C00 # sxtw x, w (size 4)
|
|
749
|
+
end
|
|
750
|
+
emit_word(word | (A << 5) | A)
|
|
751
|
+
store_reg(A, dst)
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
# :zext — zero-extend a's low `size` bytes. The 1/2-byte forms are 32-bit
|
|
755
|
+
# uxtb/uxth (which zero the upper 32 bits too); size 4 is a 64-bit ubfx of
|
|
756
|
+
# the low 32 bits, matching x86's "mov eax, eax".
|
|
757
|
+
def emit_zext(dst, src, size)
|
|
758
|
+
load_reg(A, src)
|
|
759
|
+
word =
|
|
760
|
+
case size
|
|
761
|
+
when 1 then 0x53001C00 # uxtb w, w
|
|
762
|
+
when 2 then 0x53003C00 # uxth w, w
|
|
763
|
+
else 0xD3407C00 # ubfx x, x, #0, #32 (size 4)
|
|
764
|
+
end
|
|
765
|
+
emit_word(word | (A << 5) | A)
|
|
766
|
+
store_reg(A, dst)
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
# :load / :uload — read `size` bytes through the pointer in a's slot. A
|
|
770
|
+
# signed load sign-extends a byte/halfword (ldrsb/ldrsh), the unsigned
|
|
771
|
+
# form zero-extends (ldrb/ldrh); a 4-byte load is a plain W load (upper
|
|
772
|
+
# half zeroed) and an 8-byte load a full X load, both sign-agnostic.
|
|
773
|
+
def emit_load(dst, ptr, size, signed:)
|
|
774
|
+
load_reg(A, ptr) # A = pointer value
|
|
775
|
+
word =
|
|
776
|
+
case size
|
|
777
|
+
when 8 then 0xF9400000 # ldr x, [x]
|
|
778
|
+
when 2 then signed ? 0x79C00000 : 0x79400000 # ldrsh/ldrh w, [x]
|
|
779
|
+
when 1 then signed ? 0x39C00000 : 0x39400000 # ldrsb/ldrb w, [x]
|
|
780
|
+
else 0xB9400000 # ldr w, [x] (size 4)
|
|
781
|
+
end
|
|
782
|
+
emit_word(word | (A << 5) | A)
|
|
783
|
+
store_reg(A, dst)
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
# :store — write value b's low `size` bytes through the pointer in a. The
|
|
787
|
+
# narrower stores (strb/strh/str-w) are exactly the truncation a narrow
|
|
788
|
+
# lvalue needs.
|
|
789
|
+
def emit_store(ptr, value, size)
|
|
790
|
+
load_reg(A, ptr) # A = destination address
|
|
791
|
+
load_reg(B, value) # B = value
|
|
792
|
+
word =
|
|
793
|
+
case size
|
|
794
|
+
when 8 then 0xF9000000 # str x, [x]
|
|
795
|
+
when 2 then 0x79000000 # strh w, [x]
|
|
796
|
+
when 1 then 0x39000000 # strb w, [x]
|
|
797
|
+
else 0xB9000000 # str w, [x] (size 4)
|
|
798
|
+
end
|
|
799
|
+
emit_word(word | (A << 5) | B)
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
# :jump_if_zero — branch when a's low 32 bits are zero. Testing the W view
|
|
803
|
+
# (cbz w) mirrors the x86_64 backend's 32-bit "test eax, eax": the
|
|
804
|
+
# condition is an int 0/1 or a truthiness test the generator has already
|
|
805
|
+
# reduced.
|
|
806
|
+
def emit_jump_if_zero(cond, label_id)
|
|
807
|
+
load_reg(A, cond)
|
|
808
|
+
@fixups << [@code.bytesize, label_id, :cbz]
|
|
809
|
+
emit_word(0x34000000 | A) # cbz w{A}, <patched>
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
# :call — a direct call. Arguments are placed in x0..x7 / v0..v7, then `bl`
|
|
813
|
+
# (its 26-bit immediate left zero and recorded as an R_AARCH64_CALL26
|
|
814
|
+
# relocation the linker resolves), and the result is stored back from x0 or
|
|
815
|
+
# v0 — or, for an aggregate returned in registers, scattered into the
|
|
816
|
+
# caller's buffer (see #store_call_result).
|
|
817
|
+
def emit_call(dst, name, args, size)
|
|
818
|
+
_fixed, ret = size || [nil, nil]
|
|
819
|
+
stack_bytes = place_arguments(args)
|
|
820
|
+
@relocations << { kind: :call, offset: @code.bytesize, symbol: name }
|
|
821
|
+
emit_word(0x94000000) # bl <patched by R_AARCH64_CALL26>
|
|
822
|
+
store_call_result(dst, ret)
|
|
823
|
+
release_dynamic_argument_area(stack_bytes)
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
# :call_indirect — the same sequence through a computed target. The
|
|
827
|
+
# arguments go into their registers first and the callee's address is
|
|
828
|
+
# loaded only afterwards, into the A scratch: A is not an argument register,
|
|
829
|
+
# so the branch target cannot be one of the values just placed, and loading
|
|
830
|
+
# it last means the argument loads never have to work around it. `blr`
|
|
831
|
+
# branches to the register and sets x30, exactly as `bl` does to a label —
|
|
832
|
+
# no relocation, the address being an ordinary run-time value.
|
|
833
|
+
def emit_call_indirect(dst, target_vreg, args, size)
|
|
834
|
+
_fixed, ret = size || [nil, nil]
|
|
835
|
+
stack_bytes = place_arguments(args)
|
|
836
|
+
load_reg(A, target_vreg)
|
|
837
|
+
emit_word(0xD63F0000 | (A << 5)) # blr A
|
|
838
|
+
store_call_result(dst, ret)
|
|
839
|
+
release_dynamic_argument_area(stack_bytes)
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
# Parks a call's result in its destination slot. An in-register aggregate
|
|
843
|
+
# result (`ret` a [buffer_vreg, pieces] array) is scattered into the
|
|
844
|
+
# caller's scratch buffer and has no destination slot at all, the value
|
|
845
|
+
# being that buffer's address; `ret` :sse4/:sse8 means the value comes back
|
|
846
|
+
# in v0 (stored at its own width, like any floating value); otherwise the
|
|
847
|
+
# result is the integer/pointer one in x0. A call whose value is discarded
|
|
848
|
+
# has no destination and stores nothing.
|
|
849
|
+
def store_call_result(dst, ret)
|
|
850
|
+
return store_struct_call_result(ret) if ret.is_a?(Array)
|
|
851
|
+
return unless dst
|
|
852
|
+
|
|
853
|
+
if ret == :sse4 || ret == :sse8
|
|
854
|
+
store_fp(FP_ARG_REGISTERS[0], dst, ret == :sse8 ? 8 : 4)
|
|
855
|
+
else
|
|
856
|
+
store_reg(ARG_REGISTERS[0], dst)
|
|
857
|
+
end
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
# Scatters an in-register aggregate result into the caller's scratch
|
|
861
|
+
# buffer. `ret` is [buffer_vreg, pieces]; buffer_vreg's slot holds the
|
|
862
|
+
# buffer address, loaded into the A scratch — which is neither an integer
|
|
863
|
+
# nor a vector result register, so loading it clobbers nothing the callee
|
|
864
|
+
# just set. Each piece is written at its own offset and width, so a struct
|
|
865
|
+
# of two floats lands as s0 at +0 and s1 at +4 while a struct of two longs
|
|
866
|
+
# lands as x0 at +0 and x1 at +8.
|
|
867
|
+
def store_struct_call_result(ret)
|
|
868
|
+
buffer_vreg, pieces = ret
|
|
869
|
+
load_reg(A, buffer_vreg)
|
|
870
|
+
each_result_piece(pieces) do |piece, reg, fp|
|
|
871
|
+
emit_piece_access(reg, A, piece.offset, piece.size, load: false, fp: fp)
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
# Yields each aggregate-result piece with the register it travels in and
|
|
876
|
+
# whether that register is a vector one, handing out x0/x1 and v0..v3 in
|
|
877
|
+
# piece order. Shared by the caller-side scatter and the callee-side
|
|
878
|
+
# gather, so the two cannot drift apart.
|
|
879
|
+
def each_result_piece(pieces)
|
|
880
|
+
next_gp = 0
|
|
881
|
+
next_fp = 0
|
|
882
|
+
pieces.each do |piece|
|
|
883
|
+
if piece.kind == :gp
|
|
884
|
+
yield piece, RESULT_GP_REGISTERS[next_gp], false
|
|
885
|
+
next_gp += 1
|
|
886
|
+
else
|
|
887
|
+
yield piece, RESULT_FP_REGISTERS[next_fp], true
|
|
888
|
+
next_fp += 1
|
|
889
|
+
end
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
# Places a call's arguments: an integer/pointer one into the next of
|
|
894
|
+
# x0..x7, a floating one into the next of v0..v7, and a :mem one into the
|
|
895
|
+
# outgoing argument area at [sp + 8*k] in left-to-right order, which is
|
|
896
|
+
# where AAPCS64 6.4.2 has the callee look for it. A stack argument is moved
|
|
897
|
+
# as a whole eightbyte, matching how the callee reads it back.
|
|
898
|
+
#
|
|
899
|
+
# The stack arguments are written first, in a pass of their own, because
|
|
900
|
+
# they travel through the A scratch: doing them after the register loads
|
|
901
|
+
# would be safe for A itself (no argument register is A), but keeping the
|
|
902
|
+
# two passes apart makes the order independent of how the argument list
|
|
903
|
+
# happens to interleave. Each load then writes only its own destination
|
|
904
|
+
# and reads sp, so placing a later argument never clobbers an earlier one,
|
|
905
|
+
# and the integer and vector register files never collide.
|
|
906
|
+
#
|
|
907
|
+
# In an alloca function there is no static area to write into, so this
|
|
908
|
+
# call's own is carved out here by lowering sp — below every block the
|
|
909
|
+
# body has allocated, which is exactly where the callee's arguments belong
|
|
910
|
+
# and where they disturb nothing. #release_dynamic_argument_area gives the
|
|
911
|
+
# space back once the call has returned. The byte count is returned so the
|
|
912
|
+
# two halves cannot disagree about how much moved.
|
|
913
|
+
def place_arguments(args)
|
|
914
|
+
stack_bytes = align16(args.count { |_vreg, kind| kind == :mem || kind == :pad_stack } * 8)
|
|
915
|
+
adjust_sp(stack_bytes, sub: true) if @uses_alloca && stack_bytes.positive?
|
|
916
|
+
|
|
917
|
+
next_stack = 0
|
|
918
|
+
args.each do |vreg, kind|
|
|
919
|
+
# :pad_stack reserves one stack eightbyte to 16-align the aggregate
|
|
920
|
+
# behind it; it carries no value, so it only advances the counter.
|
|
921
|
+
next_stack += 1 if kind == :pad_stack
|
|
922
|
+
next unless kind == :mem
|
|
923
|
+
|
|
924
|
+
load_reg(A, vreg)
|
|
925
|
+
store_outgoing_at(A, 8 * next_stack)
|
|
926
|
+
next_stack += 1
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
next_gp = 0
|
|
930
|
+
next_fp = 0
|
|
931
|
+
args.each do |vreg, kind|
|
|
932
|
+
case kind
|
|
933
|
+
when :gp
|
|
934
|
+
raise "call argument :gp overruns the integer registers" if next_gp >= ARG_REGISTERS.size
|
|
935
|
+
|
|
936
|
+
load_reg(ARG_REGISTERS[next_gp], vreg)
|
|
937
|
+
next_gp += 1
|
|
938
|
+
when :pad
|
|
939
|
+
# An even-pair alignment pad reserves one integer register, unloaded.
|
|
940
|
+
next_gp += 1
|
|
941
|
+
when :mem, :pad_stack then next
|
|
942
|
+
when :sse4, :sse8
|
|
943
|
+
raise "call argument #{kind} overruns the vector registers" if next_fp >= FP_ARG_REGISTERS.size
|
|
944
|
+
|
|
945
|
+
load_fp(FP_ARG_REGISTERS[next_fp], vreg, kind == :sse8 ? 8 : 4)
|
|
946
|
+
next_fp += 1
|
|
947
|
+
when :indirect_result
|
|
948
|
+
# The result buffer's address goes in x8, outside both argument
|
|
949
|
+
# sequences, so it never displaces a real argument.
|
|
950
|
+
load_reg(INDIRECT_RESULT_REGISTER, vreg)
|
|
951
|
+
else
|
|
952
|
+
raise "unknown call argument kind #{kind.inspect}"
|
|
953
|
+
end
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
stack_bytes
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
# Gives back the outgoing argument area #place_arguments carved out of the
|
|
960
|
+
# stack for a call in an alloca function, once the call has returned and
|
|
961
|
+
# its result has been parked. Raising sp again is what keeps a call inside
|
|
962
|
+
# a loop from walking the stack down one area per iteration. In an
|
|
963
|
+
# ordinary function the area is part of the fixed frame and nothing moved,
|
|
964
|
+
# so there is nothing to give back.
|
|
965
|
+
def release_dynamic_argument_area(stack_bytes)
|
|
966
|
+
adjust_sp(stack_bytes, sub: false) if @uses_alloca && stack_bytes.positive?
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
# :ret — loads the return value into its result register and runs the
|
|
970
|
+
# epilogue. size nil is an integer/pointer return (x0), size 4/8 a floating
|
|
971
|
+
# one (v0, loaded at that width) and a piece array an aggregate returned in
|
|
972
|
+
# registers. A void return (nil operand) loads nothing. An aggregate too
|
|
973
|
+
# large for registers is not returned this way: its callee copies the
|
|
974
|
+
# result through the hidden x8 pointer, which reaches here as a plain
|
|
975
|
+
# size-nil return of that pointer.
|
|
976
|
+
def emit_ret(value_vreg, size)
|
|
977
|
+
if size.is_a?(Array)
|
|
978
|
+
emit_struct_ret(value_vreg, size)
|
|
979
|
+
elsif size == 4 || size == 8
|
|
980
|
+
load_fp(FP_ARG_REGISTERS[0], value_vreg, size)
|
|
981
|
+
elsif value_vreg
|
|
982
|
+
load_reg(ARG_REGISTERS[0], value_vreg)
|
|
983
|
+
end
|
|
984
|
+
emit_epilogue
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
# Gathers an in-register aggregate return into its result registers.
|
|
988
|
+
# `buffer_vreg` holds the address of the value (a stack object the
|
|
989
|
+
# generator has already filled), loaded into the A scratch — never itself a
|
|
990
|
+
# result register — and each piece is read from its own offset at its own
|
|
991
|
+
# width, leaving x0/x1 and v0..v3 set for the epilogue's `ret`.
|
|
992
|
+
def emit_struct_ret(buffer_vreg, pieces)
|
|
993
|
+
load_reg(A, buffer_vreg)
|
|
994
|
+
each_result_piece(pieces) do |piece, reg, fp|
|
|
995
|
+
emit_piece_access(reg, A, piece.offset, piece.size, load: true, fp: fp)
|
|
996
|
+
end
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
# --- whole-object copies ----------------------------------------------
|
|
1000
|
+
|
|
1001
|
+
# The most eightbytes #emit_memcpy will move with a straight-line run of
|
|
1002
|
+
# load/store pairs before it switches to a counted loop. Below the limit
|
|
1003
|
+
# the unrolled form is both shorter and branchless; above it the code size
|
|
1004
|
+
# would grow with the struct, which a loop bounds at a fixed six
|
|
1005
|
+
# instructions however large the object is.
|
|
1006
|
+
MEMCPY_UNROLL_LIMIT = 8
|
|
1007
|
+
|
|
1008
|
+
# :memcpy — copies `byte_count` bytes from the address in `src_vreg`'s slot
|
|
1009
|
+
# to the address in `dest_vreg`'s slot. This is the whole-object move a
|
|
1010
|
+
# struct assignment needs, and equally the one that makes a struct
|
|
1011
|
+
# argument's private copy and brings a by-reference parameter into storage
|
|
1012
|
+
# of the callee's own.
|
|
1013
|
+
#
|
|
1014
|
+
# The count is a compile-time constant, so the shape of the copy is decided
|
|
1015
|
+
# here rather than tested at run time: the eightbytes go through a counted
|
|
1016
|
+
# loop when there are enough of them to be worth one and a straight run of
|
|
1017
|
+
# load/store pairs otherwise, and the trailing bytes (a struct's size need
|
|
1018
|
+
# not be a multiple of eight) follow in one access each of 4, 2 and 1
|
|
1019
|
+
# bytes. Every tail access is naturally aligned against the block before it
|
|
1020
|
+
# — a 4 follows a multiple of 8, a 2 a multiple of 4, a 1 a multiple of 2 —
|
|
1021
|
+
# so each fits the scaled unsigned-offset form without composing an address.
|
|
1022
|
+
#
|
|
1023
|
+
# A and B hold the two addresses, C the loop counter and ADDR the value in
|
|
1024
|
+
# flight. None is an argument register, so a copy made while a call's
|
|
1025
|
+
# arguments are being prepared never disturbs one already placed.
|
|
1026
|
+
def emit_memcpy(dest_vreg, src_vreg, byte_count)
|
|
1027
|
+
load_reg(A, dest_vreg) # A = destination address
|
|
1028
|
+
load_reg(B, src_vreg) # B = source address
|
|
1029
|
+
chunks = byte_count / 8
|
|
1030
|
+
if chunks > MEMCPY_UNROLL_LIMIT
|
|
1031
|
+
emit_memcpy_loop(chunks)
|
|
1032
|
+
offset = 0 # the loop leaves A and B just past the eightbytes it moved
|
|
1033
|
+
else
|
|
1034
|
+
chunks.times { |i| emit_memcpy_step(8 * i, 8) }
|
|
1035
|
+
offset = 8 * chunks
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
remaining = byte_count - 8 * chunks
|
|
1039
|
+
[4, 2, 1].each do |width|
|
|
1040
|
+
next if remaining < width
|
|
1041
|
+
|
|
1042
|
+
emit_memcpy_step(offset, width)
|
|
1043
|
+
offset += width
|
|
1044
|
+
remaining -= width
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
# Moves `chunks` eightbytes with a counted loop, advancing both addresses
|
|
1049
|
+
# as it goes so the caller's tail accesses start from offset zero. The
|
|
1050
|
+
# counter is decremented by a flag-setting `subs` and the branch taken
|
|
1051
|
+
# while it is non-zero, which needs no label fixup: the target is behind
|
|
1052
|
+
# the branch and its distance is already known.
|
|
1053
|
+
def emit_memcpy_loop(chunks)
|
|
1054
|
+
materialize(C, chunks, 64)
|
|
1055
|
+
start = @code.bytesize
|
|
1056
|
+
emit_memcpy_step(0, 8)
|
|
1057
|
+
emit_add_imm(B, B, 8, shift12: false)
|
|
1058
|
+
emit_add_imm(A, A, 8, shift12: false)
|
|
1059
|
+
emit_word(0xF1000400 | (C << 5) | C) # subs C, C, #1
|
|
1060
|
+
words = (start - @code.bytesize) / 4
|
|
1061
|
+
emit_word(0x54000000 | ((words & 0x7FFFF) << 5) | CONDITIONS.fetch(:ne)) # b.ne <start>
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
# One load/store pair of `width` bytes at `offset` from both addresses,
|
|
1065
|
+
# relaying the value through the ADDR scratch.
|
|
1066
|
+
def emit_memcpy_step(offset, width)
|
|
1067
|
+
emit_piece_access(ADDR, B, offset, width, load: true, fp: false)
|
|
1068
|
+
emit_piece_access(ADDR, A, offset, width, load: false, fp: false)
|
|
1069
|
+
end
|
|
1070
|
+
|
|
1071
|
+
# --- atomics -----------------------------------------------------------
|
|
1072
|
+
#
|
|
1073
|
+
# Every atomic op lowers at sequential consistency, the strongest order
|
|
1074
|
+
# (the IR carries no weaker one — see IR::Generator#gen_builtin_atomic for
|
|
1075
|
+
# why strengthening is always sound), and `size` is only ever 4 or 8, the
|
|
1076
|
+
# generator having diagnosed every other width. The 8-byte forms differ
|
|
1077
|
+
# from the 4-byte ones only in the encoding's size field (bit 30), which is
|
|
1078
|
+
# what the ACQUIRE_/RELEASE_ tables below carry.
|
|
1079
|
+
#
|
|
1080
|
+
# Everything is built from the armv8-a *baseline* — the load-acquire /
|
|
1081
|
+
# store-release exclusive pair and a retry loop — and nothing else. The
|
|
1082
|
+
# single-instruction LSE atomics (`casal`, `ldadd`, `swp`) would be shorter
|
|
1083
|
+
# but need armv8.1-a, and gcc's alternative of calling into libgcc's
|
|
1084
|
+
# outline atomics (`__aarch64_ldadd4_acq_rel` and kin) would put rubycc's
|
|
1085
|
+
# output at the mercy of a runtime library it otherwise never links. The
|
|
1086
|
+
# baseline sequence runs on every AArch64 part.
|
|
1087
|
+
#
|
|
1088
|
+
# The retry loops branch backwards to a target inside the same IR
|
|
1089
|
+
# instruction, so they need no label fixup: the distance is already known
|
|
1090
|
+
# when the branch is emitted, exactly as in #emit_memcpy_loop.
|
|
1091
|
+
|
|
1092
|
+
# The opcode tables these sequences draw on (LDAR/STLR, LDAXR/STLXR,
|
|
1093
|
+
# CBNZ_W, ATOMIC_RMW_OPCODES) sit with the rest of the machine encodings at
|
|
1094
|
+
# the foot of the class, since the read-modify-write table is built from
|
|
1095
|
+
# the same shifted-register forms the ordinary binary ops use.
|
|
1096
|
+
|
|
1097
|
+
# :atomic_fence — DMB ISH, the full-system-domain barrier used by C11's
|
|
1098
|
+
# sequentially-consistent fence on the baseline AArch64 target.
|
|
1099
|
+
def emit_atomic_fence
|
|
1100
|
+
emit_word(0xD5033BBF)
|
|
1101
|
+
end
|
|
1102
|
+
|
|
1103
|
+
# :atomic_load — a sequentially consistent read (LDAR).
|
|
1104
|
+
def emit_atomic_load(dst, ptr, size)
|
|
1105
|
+
load_reg(A, ptr)
|
|
1106
|
+
emit_word(LDAR.fetch(size) | (A << 5) | A)
|
|
1107
|
+
store_reg(A, dst)
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
# :atomic_store — a sequentially consistent write (STLR).
|
|
1111
|
+
def emit_atomic_store(ptr, value, size)
|
|
1112
|
+
load_reg(A, ptr)
|
|
1113
|
+
load_reg(B, value)
|
|
1114
|
+
emit_word(STLR.fetch(size) | (A << 5) | B)
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
# :atomic_rmw — every read-modify-write kind as one exclusive retry loop:
|
|
1118
|
+
#
|
|
1119
|
+
# retry:
|
|
1120
|
+
# ldaxr C, [A] ; C = the value read
|
|
1121
|
+
# <op> D, C, B ; D = the value to store (absent for :exchange,
|
|
1122
|
+
# ; which stores the operand B unchanged)
|
|
1123
|
+
# stlxr wE, D, [A]
|
|
1124
|
+
# cbnz wE, retry
|
|
1125
|
+
#
|
|
1126
|
+
# The result is C for :exchange and the :fetch_* forms (the value read) and
|
|
1127
|
+
# D for the :*_fetch ones (the value stored). Unlike x86-64 — where an
|
|
1128
|
+
# exchange-add hands back the old value and the new one is recovered by
|
|
1129
|
+
# re-adding the operand — nothing is derived after the fact here: the loop
|
|
1130
|
+
# computes both values in registers already, so each form simply names the
|
|
1131
|
+
# one it wants. That is also why :or_fetch needs no special case on this
|
|
1132
|
+
# target: a loop is the only shape available, and every kind uses it.
|
|
1133
|
+
def emit_atomic_rmw(dst, ptr, value, kind, size)
|
|
1134
|
+
combine = ATOMIC_RMW_OPCODES.fetch(kind)
|
|
1135
|
+
load_reg(A, ptr) # A = the atomic object's address
|
|
1136
|
+
load_reg(B, value) # B = the operand
|
|
1137
|
+
start = @code.bytesize
|
|
1138
|
+
emit_word(LDAXR.fetch(size) | (A << 5) | C) # C = the value read
|
|
1139
|
+
if combine
|
|
1140
|
+
emit_word(combine[width(size)] | (B << 16) | (C << 5) | D) # D = C <op> B
|
|
1141
|
+
stored = D
|
|
1142
|
+
else
|
|
1143
|
+
stored = B # :exchange stores the operand as it stands
|
|
1144
|
+
end
|
|
1145
|
+
emit_word(STLXR.fetch(size) | (E << 16) | (A << 5) | stored)
|
|
1146
|
+
emit_atomic_retry(start, E)
|
|
1147
|
+
store_reg(ATOMIC_RMW_NEW_VALUE_KINDS.include?(kind) ? stored : C, dst)
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
# :atomic_cas — __atomic_compare_exchange_n:
|
|
1151
|
+
#
|
|
1152
|
+
# ldr D, [B] ; D = *expected
|
|
1153
|
+
# retry:
|
|
1154
|
+
# ldaxr E, [A] ; E = the value actually there
|
|
1155
|
+
# cmp E, D
|
|
1156
|
+
# b.ne done ; a mismatch abandons the monitor and the loop
|
|
1157
|
+
# stlxr wF, C, [A]
|
|
1158
|
+
# cbnz wF, retry
|
|
1159
|
+
# done:
|
|
1160
|
+
# cset wA, eq ; the _Bool result
|
|
1161
|
+
# b.eq skip
|
|
1162
|
+
# str E, [B] ; the failing path reports the value it saw
|
|
1163
|
+
# skip:
|
|
1164
|
+
#
|
|
1165
|
+
# The flags `cmp` left are still live at `done` on both paths — neither
|
|
1166
|
+
# STLXR nor CBNZ writes them — which is what lets one `cset` answer for the
|
|
1167
|
+
# loop however it was left. The write-back through `expected` is guarded by
|
|
1168
|
+
# the branch rather than done unconditionally so an `expected` that aliases
|
|
1169
|
+
# the atomic object does not overwrite the value just exchanged into it.
|
|
1170
|
+
# It is also the side effect <ruby/atomic.h>'s RUBY_ATOMIC_CAS reads its
|
|
1171
|
+
# answer out of, so it is not optional.
|
|
1172
|
+
def emit_atomic_cas(dst, ptr, expected, desired, size)
|
|
1173
|
+
load_reg(A, ptr) # A = the atomic object's address
|
|
1174
|
+
load_reg(B, expected) # B = &expected
|
|
1175
|
+
load_reg(C, desired) # C = the value to store
|
|
1176
|
+
emit_word(INT_LDST.fetch(size)[:load] | (B << 5) | D) # D = *expected
|
|
1177
|
+
start = @code.bytesize
|
|
1178
|
+
emit_word(LDAXR.fetch(size) | (A << 5) | E)
|
|
1179
|
+
emit_word(SUBS_SHIFTED[width(size)] | (D << 16) | (E << 5) | XZR) # cmp E, D
|
|
1180
|
+
mismatch = emit_atomic_forward_branch(:ne)
|
|
1181
|
+
emit_word(STLXR.fetch(size) | (F << 16) | (A << 5) | C)
|
|
1182
|
+
emit_atomic_retry(start, F)
|
|
1183
|
+
patch_atomic_forward_branch(mismatch)
|
|
1184
|
+
emit_cset(A, CONDITIONS.fetch(:eq))
|
|
1185
|
+
matched = emit_atomic_forward_branch(:eq)
|
|
1186
|
+
emit_word(INT_LDST.fetch(size)[:store] | (B << 5) | E) # *expected = what we saw
|
|
1187
|
+
patch_atomic_forward_branch(matched)
|
|
1188
|
+
store_reg(A, dst)
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
# Closes a retry loop: `cbnz w{status}, start`, taken while the
|
|
1192
|
+
# store-exclusive keeps reporting failure. The target is behind the branch
|
|
1193
|
+
# and its distance is already known, so no fixup is recorded.
|
|
1194
|
+
def emit_atomic_retry(start, status)
|
|
1195
|
+
words = (start - @code.bytesize) / 4
|
|
1196
|
+
emit_word(CBNZ_W | ((words & 0x7FFFF) << 5) | status)
|
|
1197
|
+
end
|
|
1198
|
+
|
|
1199
|
+
# Emits a `b.<condition>` whose target is a little further along in this
|
|
1200
|
+
# same instruction's code, leaving the 19-bit offset zero, and returns the
|
|
1201
|
+
# site so #patch_atomic_forward_branch can fill it in once the target is
|
|
1202
|
+
# reached. The IR's own label machinery is not used because neither end of
|
|
1203
|
+
# the branch is an IR label — both are interior to one instruction.
|
|
1204
|
+
def emit_atomic_forward_branch(condition)
|
|
1205
|
+
site = @code.bytesize
|
|
1206
|
+
emit_word(0x54000000 | CONDITIONS.fetch(condition))
|
|
1207
|
+
site
|
|
1208
|
+
end
|
|
1209
|
+
|
|
1210
|
+
# Resolves a branch #emit_atomic_forward_branch left open, its target being
|
|
1211
|
+
# the current position.
|
|
1212
|
+
def patch_atomic_forward_branch(site)
|
|
1213
|
+
words = (@code.bytesize - site) / 4
|
|
1214
|
+
word = @code[site, 4].unpack1("L<") | ((words & 0x7FFFF) << 5)
|
|
1215
|
+
@code[site, 4] = [word].pack("L<")
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
# --- register / slot access -------------------------------------------
|
|
1219
|
+
|
|
1220
|
+
# One ldr/str of `reg` at [base + offset], `size` bytes wide, from either
|
|
1221
|
+
# register file. The unsigned-offset immediate is scaled by the access
|
|
1222
|
+
# size, and every offset reaching here is a multiple of its own width (an
|
|
1223
|
+
# aggregate piece sits at a multiple of its width, and a copy step advances
|
|
1224
|
+
# by its own), so the scaled form always names it exactly.
|
|
1225
|
+
def emit_piece_access(reg, base, offset, size, load:, fp:)
|
|
1226
|
+
table = fp ? FP_LDST : INT_LDST
|
|
1227
|
+
emit_word(table.fetch(size)[load ? :load : :store] | ((offset / size) << 10) | (base << 5) | reg)
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1230
|
+
# ldr X{reg}, [slot]. Slots are always moved 64 bits at a time so a pointer
|
|
1231
|
+
# is never truncated; a 32-bit value's high half was zeroed when it was
|
|
1232
|
+
# produced. Reaches the slot through the scaled immediate when it fits,
|
|
1233
|
+
# otherwise through a composed address in the ADDR scratch.
|
|
1234
|
+
def load_reg(reg, vreg)
|
|
1235
|
+
load_frame_at(reg, slot_offset(vreg))
|
|
1236
|
+
end
|
|
1237
|
+
|
|
1238
|
+
# str X{reg}, [slot]. The counterpart of #load_reg.
|
|
1239
|
+
def store_reg(reg, vreg)
|
|
1240
|
+
store_frame_at(reg, slot_offset(vreg))
|
|
1241
|
+
end
|
|
1242
|
+
|
|
1243
|
+
# ldr X{reg}, [frame base + offset] for any fixed-frame offset, not just a
|
|
1244
|
+
# vreg slot's: the variadic register-save area and the caller's incoming
|
|
1245
|
+
# argument area are addressed this way too. Reaches the address through
|
|
1246
|
+
# the scaled immediate when it fits, otherwise through a composed address
|
|
1247
|
+
# in the ADDR scratch. The base is sp or x29 as #frame_base_register says
|
|
1248
|
+
# — every offset that reaches here names a place in the fixed frame, which
|
|
1249
|
+
# is what makes the substitution safe.
|
|
1250
|
+
def load_frame_at(reg, offset)
|
|
1251
|
+
if offset <= MAX_SCALED_OFFSET
|
|
1252
|
+
emit_word(0xF9400000 | ((offset / 8) << 10) | (frame_base_register << 5) | reg)
|
|
1253
|
+
else
|
|
1254
|
+
emit_slot_address(ADDR, offset)
|
|
1255
|
+
emit_word(0xF9400000 | (ADDR << 5) | reg) # ldr x, [ADDR]
|
|
1256
|
+
end
|
|
1257
|
+
end
|
|
1258
|
+
|
|
1259
|
+
# str X{reg}, [frame base + offset]. The counterpart of #load_frame_at.
|
|
1260
|
+
def store_frame_at(reg, offset)
|
|
1261
|
+
if offset <= MAX_SCALED_OFFSET
|
|
1262
|
+
emit_word(0xF9000000 | ((offset / 8) << 10) | (frame_base_register << 5) | reg)
|
|
1263
|
+
else
|
|
1264
|
+
emit_slot_address(ADDR, offset)
|
|
1265
|
+
emit_word(0xF9000000 | (ADDR << 5) | reg) # str x, [ADDR]
|
|
1266
|
+
end
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
# str X{reg}, [sp + offset] for a call's outgoing argument area. This one
|
|
1270
|
+
# is sp-relative on purpose and in every function: AAPCS64 has the callee
|
|
1271
|
+
# read its stack arguments from the sp the `bl` was executed with, so the
|
|
1272
|
+
# area is defined against sp and not against the fixed frame. In an
|
|
1273
|
+
# ordinary function the two coincide; in an alloca function they do not,
|
|
1274
|
+
# and the area is the block #place_arguments has just lowered sp over.
|
|
1275
|
+
def store_outgoing_at(reg, offset)
|
|
1276
|
+
if offset <= MAX_SCALED_OFFSET
|
|
1277
|
+
emit_word(0xF9000000 | ((offset / 8) << 10) | (SP << 5) | reg) # str x, [sp, #off]
|
|
1278
|
+
else
|
|
1279
|
+
emit_base_address(ADDR, SP, offset)
|
|
1280
|
+
emit_word(0xF9000000 | (ADDR << 5) | reg) # str x, [ADDR]
|
|
1281
|
+
end
|
|
1282
|
+
end
|
|
1283
|
+
|
|
1284
|
+
# ldr S{reg}/D{reg}, [slot]. Unlike the integer path, a floating value is
|
|
1285
|
+
# moved at its *own* width rather than always 64 bits: a `float` slot holds
|
|
1286
|
+
# four meaningful bytes and its upper half is indeterminate, so reading the
|
|
1287
|
+
# slot as a D register would feed the arithmetic a different number
|
|
1288
|
+
# entirely. That is the same rule the x86_64 backend follows with
|
|
1289
|
+
# movss/movsd, and it stays consistent with the slot discipline, which only
|
|
1290
|
+
# ever promises the low `size` bytes of a value narrower than eight.
|
|
1291
|
+
#
|
|
1292
|
+
# The value travels between slot and vector register directly, never
|
|
1293
|
+
# through a general-purpose register: an `fmov` round trip would cost two
|
|
1294
|
+
# extra instructions per operand and buy nothing, since ldr/str address a
|
|
1295
|
+
# V register with the same sp-relative form the X registers use — the sole
|
|
1296
|
+
# difference being the V bit that selects the register file.
|
|
1297
|
+
def load_fp(reg, vreg, size)
|
|
1298
|
+
emit_fp_slot_access(reg, slot_offset(vreg), size, load: true)
|
|
1299
|
+
end
|
|
1300
|
+
|
|
1301
|
+
# str S{reg}/D{reg}, [slot]. The counterpart of #load_fp.
|
|
1302
|
+
def store_fp(reg, vreg, size)
|
|
1303
|
+
emit_fp_slot_access(reg, slot_offset(vreg), size, load: false)
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
# The shared body of #load_fp / #store_fp. The unsigned-offset immediate is
|
|
1307
|
+
# scaled by the access size, so a 4-byte access reaches half as far as an
|
|
1308
|
+
# 8-byte one; either way a slot past the field is reached through an
|
|
1309
|
+
# address composed into the ADDR scratch, exactly as the integer path does.
|
|
1310
|
+
def emit_fp_slot_access(reg, offset, size, load:)
|
|
1311
|
+
base = FP_LDST.fetch(size)[load ? :load : :store]
|
|
1312
|
+
if offset <= 4095 * size
|
|
1313
|
+
emit_word(base | ((offset / size) << 10) | (frame_base_register << 5) | reg)
|
|
1314
|
+
else
|
|
1315
|
+
emit_slot_address(ADDR, offset)
|
|
1316
|
+
emit_word(base | (ADDR << 5) | reg)
|
|
1317
|
+
end
|
|
1318
|
+
end
|
|
1319
|
+
|
|
1320
|
+
# --- floating-point arithmetic ----------------------------------------
|
|
1321
|
+
|
|
1322
|
+
# :fadd/:fsub/:fmul/:fdiv — the operands are loaded into the FA/FB vector
|
|
1323
|
+
# scratch pair at the IR's operand width and combined by the single/double
|
|
1324
|
+
# form of the same three-register instruction, `size` selecting the type
|
|
1325
|
+
# field. A floating negation has no op of its own: the generator flips the
|
|
1326
|
+
# sign bit with an integer :xor, which the integer path already lowers.
|
|
1327
|
+
def emit_float_binary(inst, base_table)
|
|
1328
|
+
size = inst.size
|
|
1329
|
+
load_fp(FA, inst.a, size)
|
|
1330
|
+
load_fp(FB, inst.b, size)
|
|
1331
|
+
emit_word(base_table.fetch(size) | (FB << 16) | (FA << 5) | FA)
|
|
1332
|
+
store_fp(FA, inst.dst, size)
|
|
1333
|
+
end
|
|
1334
|
+
|
|
1335
|
+
# :feq..:fge — materialized into the destination as an int 0/1 the same way
|
|
1336
|
+
# an integer comparison is, but through `fcmp`, whose NZCV encoding of an
|
|
1337
|
+
# unordered result lets a single `cset` carry the NaN rule (see
|
|
1338
|
+
# FLOAT_CONDITIONS). The `cset` is the 32-bit form, so the slot gets a
|
|
1339
|
+
# clean int with its upper half zeroed.
|
|
1340
|
+
def emit_float_comparison(dst, a, b, condition, size)
|
|
1341
|
+
load_fp(FA, a, size)
|
|
1342
|
+
load_fp(FB, b, size)
|
|
1343
|
+
emit_word(FCMP.fetch(size) | (FB << 16) | (FA << 5)) # fcmp FA, FB
|
|
1344
|
+
emit_cset(A, condition)
|
|
1345
|
+
store_reg(A, dst)
|
|
1346
|
+
end
|
|
1347
|
+
|
|
1348
|
+
# :itof — an integer in a slot converted to a floating value. `int_desc` is
|
|
1349
|
+
# the source [width, signed?]: a width-8 source reads the X view and a
|
|
1350
|
+
# narrower one the W view (where it already sits sign- or zero-extended),
|
|
1351
|
+
# and the descriptor's signedness picks `scvtf` or `ucvtf`. Because the
|
|
1352
|
+
# machine offers both, no widening trick is needed for an `unsigned int`
|
|
1353
|
+
# source the way the signed-only x86 instruction requires. (An `unsigned
|
|
1354
|
+
# long` source never reaches a backend; the generator synthesizes it.)
|
|
1355
|
+
def emit_itof(dst, src_vreg, int_desc, float_size)
|
|
1356
|
+
int_width, signed = int_desc
|
|
1357
|
+
load_reg(A, src_vreg)
|
|
1358
|
+
emit_word(fp_int_convert(int_width == 8 ? 1 : 0, float_size,
|
|
1359
|
+
0b00, signed ? 0b010 : 0b011, A, FA))
|
|
1360
|
+
store_fp(FA, dst, float_size)
|
|
1361
|
+
end
|
|
1362
|
+
|
|
1363
|
+
# :ftoi — a floating value truncated toward zero into an integer slot.
|
|
1364
|
+
# `size` is the *source* float width and `int_desc` the destination
|
|
1365
|
+
# [width, signed?]; rmode 11 is the round-toward-zero mode the C cast
|
|
1366
|
+
# requires, and the descriptor picks `fcvtzs` or `fcvtzu`. Unlike x86, the
|
|
1367
|
+
# ISA has a native unsigned W-form, which is important at the rounded
|
|
1368
|
+
# 2^32 boundary: fcvtzu X yields 0x1_0000_0000 whose low 32 bits are zero,
|
|
1369
|
+
# while fcvtzu W saturates to UINT_MAX as gcc does. Use the C destination
|
|
1370
|
+
# width directly, so only a real 64-bit destination selects the X form.
|
|
1371
|
+
def emit_ftoi(dst, src_vreg, int_desc, float_size)
|
|
1372
|
+
int_width, signed = int_desc
|
|
1373
|
+
load_fp(FA, src_vreg, float_size)
|
|
1374
|
+
sf = int_width == 8 ? 1 : 0
|
|
1375
|
+
emit_word(fp_int_convert(sf, float_size,
|
|
1376
|
+
0b11, signed ? 0b000 : 0b001, FA, A))
|
|
1377
|
+
store_reg(A, dst)
|
|
1378
|
+
end
|
|
1379
|
+
|
|
1380
|
+
# :ftof — a float<->double width change, the one-source `fcvt`. `size` is
|
|
1381
|
+
# the source width, so the result is stored at the opposite one.
|
|
1382
|
+
def emit_ftof(dst, src_vreg, src_size)
|
|
1383
|
+
load_fp(FA, src_vreg, src_size)
|
|
1384
|
+
# fcvt: the type field names the source, the opc field the destination.
|
|
1385
|
+
emit_word((src_size == 8 ? 0x1E624000 : 0x1E22C000) | (FA << 5) | FA)
|
|
1386
|
+
store_fp(FA, dst, src_size == 8 ? 4 : 8)
|
|
1387
|
+
end
|
|
1388
|
+
|
|
1389
|
+
# "Conversion between floating-point and integer": sf selects the X (1) or
|
|
1390
|
+
# W (0) view of the integer side, the type field the float side, rmode the
|
|
1391
|
+
# rounding mode and opcode the direction and signedness.
|
|
1392
|
+
def fp_int_convert(sf, float_size, rmode, opcode, rn, rd)
|
|
1393
|
+
(sf << 31) | 0x1E200000 | (fp_type(float_size) << 22) |
|
|
1394
|
+
(rmode << 19) | (opcode << 16) | (rn << 5) | rd
|
|
1395
|
+
end
|
|
1396
|
+
|
|
1397
|
+
# The two-bit type field every floating instruction carries: 00 names the
|
|
1398
|
+
# single-precision (S register) form, 01 the double-precision (D) one.
|
|
1399
|
+
def fp_type(size) = size == 8 ? 0b01 : 0b00
|
|
1400
|
+
|
|
1401
|
+
# --- symbol addresses --------------------------------------------------
|
|
1402
|
+
|
|
1403
|
+
# :string_addr / :global_addr / :func_addr — forms the address of a symbol
|
|
1404
|
+
# that lives outside the frame (a string literal in .rodata, a file-scope
|
|
1405
|
+
# variable, or a function whose address is taken).
|
|
1406
|
+
#
|
|
1407
|
+
# AArch64 cannot name a 64-bit address in one instruction, so the address
|
|
1408
|
+
# is built in two: `adrp` puts the base of the symbol's 4 KiB page into the
|
|
1409
|
+
# register (its 21-bit immediate reaching +/-4 GiB from the referring
|
|
1410
|
+
# instruction's own page) and `add` then applies the symbol's offset within
|
|
1411
|
+
# that page. Both immediates are emitted as zero and left to the linker,
|
|
1412
|
+
# which is why the relocation is recorded once, at the `adrp`, with the
|
|
1413
|
+
# machine description spelling out that this kind costs two ELF entries
|
|
1414
|
+
# four bytes apart (see ELFWriter::AARCH64). The pair is position-
|
|
1415
|
+
# independent by construction — nothing here depends on where the code is
|
|
1416
|
+
# finally loaded — but it does bind the symbol at link time, which is what
|
|
1417
|
+
# separates it from the GOT path below.
|
|
1418
|
+
def emit_symbol_address(dst, reloc)
|
|
1419
|
+
@relocations << reloc.merge(offset: @code.bytesize)
|
|
1420
|
+
emit_word(0x90000000 | A) # adrp A, <page of sym>
|
|
1421
|
+
emit_add_imm(A, A, 0, shift12: false) # add A, A, #:lo12:sym
|
|
1422
|
+
store_reg(A, dst)
|
|
1423
|
+
end
|
|
1424
|
+
|
|
1425
|
+
# :got_addr — the PIC form of the above. Under -fPIC a symbol this unit
|
|
1426
|
+
# does not define must stay interposable, so its address is not formed but
|
|
1427
|
+
# *read*: `adrp` names the page of the symbol's Global Offset Table slot
|
|
1428
|
+
# and `ldr` loads the slot's contents, which the dynamic linker has filled
|
|
1429
|
+
# in with the symbol's run-time address. The shape is the same two-
|
|
1430
|
+
# instruction, two-relocation pair, differing only in the relocation types
|
|
1431
|
+
# and in the second instruction being a load rather than an add — so the
|
|
1432
|
+
# value that lands in the destination slot is a usable pointer either way
|
|
1433
|
+
# and every load or store through it is unchanged.
|
|
1434
|
+
def emit_got_address(dst, symbol)
|
|
1435
|
+
@relocations << { kind: :got, offset: @code.bytesize, symbol: symbol }
|
|
1436
|
+
emit_word(0x90000000 | A) # adrp A, <page of sym's GOT slot>
|
|
1437
|
+
emit_word(0xF9400000 | (A << 5) | A) # ldr A, [A, #:got_lo12:sym]
|
|
1438
|
+
store_reg(A, dst)
|
|
1439
|
+
end
|
|
1440
|
+
|
|
1441
|
+
# :addr_of / :object_addr — compute a frame address (sp + offset) into a
|
|
1442
|
+
# scratch register and park it in the destination slot as a pointer value.
|
|
1443
|
+
def emit_slot_address_to(dst, offset)
|
|
1444
|
+
emit_slot_address(A, offset)
|
|
1445
|
+
store_reg(A, dst)
|
|
1446
|
+
end
|
|
1447
|
+
|
|
1448
|
+
# Places (frame base) + offset (offset >= 0) into `reg`, the address of a
|
|
1449
|
+
# place in the fixed frame.
|
|
1450
|
+
def emit_slot_address(reg, offset)
|
|
1451
|
+
emit_base_address(reg, frame_base_register, offset)
|
|
1452
|
+
end
|
|
1453
|
+
|
|
1454
|
+
# Places base + offset (offset >= 0) into `reg`. Small offsets are one
|
|
1455
|
+
# add-immediate; offsets past the 12-bit field are split into a shifted
|
|
1456
|
+
# high part plus a low part (two adds cover up to ~16 MB); anything larger
|
|
1457
|
+
# is materialized and added with the extended-register form (which, unlike
|
|
1458
|
+
# the shifted-register add, accepts sp as its base — the reason the
|
|
1459
|
+
# fallback is written this way even though x29 would not need it).
|
|
1460
|
+
def emit_base_address(reg, base, offset)
|
|
1461
|
+
if offset <= 0xFFF
|
|
1462
|
+
emit_add_imm(reg, base, offset, shift12: false)
|
|
1463
|
+
elsif offset <= 0xFFFFFF
|
|
1464
|
+
emit_add_imm(reg, base, offset >> 12, shift12: true)
|
|
1465
|
+
low = offset & 0xFFF
|
|
1466
|
+
emit_add_imm(reg, reg, low, shift12: false) if low.positive?
|
|
1467
|
+
else
|
|
1468
|
+
materialize(reg, offset, 64)
|
|
1469
|
+
emit_word(ADD_EXTENDED | (reg << 16) | (base << 5) | reg) # add reg, base, reg, uxtx
|
|
1470
|
+
end
|
|
1471
|
+
end
|
|
1472
|
+
|
|
1473
|
+
# Which register names the fixed frame. sp in an ordinary function, where
|
|
1474
|
+
# it never moves after the prologue; x29 in one containing :alloca, where
|
|
1475
|
+
# it does. Every fixed-frame access asks here rather than naming sp, so
|
|
1476
|
+
# the two cases cannot drift apart — and an ordinary function's output is
|
|
1477
|
+
# unchanged, since the answer is still sp.
|
|
1478
|
+
def frame_base_register
|
|
1479
|
+
@uses_alloca ? FP : SP
|
|
1480
|
+
end
|
|
1481
|
+
|
|
1482
|
+
# add Rd, Rn, #imm12 (optionally << 12). Rn = 31 addresses sp here.
|
|
1483
|
+
def emit_add_imm(dst, rn, imm12, shift12:)
|
|
1484
|
+
emit_word(0x91000000 | (shift12 ? (1 << 22) : 0) | ((imm12 & 0xFFF) << 10) | (rn << 5) | dst)
|
|
1485
|
+
end
|
|
1486
|
+
|
|
1487
|
+
# Lowers or raises sp by `amount` (kept 16-aligned by the caller). Uses one
|
|
1488
|
+
# or two add/sub-immediates for realistic frames, falling back to a
|
|
1489
|
+
# materialized extended-register add/sub for an implausibly large frame.
|
|
1490
|
+
def adjust_sp(amount, sub:)
|
|
1491
|
+
base = sub ? 0xD1000000 : 0x91000000
|
|
1492
|
+
if amount <= 0xFFF
|
|
1493
|
+
emit_word(base | ((amount & 0xFFF) << 10) | (SP << 5) | SP)
|
|
1494
|
+
elsif amount <= 0xFFFFFF
|
|
1495
|
+
emit_word(base | (1 << 22) | (((amount >> 12) & 0xFFF) << 10) | (SP << 5) | SP)
|
|
1496
|
+
low = amount & 0xFFF
|
|
1497
|
+
emit_word(base | ((low & 0xFFF) << 10) | (SP << 5) | SP) if low.positive?
|
|
1498
|
+
else
|
|
1499
|
+
materialize(ADDR, amount, 64)
|
|
1500
|
+
ext = sub ? SUB_EXTENDED : ADD_EXTENDED
|
|
1501
|
+
emit_word(ext | (ADDR << 16) | (SP << 5) | SP) # add/sub sp, sp, ADDR, uxtx
|
|
1502
|
+
end
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1505
|
+
# stp Xt1, Xt2, [Xn, #offset] — the saved-record store (offset 0 here).
|
|
1506
|
+
def emit_stp(rt1, rt2, rn, offset)
|
|
1507
|
+
emit_word(0xA9000000 | (((offset / 8) & 0x7F) << 15) | (rt2 << 10) | (rn << 5) | rt1)
|
|
1508
|
+
end
|
|
1509
|
+
|
|
1510
|
+
# ldp Xt1, Xt2, [Xn, #offset] — the saved-record reload.
|
|
1511
|
+
def emit_ldp(rt1, rt2, rn, offset)
|
|
1512
|
+
emit_word(0xA9400000 | (((offset / 8) & 0x7F) << 15) | (rt2 << 10) | (rn << 5) | rt1)
|
|
1513
|
+
end
|
|
1514
|
+
|
|
1515
|
+
# --- immediate materialization ----------------------------------------
|
|
1516
|
+
|
|
1517
|
+
# Builds `value` into `reg` with a movz for the low 16 bits followed by a
|
|
1518
|
+
# movk for each higher non-zero 16-bit field. `bits` is 32 (a movz to the
|
|
1519
|
+
# W view zeroes the upper 32) or 64. Always at least one instruction, and a
|
|
1520
|
+
# function only of the value, so the output stays deterministic.
|
|
1521
|
+
def materialize(reg, value, bits)
|
|
1522
|
+
value &= bits == 64 ? 0xFFFFFFFFFFFFFFFF : 0xFFFFFFFF
|
|
1523
|
+
sf = bits == 64 ? 1 : 0
|
|
1524
|
+
halfwords = bits / 16
|
|
1525
|
+
emit_movz(reg, value & 0xFFFF, 0, sf)
|
|
1526
|
+
(1...halfwords).each do |i|
|
|
1527
|
+
field = (value >> (16 * i)) & 0xFFFF
|
|
1528
|
+
emit_movk(reg, field, i, sf) unless field.zero?
|
|
1529
|
+
end
|
|
1530
|
+
end
|
|
1531
|
+
|
|
1532
|
+
def emit_movz(reg, imm16, shift_hw, sf)
|
|
1533
|
+
base = sf == 1 ? 0xD2800000 : 0x52800000
|
|
1534
|
+
emit_word(base | (shift_hw << 21) | ((imm16 & 0xFFFF) << 5) | reg)
|
|
1535
|
+
end
|
|
1536
|
+
|
|
1537
|
+
def emit_movk(reg, imm16, shift_hw, sf)
|
|
1538
|
+
base = sf == 1 ? 0xF2800000 : 0x72800000
|
|
1539
|
+
emit_word(base | (shift_hw << 21) | ((imm16 & 0xFFFF) << 5) | reg)
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
# --- comparisons and branches -----------------------------------------
|
|
1543
|
+
|
|
1544
|
+
# cset Rd, <cond> — writes 1 when the condition held after `cmp`, else 0.
|
|
1545
|
+
# It is a `csinc Rd, xzr, xzr, <inverted cond>` (increment 0 when the
|
|
1546
|
+
# condition is false is inverted so a true condition yields 1). The 32-bit
|
|
1547
|
+
# form zeroes the upper half, giving a clean int 0/1.
|
|
1548
|
+
def emit_cset(dst, condition)
|
|
1549
|
+
inverted = condition ^ 1
|
|
1550
|
+
emit_word(0x1A800400 | (XZR << 16) | (inverted << 12) | (XZR << 5) | dst)
|
|
1551
|
+
end
|
|
1552
|
+
|
|
1553
|
+
# b <label> — an unconditional branch, immediate left zero and recorded as
|
|
1554
|
+
# a fixup patched once the label offset is known.
|
|
1555
|
+
def emit_b(label_id)
|
|
1556
|
+
@fixups << [@code.bytesize, label_id, :b]
|
|
1557
|
+
emit_word(0x14000000)
|
|
1558
|
+
end
|
|
1559
|
+
|
|
1560
|
+
# Patches every recorded branch with the signed word distance from the
|
|
1561
|
+
# branch to its target. b uses a 26-bit field (±128 MB), cbz a 19-bit one
|
|
1562
|
+
# (±1 MB); both are far wider than any single function needs, but an
|
|
1563
|
+
# overrun raises rather than silently truncating.
|
|
1564
|
+
def resolve_fixups
|
|
1565
|
+
@fixups.each do |patch_offset, label_id, kind|
|
|
1566
|
+
target = @labels[label_id]
|
|
1567
|
+
raise "unresolved label #{label_id}" unless target
|
|
1568
|
+
|
|
1569
|
+
words = (target - patch_offset) / 4
|
|
1570
|
+
word = @code[patch_offset, 4].unpack1("L<")
|
|
1571
|
+
case kind
|
|
1572
|
+
when :b
|
|
1573
|
+
raise "branch displacement out of range" unless words.between?(-(1 << 25), (1 << 25) - 1)
|
|
1574
|
+
|
|
1575
|
+
word |= words & 0x03FFFFFF
|
|
1576
|
+
when :cbz
|
|
1577
|
+
raise "conditional branch displacement out of range" unless words.between?(-(1 << 18), (1 << 18) - 1)
|
|
1578
|
+
|
|
1579
|
+
word |= (words & 0x7FFFF) << 5
|
|
1580
|
+
end
|
|
1581
|
+
@code[patch_offset, 4] = [word].pack("L<")
|
|
1582
|
+
end
|
|
1583
|
+
end
|
|
1584
|
+
|
|
1585
|
+
# --- encoding helpers -------------------------------------------------
|
|
1586
|
+
|
|
1587
|
+
# The width key for the shifted-register op tables: 64 for a size-8 IR op
|
|
1588
|
+
# (long/pointer), 32 otherwise (the default int width).
|
|
1589
|
+
def width(size)
|
|
1590
|
+
size == 8 ? 64 : 32
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
# Base opcodes for the shifted-register data-processing instructions,
|
|
1594
|
+
# keyed by operand width. The Rm/Rn/Rd fields are OR-ed in by the caller;
|
|
1595
|
+
# the shift amount is always zero (a plain register operand).
|
|
1596
|
+
ADD_SHIFTED = { 32 => 0x0B000000, 64 => 0x8B000000 }.freeze
|
|
1597
|
+
SUB_SHIFTED = { 32 => 0x4B000000, 64 => 0xCB000000 }.freeze
|
|
1598
|
+
AND_SHIFTED = { 32 => 0x0A000000, 64 => 0x8A000000 }.freeze
|
|
1599
|
+
ORR_SHIFTED = { 32 => 0x2A000000, 64 => 0xAA000000 }.freeze
|
|
1600
|
+
EOR_SHIFTED = { 32 => 0x4A000000, 64 => 0xCA000000 }.freeze
|
|
1601
|
+
LSLV = { 32 => 0x1AC02000, 64 => 0x9AC02000 }.freeze
|
|
1602
|
+
LSRV = { 32 => 0x1AC02400, 64 => 0x9AC02400 }.freeze
|
|
1603
|
+
ASRV = { 32 => 0x1AC02800, 64 => 0x9AC02800 }.freeze
|
|
1604
|
+
|
|
1605
|
+
# umulh Rd, Rn, Rm — a "data-processing (3 source)" instruction in the
|
|
1606
|
+
# same family as madd/msub, with op31 = 110 selecting the unsigned-high
|
|
1607
|
+
# variant and Ra fixed to xzr (31) in bits [14:10]. Only the 64-bit (sf=1)
|
|
1608
|
+
# encoding exists; there is no 32-bit counterpart to pick between.
|
|
1609
|
+
UMULH = 0x9BC07C00
|
|
1610
|
+
|
|
1611
|
+
# "Data-processing (1 source)":
|
|
1612
|
+
# sf(31) 1(30) S(29)=0 11010110(28:21) opcode2(20:16)=00000
|
|
1613
|
+
# opcode(15:10) Rn(9:5) Rd(4:0)
|
|
1614
|
+
# opcode 000000 is RBIT (reverse the bit order in the register) and
|
|
1615
|
+
# 000100 is CLZ (count leading zeros), so the two differ by 4 in that
|
|
1616
|
+
# field — 0x1000 once shifted into place. Both exist at either operand
|
|
1617
|
+
# width, sf choosing the W or X view.
|
|
1618
|
+
RBIT = { 32 => 0x5AC00000, 64 => 0xDAC00000 }.freeze
|
|
1619
|
+
CLZ = { 32 => 0x5AC01000, 64 => 0xDAC01000 }.freeze
|
|
1620
|
+
|
|
1621
|
+
# "Add/subtract (extended register)", 64-bit, with option = 011 (UXTX, the
|
|
1622
|
+
# identity extension of an X operand) and no shift:
|
|
1623
|
+
# sf(31)=1 op(30) S(29)=0 01011(28:24) opt(23:22)=00 1(21) Rm(20:16)
|
|
1624
|
+
# option(15:13)=011 imm3(12:10)=000 Rn(9:5) Rd(4:0)
|
|
1625
|
+
# This family, not the shifted-register one, is what a register-sized
|
|
1626
|
+
# stack adjustment must use: here a register field of 31 reads as sp,
|
|
1627
|
+
# while in the shifted-register add/sub the same 31 reads as the zero
|
|
1628
|
+
# register (which is why "sub sp, sp, x9" has no shifted-register form).
|
|
1629
|
+
ADD_EXTENDED = 0x8B206000
|
|
1630
|
+
SUB_EXTENDED = 0xCB206000
|
|
1631
|
+
|
|
1632
|
+
# "Logical (immediate)", 64-bit AND with the bitmask immediate
|
|
1633
|
+
# 0xFFFF_FFFF_FFFF_FFF0:
|
|
1634
|
+
# sf(31)=1 opc(30:29)=00 100100(28:23) N(22)=1 immr(21:16) imms(15:10)
|
|
1635
|
+
# Rn(9:5) Rd(4:0)
|
|
1636
|
+
# The mask is a run of 60 ones, so it is one of the patterns this encoding
|
|
1637
|
+
# can name: imms = 59 gives the run length (ones minus one) and immr = 60
|
|
1638
|
+
# the right rotation that moves the run's four-zero gap to the bottom.
|
|
1639
|
+
# Rounding an alloca size up to 16 therefore costs one instruction rather
|
|
1640
|
+
# than the four a movz/movk of an arbitrary 64-bit constant would.
|
|
1641
|
+
AND_NOT15 = 0x927CEC00
|
|
1642
|
+
|
|
1643
|
+
# The flag-setting subtract, in the same shifted-register family. With the
|
|
1644
|
+
# zero register as its destination it is the `cmp` both a comparison and an
|
|
1645
|
+
# atomic compare-exchange leave their answer in.
|
|
1646
|
+
SUBS_SHIFTED = { 32 => 0x6B000000, 64 => 0xEB000000 }.freeze
|
|
1647
|
+
|
|
1648
|
+
# Load-acquire (LDAR) / store-release (STLR), keyed by access width. A
|
|
1649
|
+
# seq_cst load is a plain LDAR and a seq_cst store a plain STLR: AArch64's
|
|
1650
|
+
# acquire/release instructions are *sequentially consistent* with respect
|
|
1651
|
+
# to one another (armv8 gives LDAR/STLR the RCsc property, unlike C++'s
|
|
1652
|
+
# weaker RCpc acquire/release), so neither needs an extra barrier.
|
|
1653
|
+
LDAR = { 4 => 0x88DFFC00, 8 => 0xC8DFFC00 }.freeze
|
|
1654
|
+
STLR = { 4 => 0x889FFC00, 8 => 0xC89FFC00 }.freeze
|
|
1655
|
+
|
|
1656
|
+
# The exclusive pair every read-modify-write sequence is built from: LDAXR
|
|
1657
|
+
# takes the exclusive monitor and STLXR releases it, writing 0 into its
|
|
1658
|
+
# status register when the store went through and 1 when the monitor had
|
|
1659
|
+
# been lost. The status register is a W register at either access width.
|
|
1660
|
+
LDAXR = { 4 => 0x885FFC00, 8 => 0xC85FFC00 }.freeze
|
|
1661
|
+
STLXR = { 4 => 0x8800FC00, 8 => 0xC800FC00 }.freeze
|
|
1662
|
+
|
|
1663
|
+
# cbnz w{Rt}, <offset> — the retry branch that closes each of those loops,
|
|
1664
|
+
# taken while the store-exclusive keeps reporting failure. The 19-bit
|
|
1665
|
+
# signed word offset is OR-ed in at bit 5.
|
|
1666
|
+
CBNZ_W = 0x35000000
|
|
1667
|
+
|
|
1668
|
+
# The instruction each :atomic_rmw kind combines the value read with,
|
|
1669
|
+
# keyed the same way the shifted-register tables above are. :exchange has
|
|
1670
|
+
# none — it stores its operand unchanged.
|
|
1671
|
+
ATOMIC_RMW_OPCODES = {
|
|
1672
|
+
exchange: nil,
|
|
1673
|
+
fetch_add: ADD_SHIFTED, add_fetch: ADD_SHIFTED,
|
|
1674
|
+
fetch_sub: SUB_SHIFTED, sub_fetch: SUB_SHIFTED,
|
|
1675
|
+
or_fetch: ORR_SHIFTED
|
|
1676
|
+
}.freeze
|
|
1677
|
+
|
|
1678
|
+
# The :atomic_rmw kinds whose value is the one *stored* rather than the one
|
|
1679
|
+
# read — gcc's "__atomic_<op>_fetch" half of each pair.
|
|
1680
|
+
ATOMIC_RMW_NEW_VALUE_KINDS = %i[add_fetch sub_fetch or_fetch].freeze
|
|
1681
|
+
|
|
1682
|
+
# Base opcodes for the floating instructions, keyed by the IR operand size
|
|
1683
|
+
# (4 float / 8 double), which is the type field the encoding carries. The
|
|
1684
|
+
# first four are "Floating-point data-processing (2 source)" and differ
|
|
1685
|
+
# only in their opcode field; FCMP is the compare whose result goes to the
|
|
1686
|
+
# flags rather than to a register, so it names no destination.
|
|
1687
|
+
FMUL = { 4 => 0x1E200800, 8 => 0x1E600800 }.freeze
|
|
1688
|
+
FDIV = { 4 => 0x1E201800, 8 => 0x1E601800 }.freeze
|
|
1689
|
+
FADD = { 4 => 0x1E202800, 8 => 0x1E602800 }.freeze
|
|
1690
|
+
FSUB = { 4 => 0x1E203800, 8 => 0x1E603800 }.freeze
|
|
1691
|
+
FCMP = { 4 => 0x1E202000, 8 => 0x1E602000 }.freeze
|
|
1692
|
+
|
|
1693
|
+
# "Load/store register (unsigned immediate)" with V = 1, which selects the
|
|
1694
|
+
# vector/floating register file. The size field follows the access width:
|
|
1695
|
+
# 10 for a 32-bit S register, 11 for a 64-bit D register.
|
|
1696
|
+
FP_LDST = {
|
|
1697
|
+
4 => { load: 0xBD400000, store: 0xBD000000 },
|
|
1698
|
+
8 => { load: 0xFD400000, store: 0xFD000000 }
|
|
1699
|
+
}.freeze
|
|
1700
|
+
|
|
1701
|
+
# The same form with V = 0, the general-purpose register file, at each of
|
|
1702
|
+
# the four access widths. The narrow loads are the zero-extending ones
|
|
1703
|
+
# (ldrb/ldrh rather than ldrsb/ldrsh): both a whole-object copy and an
|
|
1704
|
+
# aggregate piece move bytes rather than numbers, so nothing here should
|
|
1705
|
+
# give them a sign.
|
|
1706
|
+
INT_LDST = {
|
|
1707
|
+
1 => { load: 0x39400000, store: 0x39000000 },
|
|
1708
|
+
2 => { load: 0x79400000, store: 0x79000000 },
|
|
1709
|
+
4 => { load: 0xB9400000, store: 0xB9000000 },
|
|
1710
|
+
8 => { load: 0xF9400000, store: 0xF9000000 }
|
|
1711
|
+
}.freeze
|
|
1712
|
+
|
|
1713
|
+
def align16(value)
|
|
1714
|
+
(value + 15) & ~15
|
|
1715
|
+
end
|
|
1716
|
+
|
|
1717
|
+
# Appends one 32-bit instruction word, little-endian (AArch64 is
|
|
1718
|
+
# little-endian and every instruction is exactly four bytes).
|
|
1719
|
+
def emit_word(word)
|
|
1720
|
+
@code << [word & 0xFFFFFFFF].pack("L<")
|
|
1721
|
+
end
|
|
1722
|
+
end
|
|
1723
|
+
end
|
|
1724
|
+
end
|