rubycc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +56 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE +52 -0
  5. data/README.md +208 -0
  6. data/data/README.md +117 -0
  7. data/data/r10_corpus_scan.json +4082 -0
  8. data/data/r10_manual_classification.json +5342 -0
  9. data/data/r10_verification_m4a.json +1531 -0
  10. data/data/r10_verification_m4b.json +1818 -0
  11. data/data/r10_verification_m4c.json +1489 -0
  12. data/data/r10_verification_m4d.json +318 -0
  13. data/data/r10_verification_rbs.json +192 -0
  14. data/data/verified_gems.json +397 -0
  15. data/exe/rmake +16 -0
  16. data/exe/rubycc +9 -0
  17. data/exe/rubycc-ar +114 -0
  18. data/exe/rubycc-doctor +14 -0
  19. data/exe/rubycc-pkgconf +8 -0
  20. data/include/float.h +87 -0
  21. data/include/iso646.h +18 -0
  22. data/include/libc/alloca.h +18 -0
  23. data/include/libc/arpa/inet.h +61 -0
  24. data/include/libc/assert.h +43 -0
  25. data/include/libc/dirent.h +72 -0
  26. data/include/libc/dlfcn.h +58 -0
  27. data/include/libc/features.h +205 -0
  28. data/include/libc/glibc/aarch64/ctype.h +108 -0
  29. data/include/libc/glibc/aarch64/endian.h +54 -0
  30. data/include/libc/glibc/aarch64/errno.h +154 -0
  31. data/include/libc/glibc/aarch64/fcntl.h +144 -0
  32. data/include/libc/glibc/aarch64/inttypes.h +182 -0
  33. data/include/libc/glibc/aarch64/limits.h +63 -0
  34. data/include/libc/glibc/aarch64/pthread.h +131 -0
  35. data/include/libc/glibc/aarch64/setjmp.h +84 -0
  36. data/include/libc/glibc/aarch64/stdint.h +174 -0
  37. data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
  38. data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
  39. data/include/libc/glibc/aarch64/sys/select.h +75 -0
  40. data/include/libc/glibc/aarch64/sys/stat.h +136 -0
  41. data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
  42. data/include/libc/glibc/aarch64/sys/time.h +74 -0
  43. data/include/libc/glibc/aarch64/sys/types.h +127 -0
  44. data/include/libc/glibc/aarch64/time.h +113 -0
  45. data/include/libc/glibc/x86_64/ctype.h +108 -0
  46. data/include/libc/glibc/x86_64/endian.h +54 -0
  47. data/include/libc/glibc/x86_64/errno.h +154 -0
  48. data/include/libc/glibc/x86_64/fcntl.h +134 -0
  49. data/include/libc/glibc/x86_64/inttypes.h +182 -0
  50. data/include/libc/glibc/x86_64/limits.h +61 -0
  51. data/include/libc/glibc/x86_64/pthread.h +111 -0
  52. data/include/libc/glibc/x86_64/setjmp.h +82 -0
  53. data/include/libc/glibc/x86_64/stdint.h +163 -0
  54. data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
  55. data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
  56. data/include/libc/glibc/x86_64/sys/select.h +75 -0
  57. data/include/libc/glibc/x86_64/sys/stat.h +130 -0
  58. data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
  59. data/include/libc/glibc/x86_64/sys/time.h +74 -0
  60. data/include/libc/glibc/x86_64/sys/types.h +123 -0
  61. data/include/libc/glibc/x86_64/time.h +113 -0
  62. data/include/libc/grp.h +50 -0
  63. data/include/libc/langinfo.h +126 -0
  64. data/include/libc/link.h +18 -0
  65. data/include/libc/locale.h +83 -0
  66. data/include/libc/math.h +191 -0
  67. data/include/libc/netinet/in.h +126 -0
  68. data/include/libc/netinet/tcp.h +44 -0
  69. data/include/libc/poll.h +46 -0
  70. data/include/libc/pwd.h +58 -0
  71. data/include/libc/regex.h +53 -0
  72. data/include/libc/sched.h +35 -0
  73. data/include/libc/signal.h +204 -0
  74. data/include/libc/stdio.h +157 -0
  75. data/include/libc/stdlib.h +92 -0
  76. data/include/libc/string.h +74 -0
  77. data/include/libc/strings.h +25 -0
  78. data/include/libc/sys/cdefs.h +126 -0
  79. data/include/libc/sys/inotify.h +111 -0
  80. data/include/libc/sys/ioctl.h +36 -0
  81. data/include/libc/sys/mman.h +65 -0
  82. data/include/libc/sys/param.h +41 -0
  83. data/include/libc/sys/resource.h +109 -0
  84. data/include/libc/sys/socket.h +167 -0
  85. data/include/libc/sys/statfs.h +82 -0
  86. data/include/libc/sys/timerfd.h +55 -0
  87. data/include/libc/sys/uio.h +40 -0
  88. data/include/libc/sys/un.h +25 -0
  89. data/include/libc/sys/utsname.h +35 -0
  90. data/include/libc/sys/wait.h +135 -0
  91. data/include/libc/termios.h +179 -0
  92. data/include/libc/unistd.h +194 -0
  93. data/include/stdalign.h +16 -0
  94. data/include/stdarg.h +31 -0
  95. data/include/stdatomic.h +158 -0
  96. data/include/stdbool.h +15 -0
  97. data/include/stdckdint.h +28 -0
  98. data/include/stddef.h +60 -0
  99. data/include/stdnoreturn.h +18 -0
  100. data/include/x86intrin.h +16 -0
  101. data/lib/rubycc/backend/aarch64.rb +1724 -0
  102. data/lib/rubycc/backend/x86_64.rb +1369 -0
  103. data/lib/rubycc/compile_error.rb +36 -0
  104. data/lib/rubycc/compiler.rb +305 -0
  105. data/lib/rubycc/doctor/builder.rb +151 -0
  106. data/lib/rubycc/doctor/cli.rb +190 -0
  107. data/lib/rubycc/doctor/fetcher.rb +84 -0
  108. data/lib/rubycc/doctor/gemfile.rb +115 -0
  109. data/lib/rubycc/doctor/verified_gems.rb +114 -0
  110. data/lib/rubycc/doctor.rb +16 -0
  111. data/lib/rubycc/driver.rb +463 -0
  112. data/lib/rubycc/front/ast.rb +528 -0
  113. data/lib/rubycc/front/constant_evaluator.rb +631 -0
  114. data/lib/rubycc/front/initializer_resolver.rb +592 -0
  115. data/lib/rubycc/front/lexeme_reader.rb +460 -0
  116. data/lib/rubycc/front/lexer.rb +232 -0
  117. data/lib/rubycc/front/parser.rb +4122 -0
  118. data/lib/rubycc/front/token.rb +49 -0
  119. data/lib/rubycc/ir/call_convention.rb +486 -0
  120. data/lib/rubycc/ir/generator.rb +6036 -0
  121. data/lib/rubycc/ir/ir.rb +417 -0
  122. data/lib/rubycc/link/compat_runtime.rb +89 -0
  123. data/lib/rubycc/link/errors.rb +13 -0
  124. data/lib/rubycc/link/executable_linker.rb +405 -0
  125. data/lib/rubycc/link/library_resolver.rb +437 -0
  126. data/lib/rubycc/link/partial_linker.rb +546 -0
  127. data/lib/rubycc/link/shared_linker.rb +1732 -0
  128. data/lib/rubycc/mkmf_shim.rb +83 -0
  129. data/lib/rubycc/objfile/ar_archive.rb +437 -0
  130. data/lib/rubycc/objfile/elf_reader.rb +646 -0
  131. data/lib/rubycc/objfile/elf_writer.rb +891 -0
  132. data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
  133. data/lib/rubycc/pkgconf/cli.rb +139 -0
  134. data/lib/rubycc/pkgconf/errors.rb +39 -0
  135. data/lib/rubycc/pkgconf/model.rb +28 -0
  136. data/lib/rubycc/pkgconf/parser.rb +109 -0
  137. data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
  138. data/lib/rubycc/pkgconf/resolver.rb +70 -0
  139. data/lib/rubycc/pkgconf/search_path.rb +38 -0
  140. data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
  141. data/lib/rubycc/preprocess/constant_expression.rb +219 -0
  142. data/lib/rubycc/preprocess/glibc_version.rb +124 -0
  143. data/lib/rubycc/preprocess/pp_token.rb +62 -0
  144. data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
  145. data/lib/rubycc/preprocess/scanner.rb +290 -0
  146. data/lib/rubycc/preprocess/token_converter.rb +157 -0
  147. data/lib/rubycc/rmake/cli.rb +143 -0
  148. data/lib/rubycc/rmake/errors.rb +71 -0
  149. data/lib/rubycc/rmake/executor.rb +818 -0
  150. data/lib/rubycc/rmake/expander.rb +251 -0
  151. data/lib/rubycc/rmake/makefile.rb +352 -0
  152. data/lib/rubycc/rmake/model.rb +137 -0
  153. data/lib/rubycc/rmake/parser.rb +226 -0
  154. data/lib/rubycc/rmake/rmake.rb +15 -0
  155. data/lib/rubycc/type.rb +1236 -0
  156. data/lib/rubycc/version.rb +5 -0
  157. data/lib/rubycc.rb +36 -0
  158. data/lib/rubygems_plugin.rb +102 -0
  159. metadata +219 -0
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubycc
4
+ module Front
5
+ # A single lexical token. `type` is one of :num, :float, :ident, :keyword,
6
+ # :punct, :string, :eof. `value` is an Integer for :num, a Ruby Float for
7
+ # :float (a floating constant), an ASCII-8BIT String of the escape-resolved
8
+ # bytes for :string, a String for :ident/:keyword/:punct, and nil for :eof.
9
+ # `base` (10/8/16) and `suffix` accompany a numeric constant so the parser
10
+ # can fix its type: an integer :num carries a base and a normalized u/l
11
+ # suffix run ("", "u", "ul") per 6.4.4.1, while a :float carries no base but
12
+ # a normalized floating suffix ("" for double, "f" for float, "l" for long
13
+ # double, itself treated as double). Both are nil on every other token (a
14
+ # character constant is a :num with base 10 and no suffix). The remaining
15
+ # fields locate the token in the source for diagnostics.
16
+ class Token
17
+ TYPES = %i[num float ident keyword punct string eof].freeze
18
+
19
+ attr_reader :type, :value, :filename, :line, :column, :source_line, :base, :suffix
20
+
21
+ def initialize(type:, value:, filename:, line:, column:, source_line:, base: nil, suffix: nil)
22
+ @type = type
23
+ @value = value
24
+ @filename = filename
25
+ @line = line
26
+ @column = column
27
+ @source_line = source_line
28
+ @base = base
29
+ @suffix = suffix
30
+ end
31
+
32
+ def punct?(str)
33
+ type == :punct && value == str
34
+ end
35
+
36
+ def keyword?(str)
37
+ type == :keyword && value == str
38
+ end
39
+
40
+ def eof?
41
+ type == :eof
42
+ end
43
+
44
+ def inspect
45
+ "#<Token #{type} #{value.inspect} @#{line}:#{column}>"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,486 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../type"
4
+
5
+ module Rubycc
6
+ module IR
7
+ # One piece of a by-value aggregate as its convention moves it: the byte
8
+ # `offset` within the aggregate the piece is read from (and written back to
9
+ # at the far end), the `size` of that access, and the `kind` of place it
10
+ # travels in (:gp an integer register, :sse4/:sse8 a vector one, :mem a
11
+ # stack eightbyte).
12
+ #
13
+ # An aggregate is never moved as a whole — the generator takes it apart into
14
+ # these pieces, loads each into a virtual register and hands the backend one
15
+ # ABI slot per piece — so the piece list *is* the classification, and it is
16
+ # exactly where the two conventions part ways. System V AMD64 always cuts on
17
+ # eightbyte boundaries (offset 8*i, size 8, the eightbyte's class), while
18
+ # AAPCS64 cuts a homogeneous floating aggregate along its members instead
19
+ # (offset 4*i, size 4 for a struct of floats, each member its own vector
20
+ # register). That is why a piece carries an offset and a width rather than
21
+ # just an index: struct { float a, b; } is one eightbyte on x86-64 and two
22
+ # single-precision registers on aarch64.
23
+ AbiPiece = Data.define(:offset, :size, :kind)
24
+
25
+ # How a convention passes one aggregate by value:
26
+ # :registers — `pieces` names each register-borne piece;
27
+ # :memory — the value is laid into the caller's stack argument area
28
+ # whole, and a result of this shape is written through a
29
+ # hidden pointer the caller passes as an ordinary leading
30
+ # argument (System V AMD64's MEMORY class);
31
+ # :by_reference — the caller copies the value somewhere of its own and
32
+ # passes the copy's *address* instead, and a result of
33
+ # this shape is written through the convention's dedicated
34
+ # indirect result register (AAPCS64's x8).
35
+ # `align16` marks an aggregate of 16-byte alignment (one built around an
36
+ # __int128, or declared _Alignas(16)). Both conventions round its *stack*
37
+ # offset up to a 16-byte boundary when it spills; AAPCS64 additionally rounds
38
+ # NGRN up so its first integer register is even-numbered, landing it in an
39
+ # aligned x-register pair (System V has no such register rule — a 16-byte
40
+ # argument takes two consecutive registers of either parity).
41
+ AggregatePlan = Data.define(:mode, :pieces, :align16)
42
+
43
+ # One argument as the placement pass sees it: the candidate kind of each of
44
+ # its ABI slots, whether its aggregate is 16-byte aligned, and how many
45
+ # stack eightbytes it occupies should it spill (ceil(size/8), so the placer
46
+ # can track the stack offset a 16-byte-aligned aggregate must be padded up
47
+ # to). Placement only ever needs to count and align, never to know a C type.
48
+ ArgumentRequest = Data.define(:kinds, :align16, :mem_eightbytes)
49
+
50
+ # The part of a target's calling convention the IR generator has to know
51
+ # about, so that where every argument lands is decided once — where the
52
+ # argument's C type is still in hand — rather than guessed at by a backend
53
+ # that only sees a tag.
54
+ #
55
+ # Three things are target-specific and all three live here:
56
+ #
57
+ # * how many registers there are to hand out. System V AMD64 offers six
58
+ # integer registers (rdi, rsi, rdx, rcx, r8, r9) and AAPCS64 eight
59
+ # (x0..x7); both offer eight vector ones. Getting the counts from the
60
+ # target is what lets a seventh integer argument reach x6 on aarch64
61
+ # instead of arriving tagged for the stack.
62
+ #
63
+ # * how an aggregate is cut up and where the pieces go (#aggregate_plan).
64
+ # The System V eightbyte classification and the AAPCS64 HFA / 16-byte /
65
+ # by-reference rules disagree in ways that are silent when guessed at:
66
+ # struct { float a, b; } is one SSE eightbyte in xmm0 under System V and
67
+ # two single-precision registers, s0 and s1, under AAPCS64.
68
+ #
69
+ # * how the register files are consumed as the argument list is walked
70
+ # (#placer). Both conventions place an argument as a unit, but they
71
+ # differ on what happens when one does not fit: System V leaves the
72
+ # registers it did not use available to a later argument, while AAPCS64
73
+ # declares the file exhausted (6.4.2 stage C sets NGRN or NSRN to eight),
74
+ # so a trailing int after a spilled two-register struct is in a register
75
+ # on x86-64 and on the stack on aarch64.
76
+ #
77
+ # `hidden_result_kind` is the kind of the implicit pointer to a
78
+ # caller-provided result buffer: an ordinary leading integer argument under
79
+ # System V, and its own mechanism (:indirect_result, the x8 register) under
80
+ # AAPCS64.
81
+ #
82
+ # The `va_list_*` trio is the fourth target-specific thing the convention
83
+ # owns, and it belongs here for the same reason the others do: a `va_list`
84
+ # walks exactly the register/stack layout the convention laid a call's
85
+ # arguments out in, so the shape of the tag, the type the front end declares
86
+ # `__builtin_va_list` to name, and the flavour of the va_arg walk are all
87
+ # facets of the one ABI. `va_list_abi` is a plain tag (:system_v / :aapcs64)
88
+ # the generator switches its va_arg lowering on: the two walks read a
89
+ # different structure with the offsets running opposite ways, so they are
90
+ # kept as separate lowerings rather than one merged over a descriptor — and
91
+ # keeping the System V path untouched is what guarantees its emitted code
92
+ # does not shift by a byte.
93
+ class CallConvention
94
+ attr_reader :gp_registers, :fp_registers, :hidden_result_kind,
95
+ :va_list_tag, :va_list_type, :va_list_abi
96
+
97
+ def initialize(gp_registers:, fp_registers:, va_list_tag:, va_list_type:, va_list_abi:,
98
+ hidden_result_kind: :gp)
99
+ @gp_registers = gp_registers
100
+ @fp_registers = fp_registers
101
+ @hidden_result_kind = hidden_result_kind
102
+ @va_list_tag = va_list_tag
103
+ @va_list_type = va_list_type
104
+ @va_list_abi = va_list_abi
105
+ freeze
106
+ end
107
+
108
+ # The pieces a value of `size` bytes is cut into when it travels in the
109
+ # stack argument area. Both conventions round a stack argument up to a
110
+ # multiple of eight and align it to at least eight, so a spilled value is
111
+ # ceil(size/8) whole eightbytes whatever shape it would have taken in
112
+ # registers — an aarch64 HFA that runs out of vector registers is passed
113
+ # as packed eightbytes, not as one stack slot per member.
114
+ def self.memory_pieces(size)
115
+ Array.new((size + 7) / 8) { |i| AbiPiece.new(offset: 8 * i, size: 8, kind: :mem) }
116
+ end
117
+
118
+ # The convention's plan for passing an aggregate of `type` by value.
119
+ def aggregate_plan(_type)
120
+ raise NotImplementedError
121
+ end
122
+
123
+ # A fresh running placement of one argument list (see the Placer classes).
124
+ def placer
125
+ raise NotImplementedError
126
+ end
127
+ end
128
+
129
+ # System V AMD64 (psABI 3.2.3).
130
+ class SystemVAMD64Convention < CallConvention
131
+ def initialize
132
+ super(gp_registers: 6, fp_registers: 8,
133
+ va_list_tag: Type::VaListTag, va_list_type: Type::BuiltinVaList,
134
+ va_list_abi: :system_v)
135
+ end
136
+
137
+ # Classifies an aggregate by the psABI's eightbyte rules. A struct or
138
+ # union larger than two eightbytes — or one with any unaligned field — is
139
+ # passed in memory; otherwise each of its one or two eightbytes gets a
140
+ # class from the scalar fields that fall in it, :gp for an INTEGER
141
+ # eightbyte and :sse8 for an SSE one (moved as a full 8-byte double even
142
+ # when it holds two packed floats, since a single movsd carries the whole
143
+ # eightbyte).
144
+ #
145
+ # The unaligned-field test is what a GNU __attribute__((packed)) demands
146
+ # (Step 28): the psABI gives an aggregate "containing unaligned fields"
147
+ # class MEMORY, and gcc follows it — a packed struct whose field would
148
+ # straddle an eightbyte boundary is passed on the stack, not in registers.
149
+ # Every non-packed layout here is naturally aligned, so this only ever
150
+ # fires for a packed struct. (AAPCS64 has no such rule, which is one more
151
+ # reason the classification cannot be shared.)
152
+ def aggregate_plan(type)
153
+ size = type.size
154
+ # A 16-byte-aligned aggregate (one holding an __int128, or _Alignas(16))
155
+ # is placed on a 16-byte-aligned stack slot when it spills, so its plan
156
+ # carries the alignment for the placer to pad NSAA up to — even for a
157
+ # MEMORY-class aggregate, whose stack slot the psABI aligns the same way.
158
+ align16 = type.alignment >= 16
159
+ if size > 16 || unaligned_field?(type, 0)
160
+ return AggregatePlan.new(mode: :memory, pieces: CallConvention.memory_pieces(size), align16: align16)
161
+ end
162
+
163
+ eightbytes = Array.new((size + 7) / 8, nil)
164
+ classify_eightbytes(eightbytes, type, 0)
165
+ # A NO_CLASS eightbyte (only padding fell in it) defaults to SSE, the
166
+ # psABI's benign choice; INTEGER otherwise wins over SSE per #merge_class.
167
+ pieces = eightbytes.each_with_index.map do |cls, i|
168
+ AbiPiece.new(offset: 8 * i, size: 8, kind: cls == :integer ? :gp : :sse8)
169
+ end
170
+ AggregatePlan.new(mode: :registers, pieces: pieces, align16: align16)
171
+ end
172
+
173
+ def placer
174
+ Placer.new(self)
175
+ end
176
+
177
+ private
178
+
179
+ # Whether any scalar field of `type`, placed at absolute byte offset
180
+ # `base`, sits on an offset that does not satisfy its own alignment — the
181
+ # mark of a packed layout. A nested aggregate recurses at its members'
182
+ # offsets (a union's members all at 0) and an array at its element's; a
183
+ # scalar checks base against its alignment directly.
184
+ def unaligned_field?(type, base)
185
+ if type.struct?
186
+ # A bit-field is packed into a storage unit by design, so it is never an
187
+ # "unaligned field" in the psABI sense; only its plain neighbours are
188
+ # tested. gcc likewise passes a small bit-field struct in registers.
189
+ type.members.reject(&:bitfield?).any? { |m| unaligned_field?(m.type, base + m.offset) }
190
+ elsif type.array?
191
+ unaligned_field?(type.element, base)
192
+ else
193
+ (base % type.alignment) != 0
194
+ end
195
+ end
196
+
197
+ # Walks `type` at byte offset `base` and folds each scalar field's class
198
+ # into the eightbyte (offset / 8) it lands in. A nested struct or union
199
+ # recurses at its member offsets (a union overlays every member at the same
200
+ # offset, which the members' zero offsets already encode), and an array
201
+ # recurses element by element. A struct reaching here has already passed the
202
+ # unaligned-field test in #aggregate_plan, so no scalar straddles an
203
+ # eightbyte boundary and each falls wholly in the eightbyte at offset / 8.
204
+ def classify_eightbytes(eightbytes, type, base)
205
+ if type.struct?
206
+ type.members.each do |m|
207
+ if m.bitfield?
208
+ classify_bitfield(eightbytes, base, m)
209
+ else
210
+ classify_eightbytes(eightbytes, m.type, base + m.offset)
211
+ end
212
+ end
213
+ elsif type.array?
214
+ type.length.times { |i| classify_eightbytes(eightbytes, type.element, base + i * type.element.size) }
215
+ else
216
+ # A scalar folds its class into every eightbyte it spans. All scalars but
217
+ # a 128-bit integer fit in one (they are naturally aligned); a 16-byte
218
+ # __int128 spans two, both INTEGER, so a struct wrapping one passes by
219
+ # value in two integer registers, as gcc does.
220
+ cls = type.float? ? :sse : :integer
221
+ (base / 8..(base + type.size - 1) / 8).each do |index|
222
+ eightbytes[index] = merge_class(eightbytes[index], cls)
223
+ end
224
+ end
225
+ end
226
+
227
+ # Folds a bit-field member into the eightbytes its bits span. Every
228
+ # bit-field type in this subset is an integer type, so the field
229
+ # contributes INTEGER to each eightbyte it touches (a field wide enough, or
230
+ # placed so, that it straddles an eightbyte boundary marks both). `base` is
231
+ # the enclosing aggregate's byte offset and the member's `bit_offset` its
232
+ # bit position within that aggregate.
233
+ def classify_bitfield(eightbytes, base, member)
234
+ first_bit = base * 8 + member.bit_offset
235
+ last_bit = first_bit + member.bit_width - 1
236
+ (first_bit / 64..last_bit / 64).each do |index|
237
+ eightbytes[index] = merge_class(eightbytes[index], :integer)
238
+ end
239
+ end
240
+
241
+ # Combines two field classes sharing an eightbyte: NO_CLASS (nil) yields to
242
+ # the other, and INTEGER dominates SSE (a mixed integer/float eightbyte is
243
+ # passed in an integer register), matching the psABI merge rule this subset
244
+ # needs.
245
+ def merge_class(current, incoming)
246
+ return incoming if current.nil?
247
+ return current if incoming.nil?
248
+ return :integer if current == :integer || incoming == :integer
249
+
250
+ :sse
251
+ end
252
+
253
+ # Hands out the integer and SSE registers over one argument list. Each
254
+ # argument is placed as a unit: its required registers of both files are
255
+ # counted first, and only if *both* fit in what remains does it take them;
256
+ # otherwise the whole argument spills and no register is consumed — the
257
+ # psABI rule that an argument whose parts do not all fit in registers
258
+ # passes wholly in memory, and the reason a later, smaller argument can
259
+ # still be handed a register the spilled one could not use.
260
+ class Placer
261
+ def initialize(convention)
262
+ @convention = convention
263
+ @next_gp = 0
264
+ @next_sse = 0
265
+ @nsaa = 0 # next stacked argument, counted in eightbytes
266
+ @pad_stack = 0
267
+ end
268
+
269
+ # System V has no even-register-pair rule — a 16-byte argument takes two
270
+ # *consecutive* integer registers of either parity — so #place never
271
+ # reserves a padding register.
272
+ def pad_gp
273
+ 0
274
+ end
275
+
276
+ # One stack eightbyte the most recent #place reserved to 16-align a
277
+ # spilled 16-byte-aligned argument (the psABI aligns a stacked argument to
278
+ # its natural alignment, so an __int128 that overflowed the registers onto
279
+ # an odd stack offset starts a slot later). Zero otherwise.
280
+ attr_reader :pad_stack
281
+
282
+ # :registers when the argument takes the registers its request asks for,
283
+ # :stack when it passes in the overflow area. A request that is already
284
+ # all-:mem (a MEMORY-classified aggregate) never wanted a register.
285
+ def place(request)
286
+ @pad_stack = 0
287
+ need_gp = request.kinds.count(:gp)
288
+ need_sse = request.kinds.count { |kind| kind == :sse4 || kind == :sse8 }
289
+ spills = request.kinds.all?(:mem) ||
290
+ !(@next_gp + need_gp <= @convention.gp_registers &&
291
+ @next_sse + need_sse <= @convention.fp_registers)
292
+ if spills
293
+ @pad_stack = 1 if request.align16 && @nsaa.odd?
294
+ @nsaa += @pad_stack + request.mem_eightbytes
295
+ return :stack
296
+ end
297
+
298
+ @next_gp += need_gp
299
+ @next_sse += need_sse
300
+ :registers
301
+ end
302
+ end
303
+ end
304
+
305
+ # AAPCS64 (Procedure Call Standard for the Arm 64-bit Architecture, 6.4.2).
306
+ class AAPCS64Convention < CallConvention
307
+ # The most members a homogeneous floating aggregate may have and still
308
+ # travel in vector registers (6.4.2 stage B: "at most four uniquely
309
+ # addressable members"). A fifth member sends the whole aggregate by
310
+ # reference, however small each member is.
311
+ MAX_HFA_MEMBERS = 4
312
+
313
+ # The largest aggregate that is passed in integer registers rather than by
314
+ # reference. An HFA is exempt: four doubles are 32 bytes and still ride
315
+ # d0..d3.
316
+ MAX_REGISTER_AGGREGATE = 16
317
+
318
+ def initialize
319
+ super(gp_registers: 8, fp_registers: 8, hidden_result_kind: :indirect_result,
320
+ va_list_tag: Type::AArch64VaListTag, va_list_type: Type::AArch64BuiltinVaList,
321
+ va_list_abi: :aapcs64)
322
+ end
323
+
324
+ # Classifies an aggregate by AAPCS64 6.4.2, in the order the standard
325
+ # tests it:
326
+ #
327
+ # * a Homogeneous Floating-point Aggregate — every scalar in it, however
328
+ # deeply nested, is the same floating type, and there are at most four
329
+ # of them — puts each member in a vector register of its own. This is
330
+ # the rule with no System V counterpart at all: struct { float a, b; }
331
+ # is s0 and s1 here where System V packs both into one xmm0.
332
+ # * any other aggregate of 16 bytes or less takes one or two consecutive
333
+ # integer registers, whatever its members are (a packed struct
334
+ # included: AAPCS64 has no unaligned-field escape to memory). One whose
335
+ # alignment is 16 must start at an even-numbered register, which is
336
+ # what `align16` asks the placer for.
337
+ # * anything larger travels by reference: the caller copies it and passes
338
+ # the copy's address.
339
+ def aggregate_plan(type)
340
+ base, count = homogeneous_float(type)
341
+ if base && count <= MAX_HFA_MEMBERS && type.size == base * count
342
+ kind = base == 8 ? :sse8 : :sse4
343
+ pieces = Array.new(count) { |i| AbiPiece.new(offset: base * i, size: base, kind: kind) }
344
+ return AggregatePlan.new(mode: :registers, pieces: pieces, align16: false)
345
+ end
346
+
347
+ if type.size <= MAX_REGISTER_AGGREGATE
348
+ pieces = Array.new((type.size + 7) / 8) { |i| AbiPiece.new(offset: 8 * i, size: 8, kind: :gp) }
349
+ return AggregatePlan.new(mode: :registers, pieces: pieces, align16: type.alignment >= 16)
350
+ end
351
+
352
+ AggregatePlan.new(mode: :by_reference, pieces: [], align16: false)
353
+ end
354
+
355
+ def placer
356
+ Placer.new(self)
357
+ end
358
+
359
+ private
360
+
361
+ # Whether `type` is built entirely out of one floating type, and of how
362
+ # many of them: [element_size, count], or nil for anything else. A struct
363
+ # sums its members' counts, an array multiplies its element's by its
364
+ # length, a union takes the widest member's (all of them overlay the same
365
+ # storage), and a scalar float or double is one member of itself. A
366
+ # bit-field, an integer or a pointer anywhere inside disqualifies the
367
+ # whole aggregate at once.
368
+ #
369
+ # The count alone does not settle it: the caller also checks that the
370
+ # aggregate's size is exactly count * element_size, which is what rejects
371
+ # a struct that has been padded out of shape — struct { float a, b; }
372
+ # __attribute__((aligned(16))) has two float members but occupies 16
373
+ # bytes, and gcc passes it in x0/x1 rather than as an HFA.
374
+ def homogeneous_float(type)
375
+ if type.struct?
376
+ homogeneous_members(type)
377
+ elsif type.array?
378
+ return nil if type.length.nil? || type.length.zero?
379
+
380
+ element = homogeneous_float(type.element)
381
+ element && [element[0], element[1] * type.length]
382
+ elsif type.float?
383
+ [type.size, 1]
384
+ end
385
+ end
386
+
387
+ # The [element_size, count] of an aggregate's members, or nil when they
388
+ # disagree (or when there are none, an aggregate C cannot form anyway).
389
+ def homogeneous_members(type)
390
+ return nil if type.members.nil? || type.members.empty?
391
+
392
+ size = nil
393
+ count = 0
394
+ type.members.each do |member|
395
+ return nil if member.bitfield?
396
+
397
+ element = homogeneous_float(member.type)
398
+ return nil if element.nil? || (size && size != element[0])
399
+
400
+ size = element[0]
401
+ count = type.union? ? [count, element[1]].max : count + element[1]
402
+ end
403
+ [size, count]
404
+ end
405
+
406
+ # Hands out x0..x7 and v0..v7 over one argument list, by 6.4.2 stage C.
407
+ # An argument never draws on both files here (a scalar is one or the
408
+ # other, an HFA is all vector, every other aggregate all integer, and an
409
+ # aggregate passed by reference is just a pointer), so the two counters
410
+ # advance independently.
411
+ #
412
+ # The difference from System V that matters is what an argument that does
413
+ # not fit leaves behind: the standard sets NGRN (or NSRN) to eight, so the
414
+ # file it overflowed is *exhausted* and every later argument of that class
415
+ # goes to the stack as well. The other file is untouched — a spilled HFA
416
+ # does not stop a following int from reaching x0.
417
+ class Placer
418
+ def initialize(convention)
419
+ @convention = convention
420
+ @ngrn = 0
421
+ @nsrn = 0
422
+ @nsaa = 0 # next stacked argument, counted in eightbytes (NSAA/8)
423
+ @pad_gp = 0
424
+ @pad_stack = 0
425
+ end
426
+
427
+ # The alignment padding the most recent #place inserted *before* the
428
+ # argument it placed: one integer register (`pad_gp`) when a 16-byte
429
+ # aligned aggregate rounded NGRN up over an odd register so it lands in
430
+ # an aligned x-register pair (6.4.2 stage C.4), or one stack eightbyte
431
+ # (`pad_stack`) when such an aggregate spilled onto an odd stack offset
432
+ # and had to start at a 16-byte boundary (stage C.13's NSAA alignment).
433
+ # At most one is ever nonzero. The generator reads them to emit a matching
434
+ # pad slot, so the backend's sequential register/stack handout skips the
435
+ # same place the standard reserves.
436
+ attr_reader :pad_gp, :pad_stack
437
+
438
+ def place(request)
439
+ @pad_gp = 0
440
+ @pad_stack = 0
441
+ need_fp = request.kinds.count { |kind| kind == :sse4 || kind == :sse8 }
442
+ return place_fp(need_fp, request.mem_eightbytes) if need_fp.positive?
443
+
444
+ need_gp = request.kinds.count(:gp)
445
+ return place_gp(need_gp, request.align16, request.mem_eightbytes) if need_gp.positive?
446
+
447
+ # An :indirect_result pointer rides a register of its own (x8), which
448
+ # is not part of either file's budget.
449
+ :registers
450
+ end
451
+
452
+ private
453
+
454
+ def place_fp(count, mem_eightbytes)
455
+ if @nsrn + count <= @convention.fp_registers
456
+ @nsrn += count
457
+ :registers
458
+ else
459
+ @nsrn = @convention.fp_registers
460
+ @nsaa += mem_eightbytes
461
+ :stack
462
+ end
463
+ end
464
+
465
+ def place_gp(count, align16, mem_eightbytes)
466
+ first = align16 && @ngrn.odd? ? @ngrn + 1 : @ngrn
467
+ if first + count <= @convention.gp_registers
468
+ @pad_gp = first - @ngrn
469
+ @ngrn = first + count
470
+ :registers
471
+ else
472
+ @ngrn = @convention.gp_registers
473
+ @pad_stack = 1 if align16 && @nsaa.odd?
474
+ @nsaa += @pad_stack + mem_eightbytes
475
+ :stack
476
+ end
477
+ end
478
+ end
479
+ end
480
+
481
+ class CallConvention
482
+ SYSTEM_V_AMD64 = SystemVAMD64Convention.new
483
+ AAPCS64 = AAPCS64Convention.new
484
+ end
485
+ end
486
+ end