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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/README.md +26 -14
- data/data/verified_gems.json +85 -63
- data/exe/rubycc-ar +11 -3
- data/include/libc/sys/cdefs.h +12 -0
- data/lib/rubycc/backend/aarch64.rb +705 -117
- data/lib/rubycc/backend/slot_residency.rb +169 -0
- data/lib/rubycc/backend/x86_64.rb +924 -137
- data/lib/rubycc/command_line.rb +339 -0
- data/lib/rubycc/compile_error.rb +6 -3
- data/lib/rubycc/compiler.rb +17 -2
- data/lib/rubycc/diagnostics.rb +105 -0
- data/lib/rubycc/doctor/gemfile.rb +12 -3
- data/lib/rubycc/doctor/verified_gems.rb +5 -1
- data/lib/rubycc/driver.rb +66 -10
- data/lib/rubycc/front/ast.rb +18 -7
- data/lib/rubycc/front/constant_evaluator.rb +12 -0
- data/lib/rubycc/front/lexeme_reader.rb +3 -1
- data/lib/rubycc/front/parser.rb +51 -18
- data/lib/rubycc/ir/analysis.rb +82 -0
- data/lib/rubycc/ir/call_convention.rb +74 -7
- data/lib/rubycc/ir/generator.rb +319 -9
- data/lib/rubycc/ir/ir.rb +39 -1
- data/lib/rubycc/ir/promotion.rb +255 -0
- data/lib/rubycc/ir/simplify.rb +570 -0
- data/lib/rubycc/link/library_resolver.rb +17 -5
- data/lib/rubycc/link/partial_linker.rb +8 -1
- data/lib/rubycc/link/shared_linker.rb +2 -2
- data/lib/rubycc/mkmf_shim.rb +178 -12
- data/lib/rubycc/objfile/ar_archive.rb +13 -2
- data/lib/rubycc/objfile/elf_reader.rb +13 -2
- data/lib/rubycc/pkgconf/parser.rb +4 -0
- data/lib/rubycc/pkgconf/resolver.rb +3 -1
- data/lib/rubycc/pkgconf/system_path_filter.rb +8 -2
- data/lib/rubycc/preprocess/preprocessor.rb +161 -37
- data/lib/rubycc/preprocess/scanner.rb +69 -14
- data/lib/rubycc/preprocess/token_converter.rb +11 -1
- data/lib/rubycc/rmake/cli.rb +32 -4
- data/lib/rubycc/rmake/executor.rb +157 -226
- data/lib/rubycc/rmake/makefile.rb +35 -13
- data/lib/rubycc/rmake/parser.rb +8 -2
- data/lib/rubycc/rmake/rmake.rb +1 -0
- data/lib/rubycc/rmake/tool_command.rb +69 -0
- data/lib/rubycc/shell.rb +510 -0
- data/lib/rubycc/type.rb +23 -7
- data/lib/rubycc/version.rb +1 -1
- data/lib/rubycc.rb +12 -0
- data/lib/rubygems_plugin.rb +31 -3
- 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
|