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,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Backend
|
|
5
|
+
# Tracks, while one function is being emitted, which scratch registers still
|
|
6
|
+
# hold the value of which virtual-register slot — so a backend can drop a
|
|
7
|
+
# load of a value it has just written out.
|
|
8
|
+
#
|
|
9
|
+
# Both backends spill everything: each IR instruction loads its operands out
|
|
10
|
+
# of their slots, computes, and stores the result back. That makes the
|
|
11
|
+
# commonest adjacent pair in the stream a store followed immediately by a
|
|
12
|
+
# load of the same slot, the value never having left the register the store
|
|
13
|
+
# read from. This module is what lets the load go.
|
|
14
|
+
#
|
|
15
|
+
# The safety argument is deliberately crude, because a precise one would need
|
|
16
|
+
# exactly the liveness/aliasing machinery this layer is meant to do without:
|
|
17
|
+
#
|
|
18
|
+
# **A residency is believed only while not one byte has been emitted since
|
|
19
|
+
# it was recorded.**
|
|
20
|
+
#
|
|
21
|
+
# No register changes and no slot is written without an instruction, and
|
|
22
|
+
# every instruction reaches the code buffer through the backend's single
|
|
23
|
+
# emit primitive; so "the buffer has not grown" means "nothing has happened
|
|
24
|
+
# to either the registers or memory". That is what makes the aliasing case
|
|
25
|
+
# Step 11 recorded a non-issue here: a store through a pointer may land in
|
|
26
|
+
# any slot whose address was taken, but the store's *own bytes* end every
|
|
27
|
+
# residency in flight, so no reader can be told the slot still matches a
|
|
28
|
+
# register. The same covers calls, atomics and inline stack traffic without
|
|
29
|
+
# any of them being enumerated.
|
|
30
|
+
#
|
|
31
|
+
# Exactly one thing changes the machine state while emitting nothing: an IR
|
|
32
|
+
# :label, where control may arrive from a predecessor whose registers hold
|
|
33
|
+
# something else entirely. A label emits no code, so the buffer-length test
|
|
34
|
+
# cannot see the join, and both backends drop every residency there by hand
|
|
35
|
+
# (#forget_slot_residency). That single exception is the whole of the
|
|
36
|
+
# correctness burden this module places on its includers.
|
|
37
|
+
#
|
|
38
|
+
# The includer must provide `@code` (the ASCII-8BIT buffer being appended
|
|
39
|
+
# to) and must call #reset_slot_residency once per function.
|
|
40
|
+
#
|
|
41
|
+
# Two tables are kept, one per register file, because the files number their
|
|
42
|
+
# registers independently (xmm0 and rax are both register 0). The vector
|
|
43
|
+
# side records the *width* a value was moved at as well: movss and movsd do
|
|
44
|
+
# not leave the same register contents, so a 4-byte residency cannot answer
|
|
45
|
+
# an 8-byte question. The two tables are not independent, though — a slot
|
|
46
|
+
# written from either file invalidates claims on it in both — so every store
|
|
47
|
+
# goes through one method that cleans them together.
|
|
48
|
+
module SlotResidency
|
|
49
|
+
# Begins a function with nothing resident. The code buffer is empty here
|
|
50
|
+
# and no register describes a slot yet.
|
|
51
|
+
def reset_slot_residency
|
|
52
|
+
@resident = {}
|
|
53
|
+
@vector_resident = {}
|
|
54
|
+
@resident_at = nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Drops every residency unconditionally, for the one state change that
|
|
58
|
+
# emits no bytes: a control-flow join (an IR :label).
|
|
59
|
+
def forget_slot_residency
|
|
60
|
+
@resident_at = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Discards the table when anything has been emitted since it was recorded.
|
|
64
|
+
# Every query below is only meaningful after this has run, and every
|
|
65
|
+
# backend entry point calls it before consulting the table — which is also
|
|
66
|
+
# what stops a later #note_slot_loaded from silently re-validating entries
|
|
67
|
+
# that an intervening instruction invalidated.
|
|
68
|
+
def refresh_slot_residency
|
|
69
|
+
return if @resident_at == @code.bytesize
|
|
70
|
+
|
|
71
|
+
@resident.clear
|
|
72
|
+
@vector_resident.clear
|
|
73
|
+
@resident_at = nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Whether `reg` already holds the value of `vreg`'s slot.
|
|
77
|
+
def slot_resident_in?(reg, vreg)
|
|
78
|
+
@resident[reg] == vreg
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# A register already holding `vreg`'s slot value, or nil. Several
|
|
82
|
+
# registers may hold the same slot (a call passing one value twice); any
|
|
83
|
+
# of them will do, and the first found is chosen so the answer stays a
|
|
84
|
+
# function of the instruction stream alone (N4: same input, same bytes).
|
|
85
|
+
def register_holding_slot(vreg)
|
|
86
|
+
@resident.key(vreg)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Records that `reg` now holds `vreg`'s slot value, having just been read
|
|
90
|
+
# from that slot (or moved out of another register holding it). Only `reg`
|
|
91
|
+
# was written, so every other entry the table still carries stays true.
|
|
92
|
+
def note_slot_loaded(reg, vreg)
|
|
93
|
+
@resident[reg] = vreg
|
|
94
|
+
@resident_at = @code.bytesize
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Records that `vreg`'s slot has just been written from `reg`. Any other
|
|
98
|
+
# register that claimed the slot was describing its *old* contents and is
|
|
99
|
+
# now stale, so those claims go first — in the vector file too, since the
|
|
100
|
+
# slot the two files name is the same eight bytes; `reg`'s claim is the
|
|
101
|
+
# one the new contents match.
|
|
102
|
+
def note_slot_stored(reg, vreg)
|
|
103
|
+
drop_claims_on(vreg)
|
|
104
|
+
@resident[reg] = vreg
|
|
105
|
+
@resident_at = @code.bytesize
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Whether vector register `reg` holds `vreg`'s slot, moved at `size`
|
|
109
|
+
# bytes. The width is part of the question: a movss left the register's
|
|
110
|
+
# upper half zeroed rather than carrying the slot's other four bytes.
|
|
111
|
+
def slot_resident_in_vector?(reg, vreg, size)
|
|
112
|
+
held = @vector_resident[reg]
|
|
113
|
+
held && held[0] == vreg && held[1] == size
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A vector register holding `vreg`'s slot at `size` bytes, or nil.
|
|
117
|
+
def vector_register_holding_slot(vreg, size)
|
|
118
|
+
@vector_resident.each { |reg, held| return reg if held[0] == vreg && held[1] == size }
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# The vector-file counterpart of #note_slot_loaded.
|
|
123
|
+
def note_slot_loaded_to_vector(reg, vreg, size)
|
|
124
|
+
@vector_resident[reg] = [vreg, size]
|
|
125
|
+
@resident_at = @code.bytesize
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The vector-file counterpart of #note_slot_stored.
|
|
129
|
+
def note_slot_stored_from_vector(reg, vreg, size)
|
|
130
|
+
drop_claims_on(vreg)
|
|
131
|
+
@vector_resident[reg] = [vreg, size]
|
|
132
|
+
@resident_at = @code.bytesize
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Records that the bytes just emitted wrote `reg`, and no slot and no
|
|
136
|
+
# other register. Two shapes take it: a move out of a promoted register
|
|
137
|
+
# (whose value lives in a register of its own rather than in a slot, so no
|
|
138
|
+
# residency ever names it), and an instruction that computes its result
|
|
139
|
+
# straight into a promoted one — where the delete is what drops any claim
|
|
140
|
+
# the staging load recorded against that same register a moment earlier.
|
|
141
|
+
#
|
|
142
|
+
# This and #note_slots_undisturbed are the two ways of keeping a residency
|
|
143
|
+
# alive across an emission without claiming anything new. Both are sound
|
|
144
|
+
# under one condition, in two parts: the table must have been true
|
|
145
|
+
# immediately before those bytes — which #refresh_slot_residency
|
|
146
|
+
# establishes, and so does any note that ran with nothing emitted since —
|
|
147
|
+
# and those bytes must have written nothing the table names beyond what is
|
|
148
|
+
# being dropped here.
|
|
149
|
+
def note_register_clobbered(reg)
|
|
150
|
+
@resident.delete(reg)
|
|
151
|
+
@resident_at = @code.bytesize
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Records that the bytes just emitted disturbed nothing the table
|
|
155
|
+
# describes at all — a move *into* a promoted register, which no residency
|
|
156
|
+
# can name (see #note_register_clobbered for when this is sound).
|
|
157
|
+
def note_slots_undisturbed
|
|
158
|
+
@resident_at = @code.bytesize
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Forgets every claim on `vreg`'s slot in both files, for a store about to
|
|
162
|
+
# replace what that slot holds.
|
|
163
|
+
def drop_claims_on(vreg)
|
|
164
|
+
@resident.delete_if { |_reg, held| held == vreg } unless @resident.empty?
|
|
165
|
+
@vector_resident.delete_if { |_reg, held| held[0] == vreg } unless @vector_resident.empty?
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|