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,417 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubycc
4
+ module IR
5
+ # A single three-address instruction over virtual registers.
6
+ #
7
+ # :const dst <- a (a is an immediate Integer; size == 8 loads a
8
+ # full 64-bit immediate for a long/pointer
9
+ # constant, otherwise a 32-bit one)
10
+ # :copy dst <- a
11
+ # :add/:sub/:mul dst <- a op b
12
+ # :div/:mod dst <- a op b (signed division/remainder)
13
+ # :udiv/:umod dst <- a op b (unsigned division/remainder;
14
+ # the backend zeroes edx and uses `div`)
15
+ # :mulhi dst <- hi64(a * b) the unsigned high 64 bits of the 128-bit
16
+ # product of two 64-bit values (x86 `mul r64`,
17
+ # REX.W F7 /4: rax*b -> rdx:rax, result taken
18
+ # from rdx). Used only to synthesize a 128-bit
19
+ # (`__int128`) multiply from 64-bit halves; the
20
+ # generator pairs it with ordinary :mul (the low
21
+ # 64) and :add. size is always 8
22
+ # :and/:or/:xor dst <- a op b (bitwise)
23
+ # :shl dst <- a << b (logical left shift; b's low byte is the
24
+ # shift count, taken from cl by the backend)
25
+ # :sar dst <- a >> b (arithmetic right shift; b's low byte is the
26
+ # count. A signed left operand's ">>" lowers to
27
+ # :sar so the sign bit is replicated)
28
+ # :shr dst <- a >> b (logical right shift; the unsigned counterpart
29
+ # of :sar, chosen when the left operand is an
30
+ # unsigned type — the split mirrors :div/:udiv,
31
+ # since the machine opcodes differ by sign)
32
+ # :eq/:ne dst <- (a op b) ? 1 : 0 (sign-independent)
33
+ # :lt/:le/:gt/:ge dst <- (a op b) ? 1 : 0 (signed compare)
34
+ # :ult/:ule/:ugt/:uge dst <- (a op b) ? 1 : 0 (unsigned compare,
35
+ # setb/setbe/seta/setae; also used for pointer
36
+ # ordering, addresses being unsigned)
37
+ # :neg dst <- -a
38
+ # :fadd/:fsub/:fmul/:fdiv dst <- a op b (floating arithmetic; size is
39
+ # the operand width, 4 for float / 8 for
40
+ # double, selecting the ss/sd form). A floating
41
+ # "-a" has no op of its own: the generator
42
+ # flips the sign bit with an integer :xor
43
+ # (0x80000000 / 0x8000000000000000, size 8)
44
+ # :feq/:fne dst <- (a op b) ? 1 : 0 (floating equality;
45
+ # size 4/8. NaN-aware: :feq is false and :fne
46
+ # true when either operand is NaN)
47
+ # :flt/:fle/:fgt/:fge dst <- (a op b) ? 1 : 0 (floating ordering;
48
+ # size 4/8. Every one is false when either
49
+ # operand is NaN, the backend reversing the
50
+ # ucomis operands for :flt/:fle so an unordered
51
+ # compare clears the flag)
52
+ # :itof dst <- (float)a integer a converted to a floating value.
53
+ # size is the destination float width (4/8);
54
+ # b is the [width, signed?] descriptor of the
55
+ # integer *source* (an unsigned long source is
56
+ # rejected by the generator, so never reaches
57
+ # here)
58
+ # :ftoi dst <- (int)a floating a truncated toward zero to an
59
+ # integer. size is the float *source* width
60
+ # (4/8); b is the [width, signed?] descriptor
61
+ # of the integer destination (an unsigned long
62
+ # destination is likewise rejected upstream)
63
+ # :ftof dst <- a float<->double width change. size is the
64
+ # *source* float width (4 widening to double,
65
+ # 8 narrowing to float)
66
+ # :sext dst <- a (size: 1/2/4) a's low `size` bytes sign-extended to
67
+ # the register's full width. size 4 is a
68
+ # movsxd; size 1/2 a movsx of the low byte/word
69
+ # :zext dst <- a (size: 1/2/4) a's low `size` bytes zero-extended to
70
+ # the register's full width. size 4 is a plain
71
+ # 32-bit mov (which zeroes the upper half);
72
+ # size 1/2 a movzx
73
+ # :ret return a (a is nil for a void function's "return;"
74
+ # or its implicit fall-off-the-end return,
75
+ # which emits no value-loading code at all).
76
+ # `size` is nil for an integer/pointer return
77
+ # (the value goes in rax/eax) or 4/8 for a
78
+ # floating one, which the backend loads from a's
79
+ # slot into xmm0 with movss/movsd, the System V
80
+ # register a float/double result is returned in.
81
+ # For an in-register struct return `size` is
82
+ # instead an IR::AbiPiece array, one per piece
83
+ # the target's convention cuts the aggregate
84
+ # into (offset, width and kind): a is the address
85
+ # of the struct's buffer, and the backend gathers
86
+ # each piece into its return register — under
87
+ # System V an eightbyte at a time into rax/rdx
88
+ # (:gp) or xmm0/xmm1 (:sse8), under AAPCS64 an
89
+ # HFA member at a time into v0..v3. A struct the
90
+ # convention does not return in registers is not
91
+ # returned this way — its callee copies the
92
+ # result through the hidden pointer parameter and
93
+ # returns that pointer too (a plain size-nil :ret)
94
+ # :label a = label id (a jump target; emits no code itself)
95
+ # :jump a = label id (unconditional branch)
96
+ # :jump_if_zero a = condition vreg, b = label id (branch when a == 0)
97
+ # :call dst <- f(args) a = callee name (String),
98
+ # b = array of [arg_vreg, kind] pairs (left to
99
+ # right). `kind` gives each argument's System V
100
+ # AMD64 placement, which the generator has already
101
+ # fixed over the whole argument list (so a Phase B
102
+ # struct can apply the all-or-nothing overflow rule
103
+ # where every argument's type is known): :gp takes
104
+ # the next integer register (edi,esi,edx,ecx,r8d,r9d),
105
+ # :sse4 (float) / :sse8 (double) the next xmm
106
+ # (xmm0..7, loaded from the slot with movss/movsd),
107
+ # and :mem a stack eightbyte. The backend follows the
108
+ # kind verbatim — it assigns registers in order and
109
+ # pushes the :mem arguments in reverse so the first
110
+ # lands at the lowest address (an eightbyte each, the
111
+ # slot's low bits carrying its value). A by-value
112
+ # struct argument fans out into one [vreg, kind] pair
113
+ # per piece its convention cuts it into (a System V
114
+ # eightbyte's class :gp/:sse8, an AAPCS64 HFA
115
+ # member's :sse4/:sse8, or :mem per eightbyte when it
116
+ # spills whole), all placed together so the argument
117
+ # stays in registers or spills as a unit; an
118
+ # aggregate AAPCS64 passes by reference is reduced by
119
+ # the generator to a single :gp pointer to a
120
+ # caller-made copy, so no backend needs a rule of its
121
+ # own for it. A call whose struct result comes back
122
+ # through a hidden pointer also prepends that
123
+ # [vreg, kind] pointer as the first argument. `size` is nil, or a
124
+ # [fixed, ret] pair when either half is non-nil:
125
+ # `fixed` is the callee's fixed parameter count for a
126
+ # variadic call (else nil), which makes the backend
127
+ # set al to the count of xmm registers it used before
128
+ # the call, as the ABI requires; `ret` is :sse4/:sse8
129
+ # when the result is a float/double (loaded from xmm0
130
+ # with movss/movsd into dst's slot), a
131
+ # [buffer_vreg, pieces] pair when the result is a
132
+ # struct returned in registers (dst is nil and the
133
+ # backend scatters each IR::AbiPiece from its return
134
+ # register — rax/rdx or xmm0/xmm1 under System V,
135
+ # x0/x1 or v0..v3 under AAPCS64 — into the buffer
136
+ # buffer_vreg points at), else nil (the result comes
137
+ # back in the integer result register as usual)
138
+ # :call_indirect dst <- (*a)(args) a = a vreg holding the function's
139
+ # address (a function pointer value), b = the
140
+ # [arg_vreg, kind] pairs (the same generator-fixed
141
+ # :gp/:sse4/:sse8/:mem placement as :call); `size`
142
+ # carries the same [fixed, ret] pair.
143
+ # The backend calls through a scratch register
144
+ # :func_addr dst <- &func(a) dst gets the address of the function named a
145
+ # (a String symbol), the value a function
146
+ # designator decays to (and "&f" yields); resolved
147
+ # by a PC-relative relocation like :global_addr
148
+ # :addr_of dst <- &slot(a) dst gets the address of a's stack slot
149
+ # :object_addr dst <- &object(a) dst gets the base address of stack
150
+ # object a (an array's first element)
151
+ # :load dst <- *a dst gets `size` bytes read through pointer a,
152
+ # sign-extended (a signed char/short read is a
153
+ # movsx; size 4/8 a plain mov)
154
+ # :uload dst <- *a like :load but zero-extended (an unsigned
155
+ # char/short read is a movzx), for unsigned
156
+ # narrow types and _Bool
157
+ # :store *a <- b `size` bytes of b are written through ptr a
158
+ # :memcpy *a <- *b (size) `size` bytes are copied from the address in
159
+ # b to the address in a (a whole-struct
160
+ # assignment "s = t"); both are pointer vregs,
161
+ # `size` the struct's byte width
162
+ # :string_addr dst <- &string(a) dst gets the address of read-only string
163
+ # a (an id into the translation unit's
164
+ # string pool), i.e. a decayed char *
165
+ # :global_addr dst <- &global(a) dst gets the address of the file-scope
166
+ # variable named a (a String symbol name),
167
+ # the lvalue every global read/write and
168
+ # "&g"/array decay is lowered through
169
+ # :got_addr dst <- &symbol(a) via GOT dst gets the address of the
170
+ # file-scope object or function named a (a
171
+ # String symbol), loaded from its Global Offset
172
+ # Table slot rather than formed PC-relatively.
173
+ # The generator emits it in place of
174
+ # :global_addr / :func_addr only under -fPIC,
175
+ # and only for a symbol this translation unit
176
+ # does not define, so a definition in another
177
+ # shared object may interpose; the backend reads
178
+ # the slot with "mov rax, [rip+disp32]" and the
179
+ # linker fills the disp with an
180
+ # R_X86_64_REX_GOTPCRELX relocation against the
181
+ # symbol. A symbol defined here keeps the
182
+ # PC-relative :global_addr / :func_addr form,
183
+ # being always resolved within this DSO
184
+ # :va_start a = a vreg holding the address of a
185
+ # __va_list_tag, b = the enclosing function's
186
+ # fixed (named) parameter count. Initializes the
187
+ # target's va_list fields (the four System V ones
188
+ # gp_offset/fp_offset/overflow_arg_area/reg_save_area,
189
+ # or the five AAPCS64 ones __stack/__gr_top/
190
+ # __vr_top/__gr_offs/__vr_offs) so a later
191
+ # __builtin_va_arg reads the variable arguments;
192
+ # the backend fills them from the register-save
193
+ # area its variadic prologue set up, deriving the
194
+ # named GP and SSE counts (which seed the offsets
195
+ # and the overflow/stack start) from
196
+ # Function.param_kinds rather than from b.
197
+ # va_arg/va_end/va_copy need no IR op of their
198
+ # own — the generator lowers them to ordinary
199
+ # load/store/branch (and, for va_copy, :memcpy)
200
+ # instructions
201
+ # :alloca dst <- alloca(a) a = a vreg holding a byte count; dst gets the
202
+ # base address of that many bytes of automatic
203
+ # storage carved from the stack (__builtin_alloca).
204
+ # The backend rounds the count up to a 16-byte
205
+ # multiple and subtracts it from the stack pointer,
206
+ # so that stays 16-aligned and the block is 16-byte
207
+ # aligned; the storage is reclaimed wholesale when
208
+ # the function returns, not at end of scope. What
209
+ # makes the moving stack pointer safe differs by
210
+ # target: x86-64 addresses every other value from
211
+ # rbp already, while aarch64 is sp-relative and so
212
+ # anchors the frame of an alloca-using function in
213
+ # x29 for the duration (see backend/aarch64.rb)
214
+ # :bit_scan dst <- scan(a) counts the zero bits of the integer in vreg a,
215
+ # for __builtin_ctz/clz (and their "ll" forms).
216
+ # b is the direction — :forward for a trailing
217
+ # count (ctz), :reverse for a leading count
218
+ # (clz) — and `size` the operand width (4 or 8).
219
+ # x86-64 lowers :forward to `bsf` and :reverse
220
+ # to `bsr` followed by `xor` with (size*8 - 1),
221
+ # so clz = (width-1) - bsr; a size-8 scan takes
222
+ # a REX.W prefix. AArch64 lowers :reverse to a
223
+ # bare `clz` and :forward to `rbit` ahead of
224
+ # one, at the W or X width `size` names. A zero
225
+ # operand is undefined (as in gcc), so no zero
226
+ # case is emitted. The result is an int
227
+ #
228
+ # The five atomic ops below lower gcc's __atomic_* builtins. Every one is
229
+ # sequentially consistent — the IR carries no memory order at all, because
230
+ # the generator lowers every order the source asked for at the strongest one
231
+ # (strengthening an order is always sound; see #gen_builtin_atomic). `size`
232
+ # is the access width and is only ever 4 or 8: the generator diagnoses every
233
+ # other width, so no backend needs a narrower or wider case.
234
+ #
235
+ # :atomic_fence a sequentially-consistent memory fence
236
+ # :atomic_load dst <- atomic *a dst gets `size` bytes read atomically
237
+ # through pointer a, with sequentially
238
+ # consistent ordering. Distinct from :load
239
+ # because the two targets differ: on x86-64 an
240
+ # aligned mov already is a seq_cst load, while
241
+ # aarch64 needs the acquire form (ldar)
242
+ # :atomic_store *a <- b `size` bytes of b are written atomically
243
+ # through pointer a, sequentially consistently.
244
+ # x86-64 uses `xchg` (whose implicit lock
245
+ # supplies the trailing fence a seq_cst store
246
+ # needs), aarch64 `stlr`
247
+ # :atomic_rmw dst <- rmw(a, b) an atomic read-modify-write through pointer
248
+ # a. b is a [value_vreg, kind] pair; `kind` is
249
+ # :exchange, :fetch_add, :fetch_sub,
250
+ # :add_fetch, :sub_fetch or :or_fetch. dst gets
251
+ # the value the corresponding builtin returns —
252
+ # the value read for :exchange and the
253
+ # :fetch_* forms, the value stored for the
254
+ # :*_fetch ones — and may be nil when the
255
+ # result is discarded
256
+ # :atomic_cas dst <- cas(a, b) an atomic compare-and-exchange through
257
+ # pointer a, for __atomic_compare_exchange_n.
258
+ # b is an [expected_ptr_vreg, desired_vreg]
259
+ # pair. If *a equals *expected_ptr, *a becomes
260
+ # desired and dst gets 1; otherwise *a is
261
+ # untouched, the value actually read is stored
262
+ # back through expected_ptr — a side effect
263
+ # callers depend on — and dst gets 0. The
264
+ # write-back happens only on the failing path,
265
+ # so a caller whose expected_ptr aliases a sees
266
+ # the exchanged value rather than a stale one.
267
+ # dst is a _Bool (0/1) and is never nil
268
+ #
269
+ # `dst`, `a`, `b` are virtual register numbers (Integers) unless noted;
270
+ # unused fields are nil. `size` is an operand width in bytes. On :load /
271
+ # :uload / :store it is the memory access width (1 char, 2 short, 4 int,
272
+ # 8 pointer/long). On a binary op (the arithmetic, shift and comparison ops)
273
+ # size == 8 selects 64-bit arithmetic for `long`/`unsigned long`/pointer
274
+ # values and pointer-offset scaling; a nil (or 4) size means the default
275
+ # 32-bit arithmetic, whose natural wrap-around matches a 4-byte C type. On
276
+ # :sext / :zext, `size` is instead the *source* width being extended from.
277
+ # On the floating ops (:fadd..:fge) `size` is the floating operand width
278
+ # (4 float / 8 double); on :itof it is the destination float width, on :ftoi
279
+ # and :ftof the *source* float width, with the paired integer width carried
280
+ # in `b` as a [width, signed?] descriptor for :itof / :ftoi.
281
+ class Instruction
282
+ attr_reader :op, :dst, :a, :b, :size
283
+
284
+ def initialize(op, dst: nil, a: nil, b: nil, size: nil)
285
+ @op = op
286
+ @dst = dst
287
+ @a = a
288
+ @b = b
289
+ @size = size
290
+ end
291
+
292
+ def inspect
293
+ "#<IR #{op} dst=#{dst.inspect} a=#{a.inspect} b=#{b.inspect} size=#{size.inspect}>"
294
+ end
295
+ end
296
+
297
+ # A function in IR form: a name, a flat list of instructions and the number
298
+ # of virtual registers used (so the backend can size its stack frame).
299
+ # `param_count` is the number of ABI argument slots, not the number of C
300
+ # parameters: a scalar parameter is one slot, a by-value struct parameter one
301
+ # per piece its convention cuts it into, and the hidden result pointer of a
302
+ # function whose result does not come back in registers an extra leading
303
+ # slot. Those slots occupy
304
+ # the first `param_count` virtual registers (0..param_count-1) in that order,
305
+ # so the backend can spill the incoming argument registers into them; the
306
+ # generator then reassembles a struct parameter's slots into a stack object.
307
+ # `param_kinds` is an array of length `param_count` giving each slot's arrival
308
+ # under the *target's* calling convention (IR::CallConvention), in that
309
+ # flattened order, which the generator has fixed by the same placement
310
+ # simulation a call's arguments use: :gp (integer register), :sse4 (float
311
+ # vector register), :sse8 (double vector register — also each eightbyte of a
312
+ # struct arriving in one, and each member of an AAPCS64 HFA of doubles) or
313
+ # :mem (the stack overflow area). How many registers there are to hand out
314
+ # before a scalar spills to :mem, and how an aggregate is cut into slots at
315
+ # all, are the target's business — which is why the tags are fixed here and
316
+ # not by a backend: System V AMD64 offers six integer registers and AAPCS64
317
+ # eight, and struct { float a, b; } is one :sse8 slot on the first and two
318
+ # :sse4 slots on the second, so the same C call classifies differently on the
319
+ # two. One further kind names a mechanism only some conventions have: an
320
+ # :indirect_result slot is the implicit pointer to a caller-provided result
321
+ # buffer when the convention reserves a register of its own for it (AAPCS64's
322
+ # x8). An aggregate passed by reference needs no kind of its own — the
323
+ # generator reduces it to an ordinary :gp pointer to a caller-made copy. The
324
+ # prologue
325
+ # spills each slot straight from its location, and a variadic function derives
326
+ # its gp_offset / fp_offset / overflow start for :va_start from the counts of
327
+ # each kind.
328
+ # `stack_objects` is an array indexed by object id whose entries are the
329
+ # byte sizes of aggregate stack objects (arrays); the backend lays these
330
+ # out below the virtual-register slots and resolves :object_addr against
331
+ # them.
332
+ # `linkage` is :external for an ordinary function (a global symbol the
333
+ # linker can resolve across translation units) or :internal for a `static`
334
+ # one (a file-local symbol, emitted STB_LOCAL so it stays private to this
335
+ # object and never collides with a same-named function elsewhere).
336
+ # `variadic` is true for a "..."-terminated definition, which makes the
337
+ # backend emit a register-save-area prologue so :va_start / __builtin_va_arg
338
+ # can reach the variable arguments; a fixed-arity function leaves it false.
339
+ class Function
340
+ attr_reader :name, :insts, :vreg_count, :param_count, :stack_objects, :linkage, :variadic, :param_kinds
341
+
342
+ def initialize(name, insts, vreg_count, param_count, stack_objects, linkage, variadic, param_kinds)
343
+ @name = name
344
+ @insts = insts
345
+ @vreg_count = vreg_count
346
+ @param_count = param_count
347
+ @stack_objects = stack_objects
348
+ @linkage = linkage
349
+ @variadic = variadic
350
+ @param_kinds = param_kinds
351
+ end
352
+ end
353
+
354
+ # A file-scope variable as laid out by the compiler. `name` is its symbol
355
+ # name, `size` its storage width in bytes and `align` its required
356
+ # alignment (its type size for a scalar/pointer, its element size for an
357
+ # array, its widest member's for a struct). `init` is either nil — a
358
+ # zero-initialized global that lives in .bss — or a GlobalInit: the
359
+ # `size`-byte little-endian image to place in .data together with the
360
+ # relocations that patch pointer slots at link time. `linkage` is :external
361
+ # for an ordinary file-scope variable (a global symbol) or :internal for a
362
+ # `static` one — a file-scope `static`, or a block-scope `static` lowered to
363
+ # a uniquely named file-scope object — emitted STB_LOCAL so it stays private
364
+ # to this object.
365
+ Global = Data.define(:name, :size, :align, :init, :linkage)
366
+
367
+ # The materialized initializer of a .data global: `bytes` is its full
368
+ # `size`-byte image (a scalar packed little-endian, an aggregate laid out
369
+ # member/element by member/element, a pointer slot left as eight zeros for a
370
+ # relocation to fill), and `relocations` is the list of GlobalReloc that
371
+ # patch the pointer slots. An all-zero image with no relocations is still a
372
+ # GlobalInit (an explicitly zero-initialized global), distinct from a nil
373
+ # `init` that reserves .bss space.
374
+ GlobalInit = Data.define(:bytes, :relocations)
375
+
376
+ # One relocation inside a global's .data image. `offset` is the byte offset
377
+ # of the 8-byte pointer slot within that global. `kind` is :symbol for the
378
+ # address of another file-scope object — `symbol` names it, resolved as an
379
+ # absolute 64-bit address (a "&global" or a decayed global array) — or
380
+ # :string for a string literal, where `string_id` indexes the translation
381
+ # unit's string pool and the compiler resolves it to a .rodata offset. The
382
+ # unused field is nil for each kind. `addend` is a constant byte displacement
383
+ # added to the base address (0 for a bare "&global" or string, non-zero for a
384
+ # computed address constant such as "&arr[i]", "arr + n" or "&rec.member");
385
+ # it becomes the R_X86_64_64 relocation's r_addend.
386
+ GlobalReloc = Data.define(:offset, :kind, :symbol, :string_id, :addend) do
387
+ def initialize(offset:, kind:, symbol:, string_id:, addend: 0)
388
+ super
389
+ end
390
+ end
391
+
392
+ # One entry this translation unit contributes to an initializer/finalizer
393
+ # array. `symbol` names a function *defined here* that the runtime is to
394
+ # call: `kind` is :init for a constructor (the loader calls it before main,
395
+ # or at dlopen) or :fini for a destructor (at exit, or at dlclose), and
396
+ # `priority` is its run-order number, the compiler's
397
+ # ELFWriter::DEFAULT_ARRAY_PRIORITY standing for the unnumbered form. The
398
+ # compiler turns each entry into an 8-byte slot in the matching
399
+ # SHT_INIT_ARRAY / SHT_FINI_ARRAY section plus an absolute 64-bit relocation
400
+ # against `symbol`.
401
+ ArrayEntry = Data.define(:kind, :priority, :symbol)
402
+
403
+ # A whole translation unit lowered to IR: its `functions` (an array of
404
+ # Function), the shared read-only string pool `strings` (an array of
405
+ # ASCII-8BIT byte strings, without their NUL terminators, indexed by the id
406
+ # a :string_addr instruction carries), its `globals` (an array of Global
407
+ # in source order) and its `array_entries` (an array of ArrayEntry, empty for
408
+ # a unit with no constructor/destructor). Identical string contents are
409
+ # pooled once, so the compiler can lay them out in .rodata in this order and
410
+ # resolve each :string_addr to an offset.
411
+ Program = Data.define(:functions, :strings, :globals, :array_entries, :visibility) do
412
+ def initialize(functions:, strings:, globals:, array_entries: [], visibility: {})
413
+ super
414
+ end
415
+ end
416
+ end
417
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../compiler"
4
+ require_relative "../objfile/ar_archive"
5
+
6
+ module Rubycc
7
+ module Link
8
+ # rubycc's compiler-support runtime: the small set of symbols a CRuby built
9
+ # by a compiler other than rubycc leaves for the C runtime to supply, and
10
+ # which rubycc must therefore provide itself the way gcc's driver quietly
11
+ # links libgcc. It is delivered as an `ar` archive appended to the tail of
12
+ # every link so the linker's lazy member extraction pulls a definition in
13
+ # *only* when an input actually references it; a link that never mentions one
14
+ # of these symbols gets none of this code, exactly as if the archive were not
15
+ # on the command line.
16
+ #
17
+ # The one member of record is rb_gc_guarded_ptr_val, the target of
18
+ # RB_GC_GUARD's non-`__GNUC__` fallback (ruby/internal/memory.h). Because
19
+ # rubycc deliberately does not define __GNUC__ (DESIGN R7), CRuby's macro
20
+ # expands to a call to this function rather than the GNU inline-asm barrier;
21
+ # yet a gcc-built CRuby, having taken the asm path itself, never exports the
22
+ # function. The whole contract is to hand the pointer back untouched — the
23
+ # call is itself the optimization barrier that keeps the guarded VALUE live,
24
+ # and rubycc performs no optimization that the barrier would need to defeat.
25
+ #
26
+ # The archive is built by compiling the runtime's own C source *with rubycc*
27
+ # (a self-host: rubycc compiles its own support code) into an ET_REL object,
28
+ # then wrapping it in a one-member ranlib-indexed archive. Each entry in the
29
+ # source table is one self-contained function so the set can grow a symbol at
30
+ # a time without any member dragging in an unrelated definition.
31
+ class CompatRuntime
32
+ # The runtime's source fragments, keyed by the member name each is archived
33
+ # under. One function per fragment keeps the lazy pull-in surgical: only the
34
+ # fragment defining a referenced symbol is extracted. Compiled PIC because
35
+ # the definitions land in shared objects.
36
+ #
37
+ # `volatile` is kept on the parameter and return types so the declaration
38
+ # matches CRuby's own prototype spelling; it qualifies the pointee, not the
39
+ # ABI, so it has no effect on the generated code. `val` is unused by design
40
+ # — the argument exists only so the call site materializes the guarded
41
+ # value as a real function argument, which is the entire point of the guard.
42
+ SOURCES = {
43
+ "rb_gc_guarded_ptr_val" => <<~C
44
+ /* rubycc compat runtime: definitions CRuby's non-GNUC fallback paths
45
+ reference. RB_GC_GUARD's fallback calls rb_gc_guarded_ptr_val, which a
46
+ gcc-built CRuby does not export; returning the pointer unchanged is the
47
+ whole contract (the call itself is the optimization barrier, and rubycc
48
+ does not optimize). Pulled in lazily, only when referenced. */
49
+ typedef unsigned long __rubycc_VALUE;
50
+ volatile __rubycc_VALUE *rb_gc_guarded_ptr_val(volatile __rubycc_VALUE *ptr, __rubycc_VALUE val) {
51
+ return ptr;
52
+ }
53
+ C
54
+ }.freeze
55
+
56
+ # A stable synthetic filename for each fragment's diagnostics and file
57
+ # symbol, so a compile error (should the source ever regress) points at the
58
+ # runtime rather than at a user file.
59
+ SOURCE_FILENAME = "rubycc_compat_runtime.c"
60
+
61
+ class << self
62
+ # The compiler-support runtime as an `ar` archive (an ASCII-8BIT String),
63
+ # ready to append to a link's input list. `target` selects the machine the
64
+ # fragments are compiled for, so the runtime members match the objects they
65
+ # are merged with — an aarch64 link must not pull in an x86_64 definition.
66
+ # Memoized per target: the bytes are deterministic (N4) and their
67
+ # construction — compiling every fragment — is done once per target per
68
+ # process.
69
+ def archive_bytes(target: "x86_64")
70
+ (@archive_bytes ||= {})[target] ||= build_archive(target)
71
+ end
72
+
73
+ private
74
+
75
+ # Compiles each source fragment to a PIC ET_REL object for `target` and
76
+ # lays them out as archive members in table order, producing a
77
+ # symbol-indexed archive the linker can extract from lazily.
78
+ def build_archive(target)
79
+ writer = ObjFile::ArWriter.new
80
+ SOURCES.each do |name, source|
81
+ object = Compiler.new.compile(source, filename: SOURCE_FILENAME, pic: true, target: target)
82
+ writer.add_member("#{name}.o", object)
83
+ end
84
+ writer.to_binary
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubycc
4
+ module Link
5
+ # Raised for a link-time defect the user must fix: a symbol defined by two
6
+ # inputs, or an input construct the linker does not yet support (a COMMON
7
+ # symbol). It is deliberately not a CompileError — nothing here parses C; the
8
+ # failure belongs to the link stage and names the objects involved so the
9
+ # user can locate the conflict. A malformed input object/archive surfaces as
10
+ # the reader's ELFFormatError / ArFormatError instead.
11
+ class LinkError < Rubycc::Error; end
12
+ end
13
+ end