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,546 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Rubycc
6
+ module Link
7
+ # The static-link core, an `ld -r` equivalent: it merges an ordered list of
8
+ # ET_REL objects (and archive members pulled in lazily) into a single ET_REL
9
+ # object. Relocations are *retargeted*, not applied — an `ld -r` output keeps
10
+ # its relocations so a later final link can resolve them; the byte-patching
11
+ # apply engine belongs to the executable/.so writers, not here.
12
+ #
13
+ # The pipeline is two phases over the reader structures directly (no linker
14
+ # IR, per the roadmap): a selection phase walks the inputs left to right to
15
+ # decide *which* objects take part — a `.o` joins unconditionally, an archive
16
+ # contributes only members that resolve a still-undefined symbol (iterated to
17
+ # a fixpoint within that archive; classic single-pass, so an archive already
18
+ # passed is never revisited) — and a merge phase concatenates same-named
19
+ # sections, resolves the global symbol table, and rewrites every relocation
20
+ # onto the merged sections and symbol table before handing the result to
21
+ # RelocatableWriter.
22
+ #
23
+ # The single subtle point is a relocation against a *section symbol*: its
24
+ # target section's bytes move to a new offset in the merged section, so the
25
+ # addend, which is measured from that section's start, must gain the piece's
26
+ # placement offset. Named-symbol references need no addend fix-up because the
27
+ # symbol's own value already carries the shift.
28
+ #
29
+ # Unresolved symbols are legal in an `ld -r` output and stay UND. COMMON
30
+ # symbols are diagnosed rather than allocated (out of scope; neither rubycc
31
+ # nor gcc's -fno-common default emits them). Output is deterministic (N4):
32
+ # sections appear in first-seen order, symbols in a fixed local-then-global
33
+ # order, so identical inputs yield byte-identical output.
34
+ class PartialLinker
35
+ include ObjFile
36
+
37
+ ELFMAG = "\x7FELF".b
38
+ AR_MAGIC = "!<arch>\n".b
39
+
40
+ # Section header types the merge treats specially. Everything else — any
41
+ # named PROGBITS/NOBITS/NOTE/*_ARRAY the compiler emits or gcc adds — is
42
+ # carried through and concatenated by name.
43
+ SHT_NULL = 0
44
+ SHT_SYMTAB = 2
45
+ SHT_STRTAB = 3
46
+ SHT_RELA = 4
47
+ SHT_REL = 9
48
+ SHT_NOBITS = 8
49
+ SHT_GROUP = 17
50
+ SHT_SYMTAB_SHNDX = 18
51
+ # Section kinds that describe the object's own linking metadata (symbol and
52
+ # string tables, relocation tables, section groups) — regenerated by the
53
+ # writer, so never carried from the inputs.
54
+ SKIPPED_TYPES = [SHT_NULL, SHT_SYMTAB, SHT_STRTAB, SHT_RELA, SHT_REL,
55
+ SHT_GROUP, SHT_SYMTAB_SHNDX].freeze
56
+
57
+ SHN_UNDEF = 0
58
+ SHN_ABS = 0xFFF1
59
+
60
+ class << self
61
+ # Links `inputs` (an ordered array; each element a filesystem path, or the
62
+ # raw bytes of an ET_REL object or an ar archive) and returns the merged
63
+ # ET_REL object as an ASCII-8BIT String.
64
+ def link(inputs)
65
+ new(inputs).link
66
+ end
67
+
68
+ # Convenience: link and write the merged object to `path`.
69
+ def link_to(inputs, path)
70
+ File.binwrite(path, link(inputs))
71
+ end
72
+ end
73
+
74
+ def initialize(inputs)
75
+ @inputs = inputs.each_with_index.map { |raw, i| load_input(raw, i) }
76
+ end
77
+
78
+ def link
79
+ included = select_inputs
80
+ Merger.new(included).run
81
+ end
82
+
83
+ private
84
+
85
+ # A classified input: :object or :archive, the reader, and a label used in
86
+ # diagnostics (the path, or a synthetic name for raw bytes).
87
+ Input = Struct.new(:kind, :label, :reader, keyword_init: true)
88
+
89
+ # Classifies one raw input. A String that already opens with an ELF or ar
90
+ # magic is taken as in-memory bytes; anything else is a filesystem path to
91
+ # read. The resulting bytes are then dispatched to the matching reader.
92
+ def load_input(raw, index)
93
+ if raw.is_a?(String) && (raw.b.start_with?(ELFMAG) || raw.b.start_with?(AR_MAGIC))
94
+ bytes = raw.b
95
+ label = "input##{index}"
96
+ else
97
+ label = raw.to_s
98
+ bytes = File.binread(label)
99
+ end
100
+
101
+ if bytes.start_with?(AR_MAGIC)
102
+ Input.new(kind: :archive, label: label, reader: ArReader.read(bytes))
103
+ else
104
+ Input.new(kind: :object, label: label, reader: ELFReader.read(bytes))
105
+ end
106
+ end
107
+
108
+ # Selection phase: walk the inputs left to right, returning the ordered list
109
+ # of participating objects ([{reader:, label:}]). Object inputs join
110
+ # unconditionally; archives contribute members lazily.
111
+ def select_inputs
112
+ @included = []
113
+ @defined = Set.new # global/weak names already defined by an included object
114
+ @undefined = Set.new # names referenced but not yet defined
115
+ # When non-nil, #include_object appends every name it newly adds to
116
+ # @undefined here; the archive walk uses it to widen its candidate set.
117
+ @new_undefined = nil
118
+
119
+ @inputs.each do |input|
120
+ case input.kind
121
+ when :object then include_object(input.reader, input.label)
122
+ when :archive then pull_from_archive(input)
123
+ end
124
+ end
125
+ @included
126
+ end
127
+
128
+ # Adds an object to the participating set and folds its global/weak symbols
129
+ # into the running defined/undefined name sets, so a later archive pass
130
+ # knows what still needs resolving.
131
+ def include_object(reader, label)
132
+ @included << { reader: reader, label: label }
133
+ reader.symbols.each do |sym|
134
+ next unless sym.bind == :global || sym.bind == :weak
135
+ next if sym.name.to_s.empty?
136
+
137
+ if sym.defined?
138
+ @defined << sym.name
139
+ @undefined.delete(sym.name)
140
+ elsif sym.undefined? && !@defined.include?(sym.name)
141
+ @new_undefined << sym.name if @new_undefined && !@undefined.include?(sym.name)
142
+ @undefined << sym.name
143
+ end
144
+ end
145
+ end
146
+
147
+ # Pulls members from one archive to a fixpoint: repeatedly take any not-yet
148
+ # included member whose exports satisfy a currently-undefined symbol, since
149
+ # loading a member can surface new undefined references that pull further
150
+ # members from this SAME archive. Members are scanned in archive order for a
151
+ # deterministic result. Once this call returns the archive is done — a later
152
+ # input cannot reach back into it (single-pass semantics).
153
+ #
154
+ # The naive form of that fixpoint rescans EVERY member after each pull, so
155
+ # an archive whose members are ordered against their dependency direction
156
+ # takes one member per pass: O(M^2) member visits, which a hostile archive
157
+ # controls outright by choosing the member order. Instead an exporter index
158
+ # (symbol name -> the members defining it) narrows each pass to the members
159
+ # that could possibly match: a member can only be pulled through a name
160
+ # that is in @undefined, and every such name either was there when the
161
+ # archive was reached or was added by a member pulled from it, so seeding
162
+ # the candidate set from the former and widening it on the latter yields a
163
+ # SUPERSET of the pullable members — nothing is ever missed.
164
+ #
165
+ # The pull ORDER must not change, and that is the delicate part: it fixes
166
+ # the order of the merged sections and of the output symbol table, so N4
167
+ # (identical inputs produce byte-identical output) rests on it. Two rules
168
+ # preserve it exactly. Candidates are visited in ascending member index, so
169
+ # a pass sees them in archive order; and a candidate that appears mid-pass
170
+ # is spliced into the remaining queue when it sits after the current
171
+ # position, because the all-members scan would have reached it later in the
172
+ # same pass. Members that are not candidates are exactly the ones the
173
+ # all-members scan visits and rejects, so skipping them changes nothing.
174
+ def pull_from_archive(input)
175
+ members = input.reader.members
176
+ exporters = exporter_index(members)
177
+ taken = Set.new
178
+ candidates = Set.new
179
+ @undefined.each { |name| widen(candidates, exporters[name], taken, members) }
180
+
181
+ loop do
182
+ pulled = pull_pass(input, members, exporters, taken, candidates)
183
+ break unless pulled
184
+ end
185
+ end
186
+
187
+ # One archive-order pass over the current candidates. Returns whether any
188
+ # member was pulled, which is the fixpoint's termination test.
189
+ def pull_pass(input, members, exporters, taken, candidates)
190
+ pulled = false
191
+ queue = candidates.to_a.sort
192
+ qi = 0
193
+ while qi < queue.length
194
+ index = queue[qi]
195
+ qi += 1
196
+ member = members[index]
197
+ next if member.special? || taken.include?(member)
198
+ next unless member_exports(member).any? { |name| @undefined.include?(name) }
199
+
200
+ @new_undefined = []
201
+ include_object(ELFReader.read(member.data), "#{input.label}(#{member.name})")
202
+ added = @new_undefined
203
+ @new_undefined = nil
204
+ taken << member
205
+ candidates.delete(index)
206
+ pulled = true
207
+ added.each do |name|
208
+ widen(candidates, exporters[name], taken, members) do |cand|
209
+ # Still ahead of us: the all-members scan would reach it in this
210
+ # very pass, so the queue has to as well.
211
+ insert_pending(queue, qi, cand) if cand > index
212
+ end
213
+ end
214
+ end
215
+ pulled
216
+ end
217
+
218
+ # Adds the members exporting one name to the candidate set, yielding each
219
+ # index that was not a candidate already (so a caller mid-pass can queue
220
+ # it). Members already pulled are never re-added.
221
+ def widen(candidates, indexes, taken, members)
222
+ return if indexes.nil?
223
+
224
+ indexes.each do |index|
225
+ next if candidates.include?(index) || taken.include?(members[index])
226
+
227
+ candidates << index
228
+ yield index if block_given?
229
+ end
230
+ end
231
+
232
+ # Splices `value` into the not-yet-visited tail queue[from..], which is
233
+ # sorted ascending, keeping it sorted.
234
+ def insert_pending(queue, from, value)
235
+ lo = from
236
+ hi = queue.length
237
+ while lo < hi
238
+ mid = (lo + hi) / 2
239
+ if queue[mid] < value
240
+ lo = mid + 1
241
+ else
242
+ hi = mid
243
+ end
244
+ end
245
+ queue.insert(lo, value)
246
+ end
247
+
248
+ # symbol name => ascending array of the indexes of the members exporting
249
+ # it. Built once per archive off the same memoized #member_exports the
250
+ # selection uses, so it costs one parse per member — the same parse the
251
+ # first fixpoint pass would have done anyway.
252
+ def exporter_index(members)
253
+ index = {}
254
+ members.each_with_index do |member, i|
255
+ next if member.special?
256
+
257
+ member_exports(member).each { |name| (index[name] ||= []) << i }
258
+ end
259
+ index
260
+ end
261
+
262
+ # The defined global/weak symbol names a member exports, memoized per
263
+ # member. A member ELFReader cannot parse contributes none.
264
+ def member_exports(member)
265
+ @member_exports ||= {}
266
+ @member_exports[member.object_id] ||= begin
267
+ reader = ELFReader.read(member.data)
268
+ reader.symbols.select do |sym|
269
+ (sym.bind == :global || sym.bind == :weak) && sym.defined? && !sym.name.to_s.empty?
270
+ end.map(&:name)
271
+ rescue ObjFile::ELFFormatError
272
+ []
273
+ end
274
+ end
275
+
276
+ # Merges the selected objects into one ET_REL image. Kept as an inner class
277
+ # so the phase-2 state (placement maps, the output symbol table, the
278
+ # per-object symbol remaps) stays isolated from the selection driver.
279
+ class Merger
280
+ include ObjFile
281
+
282
+ SHT_NOBITS = PartialLinker::SHT_NOBITS
283
+ SHN_ABS = PartialLinker::SHN_ABS
284
+
285
+ def initialize(included)
286
+ @objects = included # [{reader:, label:}]
287
+ # The merged object keeps the inputs' architecture: the merge is
288
+ # machine-independent (section concatenation and numeric-type relocation
289
+ # retargeting), but the output header must carry the right e_machine so
290
+ # the final linker that reads it back can pick the target's relocation
291
+ # and crt logic. Empty inputs default to x86_64 through RelocatableWriter.
292
+ machine = included.first&.dig(:reader)&.machine
293
+ @writer = machine ? RelocatableWriter.new(machine: machine) : RelocatableWriter.new
294
+ end
295
+
296
+ def run
297
+ merge_sections
298
+ resolve_symbols
299
+ retarget_relocations
300
+ @writer.to_binary
301
+ end
302
+
303
+ private
304
+
305
+ # --- section merging -------------------------------------------------
306
+
307
+ # Concatenates same-named content sections in object order. For each
308
+ # object/section it records the piece's placement offset within the merged
309
+ # section — the shift every symbol value and relocation offset in that
310
+ # piece is adjusted by. Output alignment/entsize are the max over inputs;
311
+ # a NOBITS section (.bss) merges by size only.
312
+ def merge_sections
313
+ @order = [] # output section names, first-seen order
314
+ @meta = {} # name => merge metadata
315
+ @data = {} # name => merged bytes (PROGBITS)
316
+ @size = Hash.new(0) # name => running size
317
+ @placement = Array.new(@objects.size) { {} } # [oi][in_section.index] => offset
318
+
319
+ @objects.each_with_index do |obj, oi|
320
+ obj[:reader].sections.each do |sec|
321
+ next unless carried?(sec)
322
+
323
+ place_piece(oi, sec)
324
+ end
325
+ end
326
+
327
+ @out_section = {}
328
+ @order.each do |name|
329
+ m = @meta[name]
330
+ @out_section[name] =
331
+ if m[:type] == SHT_NOBITS
332
+ @writer.add_section(name: name, type: m[:type], flags: m[:flags],
333
+ addralign: m[:addralign], entsize: m[:entsize], size: @size[name])
334
+ else
335
+ @writer.add_section(name: name, type: m[:type], flags: m[:flags],
336
+ addralign: m[:addralign], entsize: m[:entsize], data: @data[name])
337
+ end
338
+ end
339
+ end
340
+
341
+ # A section carried into the output: any named section that is not one of
342
+ # the regenerated linking-metadata kinds.
343
+ def carried?(sec)
344
+ !PartialLinker::SKIPPED_TYPES.include?(sec.type) && !sec.name.to_s.empty?
345
+ end
346
+
347
+ # Places one input section piece into its merged section, aligning the
348
+ # running length up to the piece's own alignment first and recording where
349
+ # it landed. The output metadata accumulates the max alignment/entsize and
350
+ # the union of flags across pieces.
351
+ def place_piece(oi, sec)
352
+ name = sec.name
353
+ unless @meta.key?(name)
354
+ @order << name
355
+ @meta[name] = { type: sec.type, flags: sec.flags,
356
+ addralign: [sec.addralign, 1].max, entsize: sec.entsize }
357
+ end
358
+ m = @meta[name]
359
+ m[:addralign] = [m[:addralign], sec.addralign, 1].max
360
+ m[:entsize] = [m[:entsize], sec.entsize].max
361
+ m[:flags] |= sec.flags
362
+
363
+ offset = align(@size[name], [sec.addralign, 1].max)
364
+ @placement[oi][sec.index] = offset
365
+
366
+ if sec.type == SHT_NOBITS
367
+ @size[name] = offset + sec.size
368
+ else
369
+ buf = (@data[name] ||= +"".b)
370
+ buf << ("\0" * (offset - buf.bytesize)).b if buf.bytesize < offset
371
+ buf << sec.data
372
+ @size[name] = buf.bytesize
373
+ end
374
+ end
375
+
376
+ # --- symbol resolution ----------------------------------------------
377
+
378
+ # Builds the output symbol table in a fixed order — section symbols, then
379
+ # each object's local symbols, then the resolved globals — and records,
380
+ # per object, the map from an input symbol to its output symbol so
381
+ # relocations can be re-pointed.
382
+ def resolve_symbols
383
+ @remap = Array.new(@objects.size) { {} } # [oi][in_symbol.index] => out Symbol
384
+ @section_symbol = {} # output section name => out Symbol
385
+ @order.each do |name|
386
+ @section_symbol[name] =
387
+ @writer.add_symbol(name: nil, bind: :local, type: :section, section: @out_section[name])
388
+ end
389
+
390
+ add_local_symbols
391
+ resolve_globals
392
+ end
393
+
394
+ # Passes every input local symbol through per object, shifting a defined
395
+ # local's value by its section's placement offset and re-pointing it at the
396
+ # merged output section. Section symbols are not passed through (the output
397
+ # has one synthesized per merged section); undefined or dropped-section
398
+ # locals are skipped.
399
+ def add_local_symbols
400
+ @objects.each_with_index do |obj, oi|
401
+ obj[:reader].symbols.each do |sym|
402
+ next if sym.index.zero? # the reserved null symbol
403
+ next unless sym.bind == :local
404
+ next if sym.type == :section # synthesized, not passed through
405
+
406
+ @remap[oi][sym.index] = local_output_symbol(oi, sym)
407
+ end
408
+ end
409
+ end
410
+
411
+ # The output symbol for one input local, or nil when it cannot be carried
412
+ # (an undefined local, or one defined in a section that was not merged).
413
+ def local_output_symbol(oi, sym)
414
+ if sym.type == :file
415
+ @writer.add_symbol(name: sym.name, bind: :local, type: :file, shndx: SHN_ABS)
416
+ elsif sym.absolute?
417
+ @writer.add_symbol(name: sym.name, bind: :local, type: sym.type, shndx: SHN_ABS,
418
+ value: sym.value, size: sym.size)
419
+ elsif sym.defined? && sym.section && @out_section.key?(sym.section.name)
420
+ out = @out_section[sym.section.name]
421
+ @writer.add_symbol(name: sym.name, bind: :local, type: sym.type,
422
+ visibility: sym.visibility, section: out,
423
+ value: @placement[oi][sym.section.index] + sym.value, size: sym.size)
424
+ end
425
+ end
426
+
427
+ # Resolves the global/weak symbols to one output entry per name, applying
428
+ # the definition-precedence rules, then re-points every object's reference
429
+ # to that single entry.
430
+ def resolve_globals
431
+ @globals = {} # name => resolution state (insertion order = first-seen)
432
+ @objects.each_with_index do |obj, oi|
433
+ obj[:reader].symbols.each do |sym|
434
+ next unless sym.bind == :global || sym.bind == :weak
435
+ next if sym.name.to_s.empty?
436
+
437
+ record_global(oi, sym, obj[:label])
438
+ end
439
+ end
440
+
441
+ out = emit_globals
442
+ # A relocation may reference a global that this object leaves undefined;
443
+ # it still points at the one merged entry, so map every occurrence.
444
+ @objects.each_with_index do |obj, oi|
445
+ obj[:reader].symbols.each do |sym|
446
+ next unless sym.bind == :global || sym.bind == :weak
447
+ next if sym.name.to_s.empty?
448
+
449
+ @remap[oi][sym.index] = out[sym.name]
450
+ end
451
+ end
452
+ end
453
+
454
+ # Folds one global/weak symbol into the resolution state, enforcing the
455
+ # precedence rules: a strong definition overrides a weak one, a second
456
+ # strong definition is a hard error, weak-vs-weak keeps the first, and a
457
+ # definition always beats an undefined reference. COMMON is unsupported.
458
+ def record_global(oi, sym, label)
459
+ if sym.common?
460
+ raise LinkError, "COMMON symbol '#{sym.name}' is not supported (compile with -fno-common)"
461
+ end
462
+
463
+ state = @globals[sym.name]
464
+ weak = sym.bind == :weak
465
+
466
+ if sym.defined?
467
+ if state.nil? || !state[:defined]
468
+ @globals[sym.name] = { defined: true, oi: oi, sym: sym, weak: weak, label: label }
469
+ elsif state[:weak] && !weak
470
+ @globals[sym.name] = { defined: true, oi: oi, sym: sym, weak: false, label: label }
471
+ elsif !state[:weak] && !weak
472
+ raise LinkError,
473
+ "multiple definition of '#{sym.name}' (defined in #{state[:label]} and #{label})"
474
+ end
475
+ # strong-over-weak keep, weak-over-strong ignore, weak-over-weak first wins
476
+ elsif state.nil?
477
+ @globals[sym.name] = { defined: false, weak: weak }
478
+ elsif !state[:defined] && !weak
479
+ state[:weak] = false # a strong reference promotes the pending UND binding
480
+ end
481
+ end
482
+
483
+ # Emits the resolved globals as output symbols and returns a name -> output
484
+ # Symbol map. A defined winner is shifted onto its merged section; an
485
+ # all-undefined name stays UND with the strongest observed binding.
486
+ def emit_globals
487
+ out = {}
488
+ @globals.each do |name, state|
489
+ out[name] =
490
+ if state[:defined]
491
+ sym = state[:sym]
492
+ section = @out_section[sym.section.name]
493
+ @writer.add_symbol(name: name, bind: state[:weak] ? :weak : :global,
494
+ type: sym.type, visibility: sym.visibility, section: section,
495
+ value: @placement[state[:oi]][sym.section.index] + sym.value,
496
+ size: sym.size)
497
+ else
498
+ @writer.add_symbol(name: name, bind: state[:weak] ? :weak : :global,
499
+ type: :notype, shndx: SHN_UNDEF)
500
+ end
501
+ end
502
+ out
503
+ end
504
+
505
+ # --- relocation retargeting -----------------------------------------
506
+
507
+ # Carries every input relocation to the merged output: the offset gains the
508
+ # target piece's placement, and the symbol reference is re-pointed. A
509
+ # section-symbol reference moves to the OUTPUT section symbol with its
510
+ # addend bumped by the referenced piece's placement (the section's bytes
511
+ # now start there); every other reference keeps its addend. Relocation
512
+ # types pass through untouched.
513
+ def retarget_relocations
514
+ @objects.each_with_index do |obj, oi|
515
+ obj[:reader].relocation_sections.each do |rs|
516
+ target = rs.target
517
+ next unless target && @out_section.key?(target.name)
518
+
519
+ base = @placement[oi][target.index]
520
+ out_target = @out_section[target.name]
521
+ rs.relocations.each { |reloc| emit_relocation(oi, out_target, base, reloc) }
522
+ end
523
+ end
524
+ end
525
+
526
+ def emit_relocation(oi, out_target, base, reloc)
527
+ sym = reloc.symbol
528
+ if sym && sym.type == :section && sym.section && @out_section.key?(sym.section.name)
529
+ out_sym = @section_symbol[sym.section.name]
530
+ addend = reloc.addend + @placement[oi][sym.section.index]
531
+ else
532
+ out_sym = (sym && @remap[oi][sym.index]) || @writer.null_symbol
533
+ addend = reloc.addend
534
+ end
535
+
536
+ @writer.add_relocation(target: out_target, offset: reloc.offset + base,
537
+ symbol: out_sym, type: reloc.type, addend: addend)
538
+ end
539
+
540
+ def align(value, alignment)
541
+ (value + alignment - 1) / alignment * alignment
542
+ end
543
+ end
544
+ end
545
+ end
546
+ end