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,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