rubycc 1.0.0 → 1.1.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/README.md +26 -14
  4. data/data/verified_gems.json +85 -63
  5. data/exe/rubycc-ar +11 -3
  6. data/include/libc/sys/cdefs.h +12 -0
  7. data/lib/rubycc/backend/aarch64.rb +705 -117
  8. data/lib/rubycc/backend/slot_residency.rb +169 -0
  9. data/lib/rubycc/backend/x86_64.rb +924 -137
  10. data/lib/rubycc/command_line.rb +339 -0
  11. data/lib/rubycc/compile_error.rb +6 -3
  12. data/lib/rubycc/compiler.rb +17 -2
  13. data/lib/rubycc/diagnostics.rb +105 -0
  14. data/lib/rubycc/doctor/gemfile.rb +12 -3
  15. data/lib/rubycc/doctor/verified_gems.rb +5 -1
  16. data/lib/rubycc/driver.rb +66 -10
  17. data/lib/rubycc/front/ast.rb +18 -7
  18. data/lib/rubycc/front/constant_evaluator.rb +12 -0
  19. data/lib/rubycc/front/lexeme_reader.rb +3 -1
  20. data/lib/rubycc/front/parser.rb +51 -18
  21. data/lib/rubycc/ir/analysis.rb +82 -0
  22. data/lib/rubycc/ir/call_convention.rb +74 -7
  23. data/lib/rubycc/ir/generator.rb +319 -9
  24. data/lib/rubycc/ir/ir.rb +39 -1
  25. data/lib/rubycc/ir/promotion.rb +255 -0
  26. data/lib/rubycc/ir/simplify.rb +570 -0
  27. data/lib/rubycc/link/library_resolver.rb +17 -5
  28. data/lib/rubycc/link/partial_linker.rb +8 -1
  29. data/lib/rubycc/link/shared_linker.rb +2 -2
  30. data/lib/rubycc/mkmf_shim.rb +178 -12
  31. data/lib/rubycc/objfile/ar_archive.rb +13 -2
  32. data/lib/rubycc/objfile/elf_reader.rb +13 -2
  33. data/lib/rubycc/pkgconf/parser.rb +4 -0
  34. data/lib/rubycc/pkgconf/resolver.rb +3 -1
  35. data/lib/rubycc/pkgconf/system_path_filter.rb +8 -2
  36. data/lib/rubycc/preprocess/preprocessor.rb +161 -37
  37. data/lib/rubycc/preprocess/scanner.rb +69 -14
  38. data/lib/rubycc/preprocess/token_converter.rb +11 -1
  39. data/lib/rubycc/rmake/cli.rb +32 -4
  40. data/lib/rubycc/rmake/executor.rb +157 -226
  41. data/lib/rubycc/rmake/makefile.rb +35 -13
  42. data/lib/rubycc/rmake/parser.rb +8 -2
  43. data/lib/rubycc/rmake/rmake.rb +1 -0
  44. data/lib/rubycc/rmake/tool_command.rb +69 -0
  45. data/lib/rubycc/shell.rb +510 -0
  46. data/lib/rubycc/type.rb +23 -7
  47. data/lib/rubycc/version.rb +1 -1
  48. data/lib/rubycc.rb +12 -0
  49. data/lib/rubygems_plugin.rb +31 -3
  50. metadata +14 -3
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ir"
4
+ require_relative "simplify"
5
+ require_relative "analysis"
6
+
7
+ module Rubycc
8
+ module IR
9
+ # Chooses which virtual registers a backend should keep in a callee-saved
10
+ # machine register for the whole of a function, instead of in the stack slot
11
+ # the spill-everything discipline gives every value.
12
+ #
13
+ # The allocation this feeds is the crudest one that can pay: **one vreg owns
14
+ # one register for the function's entire length**. Nothing is ever
15
+ # reassigned, so two promoted values can never want the same register and
16
+ # interference cannot arise by construction — which is what lets this file
17
+ # exist without a control-flow graph, a live range, an interference graph or
18
+ # a spill heuristic. It is the same bargain IR::Simplify strikes one step
19
+ # earlier: decide from the flat instruction list, and refuse whatever the
20
+ # flat list cannot decide.
21
+ #
22
+ # Two questions are answered below, and only these two:
23
+ #
24
+ # 1. **Which vregs may be promoted at all** (#candidates' guards). A value
25
+ # whose address is taken has to be in memory; a value that travels
26
+ # through the vector register file has no general-purpose home to be
27
+ # promoted into; a variadic function's register-save area is written
28
+ # behind the IR's back; and an op this file does not recognize could be
29
+ # reading anything.
30
+ # 2. **In what order they are worth promoting** (#weighted_occurrences),
31
+ # there being fewer registers than candidates.
32
+ #
33
+ # Both answers are functions of the Function object alone — the instruction
34
+ # list, plus `variadic` and `param_kinds` for the two effects the list does
35
+ # not describe — with no dependence on hash iteration or anything else that
36
+ # could differ between runs, so the same source keeps producing the same
37
+ # bytes (N4).
38
+ module Promotion
39
+ module_function
40
+
41
+ # What one backward branch's span multiplies an occurrence by. The figure
42
+ # only has to order candidates, not predict a trip count: a value used
43
+ # once per iteration should outrank one used a few times in straight-line
44
+ # code. Nesting multiplies, a doubly nested use being worth
45
+ # LOOP_WEIGHT ** 2, because an inner loop's body runs the product of the
46
+ # two trip counts.
47
+ LOOP_WEIGHT = 10
48
+
49
+ # The ops that read or write a vreg through the vector register file. A
50
+ # value either of these touches is refused outright: the slot convention
51
+ # has floating values living in the same 8-byte slots as integers, so
52
+ # promoting one would mean moving it between the two register files at
53
+ # every use — and System V has no callee-saved xmm register to hold it in
54
+ # instead. Integers and pointers are the whole of this version's business.
55
+ VECTOR_OPS = %i[fadd fsub fmul fdiv feq fne flt fle fgt fge itof ftoi ftof].freeze
56
+
57
+ # The ABI kinds that name a vector register. A parameter or argument so
58
+ # classified is moved with movss/movsd straight between its slot and an xmm
59
+ # register, so its vreg touches the vector file even though no op in
60
+ # VECTOR_OPS mentions it. :sse16 (an AAPCS64 quad-precision `long double`
61
+ # in a variadic call) carries an address rather than a value, so refusing
62
+ # it is only caution — but caution costs one entry here.
63
+ VECTOR_KINDS = %i[sse4 sse8 sse16].freeze
64
+
65
+ # The same two lists as lookup tables; the arrays above are what the rules
66
+ # are read and argued from, these are what the scan below asks per
67
+ # instruction.
68
+ VECTOR_OP = VECTOR_OPS.to_h { |op| [op, true] }.freeze
69
+ VECTOR_KIND = VECTOR_KINDS.to_h { |kind| [kind, true] }.freeze
70
+
71
+ # The virtual registers worth promoting in `function`, best first. A
72
+ # backend takes as many as it has registers for; the tail is left in slots
73
+ # and costs nothing.
74
+ #
75
+ # `analysis` is the census of `function`'s instruction list, which the
76
+ # caller has usually taken already (the backend needs the transient set
77
+ # for itself, and the transient set is one of the two exclusions here).
78
+ def candidates(function, analysis = Analysis.of(function))
79
+ insts = function.insts
80
+ # A variadic function's prologue spills all six integer argument
81
+ # registers into a register-save area __builtin_va_arg reads back, an
82
+ # effect no instruction in the list describes. Rather than reason about
83
+ # which values that can disturb, the whole function is refused.
84
+ return [] if function.variadic
85
+ # The read enumeration below has to be exhaustive — a missed read would
86
+ # leave a value in a slot nobody ever writes — and an unrecognized op
87
+ # means it is not. Same fail-safe as IR::Simplify#run.
88
+ return [] unless analysis.known?
89
+
90
+ blocked = ineligible_vregs(insts, function.param_kinds, function.vreg_count)
91
+ transient = analysis.transient
92
+ counts = weighted_occurrences(insts, function.vreg_count)
93
+ chosen = []
94
+ vreg = 0
95
+ limit = counts.size
96
+ while vreg < limit
97
+ weight = counts[vreg]
98
+ # An unmentioned register has no occurrences to weigh and is no
99
+ # candidate: promoting one would spend a register, and a save and a
100
+ # restore, on a value that is never named.
101
+ chosen << vreg unless weight.nil? || weight.zero? || blocked[vreg] || transient[vreg]
102
+ vreg += 1
103
+ end
104
+ # Heaviest first, ties broken by register number. The order is a total
105
+ # one — no two candidates share a number — so it does not depend on the
106
+ # sort being stable, and the same source keeps producing the same bytes
107
+ # (N4). Comparing the two numbers in place is what a sort_by on
108
+ # [-weight, vreg] would do, without the pair it would have built (and
109
+ # then compared element by element) for every candidate.
110
+ chosen.sort! do |left, right|
111
+ by_weight = counts[right] <=> counts[left]
112
+ by_weight.zero? ? left <=> right : by_weight
113
+ end
114
+ chosen
115
+ end
116
+
117
+ # The vregs that must stay in their slots whatever their use count, as an
118
+ # array indexed by register number holding true for a blocked one.
119
+ #
120
+ # Besides the vector cases, one exclusion is about payoff rather than
121
+ # correctness: a *transient* (IR::Simplify#transient_flags) never reaches
122
+ # its slot at all, its one reader being the instruction right behind its
123
+ # producer. Promoting one would replace two instructions that do not exist
124
+ # with two register moves that do, and spend a register doing it, so the
125
+ # occurrence count — which cannot tell a slot round trip from a value that
126
+ # simply stayed in eax — is corrected in #candidates instead.
127
+ def ineligible_vregs(insts, param_kinds, vreg_count = 0)
128
+ blocked = Array.new(vreg_count)
129
+ param_kinds&.each_with_index { |kind, slot| blocked[slot] = true if VECTOR_KIND[kind] }
130
+ index = 0
131
+ size = insts.size
132
+ while index < size
133
+ inst = insts[index]
134
+ index += 1
135
+ block_vector_uses(blocked, inst)
136
+ # "&v" hands out the address of v's slot, and every later read through
137
+ # that pointer expects to find the value there. A promoted value is
138
+ # not there.
139
+ blocked[inst.a] = true if inst.op == :addr_of
140
+ end
141
+ blocked
142
+ end
143
+
144
+ # Adds whatever of `inst` travels through the vector register file. Both
145
+ # the operands and the result of a VECTOR_OPS instruction go in, even where
146
+ # only one end is a vector one (:itof reads a general-purpose register and
147
+ # :ftoi writes one), because refusing the pair costs one candidate and
148
+ # saves a rule per op.
149
+ def block_vector_uses(blocked, inst)
150
+ op = inst.op
151
+ if VECTOR_OP[op]
152
+ blocked[inst.dst] = true unless inst.dst.nil?
153
+ Simplify.each_operand_vreg(inst) { |vreg| blocked[vreg] = true }
154
+ end
155
+ case op
156
+ when :call, :call_indirect
157
+ inst.b.each { |vreg, kind| blocked[vreg] = true if vreg && VECTOR_KIND[kind] }
158
+ # `size` is the [fixed, ret] descriptor; a :sse4/:sse8 result comes
159
+ # back in xmm0 and is written to dst's slot with movss/movsd.
160
+ blocked[inst.dst] = true if !inst.dst.nil? && VECTOR_KIND[inst.size&.last]
161
+ when :ret
162
+ # An integer return's `size` is nil and a struct's an AbiPiece array;
163
+ # only a float/double return carries a width, and that one is read out
164
+ # of its slot into xmm0.
165
+ blocked[inst.a] = true if inst.size.is_a?(Integer)
166
+ end
167
+ end
168
+
169
+ # How much each vreg's occurrences are worth, as an array indexed by
170
+ # register number: one point per read and one per write, multiplied by the
171
+ # loop weight of the instruction it appears in. Reads and writes count the
172
+ # same because both are one slot access in a spill-everything backend,
173
+ # which is exactly what promotion removes.
174
+ #
175
+ # The depth the weight comes from is accumulated over the same pass that
176
+ # counts the occurrences — one running sum of #loop_deltas — rather than
177
+ # materialized as a weight per instruction first.
178
+ def weighted_occurrences(insts, vreg_count = 0)
179
+ deltas = loop_deltas(insts)
180
+ counts = Array.new(vreg_count, 0)
181
+ depth = 0
182
+ weight = 1
183
+ index = 0
184
+ size = insts.size
185
+ while index < size
186
+ inst = insts[index]
187
+ delta = deltas[index]
188
+ index += 1
189
+ unless delta.zero?
190
+ depth += delta
191
+ weight = LOOP_WEIGHT**depth
192
+ end
193
+ dst = inst.dst
194
+ unless dst.nil?
195
+ count = counts[dst]
196
+ counts[dst] = count ? count + weight : weight
197
+ end
198
+ Simplify.each_operand_vreg(inst) do |vreg|
199
+ count = counts[vreg]
200
+ counts[vreg] = count ? count + weight : weight
201
+ end
202
+ end
203
+ counts
204
+ end
205
+
206
+ # A difference list over instruction positions: +1 where a loop body
207
+ # begins, -1 just past where it ends, so nested spans accumulate into a
208
+ # depth by one running sum (LOOP_WEIGHT ** depth being the weight).
209
+ #
210
+ # The loops are found without a control-flow graph: a branch to a label
211
+ # that lies *behind* it can only be a loop's back edge, and everything
212
+ # between the label and the branch is the body it repeats. That span is
213
+ # what gets weighted. It over-counts an `if` whose two arms both sit inside
214
+ # such a span (only one of them runs per iteration) and misses a loop
215
+ # written with the branch out of line — but the answer only orders
216
+ # candidates, so being approximate costs a worse choice, never a wrong one.
217
+ #
218
+ # One pass is enough to find the back edges: a label is recorded as it is
219
+ # passed, so a branch finds its target in the table exactly when that
220
+ # target lies behind it, and a forward branch — which is not a back edge —
221
+ # finds nothing.
222
+ def loop_deltas(insts)
223
+ labels = nil
224
+ size = insts.size
225
+ deltas = Array.new(size + 1, 0)
226
+ index = 0
227
+ while index < size
228
+ inst = insts[index]
229
+ op = inst.op
230
+ if op == :label
231
+ (labels ||= {})[inst.a] = index
232
+ elsif labels && (op == :jump || op == :jump_if_zero)
233
+ start = labels[op == :jump ? inst.a : inst.b]
234
+ unless start.nil?
235
+ deltas[start] += 1
236
+ deltas[index + 1] -= 1
237
+ end
238
+ end
239
+ index += 1
240
+ end
241
+ deltas
242
+ end
243
+
244
+ # The label id `inst` may branch to, or nil when it is not a branch. The
245
+ # id lives in `a` for an unconditional jump and in `b` for a conditional
246
+ # one, whose `a` is the condition it tests.
247
+ def branch_target(inst)
248
+ case inst.op
249
+ when :jump then inst.a
250
+ when :jump_if_zero then inst.b
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end