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,631 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ast"
4
+
5
+ module Rubycc
6
+ module Front
7
+ # Evaluates a constant-expression (ISO C 6.6, "conditional-expression" with
8
+ # no assignment, increment/decrement, function call or comma reachable at
9
+ # run time) to a Ruby Integer. Given an expression AST node it walks the
10
+ # node straight away, with no dependency on the Parser or on any symbol
11
+ # table: every enum constant and typedef name the parser might have folded
12
+ # into the tree is already gone by the time this sees it (an enum
13
+ # reference is an ordinary IntLit, see AST::Program), so evaluating the
14
+ # AST alone is enough. A case label, an enumerator, a global initializer
15
+ # and an array bound all reduce their expression through this one
16
+ # evaluator; the preprocessor's "#if" (a later step) is meant to reuse it
17
+ # unchanged, which is why it never touches source positions beyond the
18
+ # token each node already carries.
19
+ #
20
+ # M1 keeps the arithmetic itself simple: every intermediate result is an
21
+ # unbounded Ruby Integer, and a declared type's width/signedness is only
22
+ # brought to bear at a Cast (see #evaluate_cast) — ordinary arithmetic
23
+ # never wraps the way a real `unsigned int` would overflow. No construct
24
+ # this subset admits in a constant expression actually depends on that
25
+ # wraparound yet, so the gap is left open rather than modelled.
26
+ class ConstantEvaluator
27
+ # Raised when some reachable part of the expression is not a
28
+ # constant-expression at all — a variable reference, a function call, an
29
+ # assignment, an increment/decrement, `sizeof` of an expression, or a
30
+ # comma. `token` is the offending node's token, for the caller to build a
31
+ # diagnostic naming its own context ("case label ...", "array size ...").
32
+ class NotConstant < StandardError
33
+ attr_reader :token
34
+
35
+ def initialize(token)
36
+ @token = token
37
+ super("expression is not a constant expression")
38
+ end
39
+ end
40
+
41
+ # Raised when a division or remainder operator that *is* reached at
42
+ # evaluation time (one on the taken side of "&&"/"||"/"?:" — the other
43
+ # side is never evaluated, so a zero divisor there raises nothing) has a
44
+ # zero right operand. `token` is the operator's token.
45
+ class DivisionByZero < StandardError
46
+ attr_reader :token
47
+
48
+ def initialize(token)
49
+ @token = token
50
+ super("division by zero in constant expression")
51
+ end
52
+ end
53
+
54
+ # Raised when a __builtin_offsetof designator cannot name a byte offset:
55
+ # its type is not a struct/union or is incomplete, a step names no such
56
+ # member, a subscript step applies to a non-array member, or the target is
57
+ # a bit-field (which has no addressable byte offset). It is a NotConstant
58
+ # so every context that already reports a non-constant expression catches
59
+ # it too; `detail` carries the specific wording for a caller (the
60
+ # generator) that can surface it, over NotConstant's generic message.
61
+ class OffsetofError < NotConstant
62
+ attr_reader :detail
63
+
64
+ def initialize(token, detail)
65
+ @detail = detail
66
+ super(token)
67
+ end
68
+ end
69
+
70
+ BINARY_OPERATIONS = {
71
+ add: ->(a, b) { a + b },
72
+ sub: ->(a, b) { a - b },
73
+ mul: ->(a, b) { a * b },
74
+ and: ->(a, b) { a & b },
75
+ or: ->(a, b) { a | b },
76
+ xor: ->(a, b) { a ^ b },
77
+ shl: ->(a, b) { a << b },
78
+ # An arithmetic (sign-extending) right shift, which is exactly what
79
+ # Ruby's Integer#>> already does for an arbitrary-precision value.
80
+ shr: ->(a, b) { a >> b },
81
+ eq: ->(a, b) { a == b ? 1 : 0 },
82
+ ne: ->(a, b) { a == b ? 0 : 1 },
83
+ lt: ->(a, b) { a < b ? 1 : 0 },
84
+ le: ->(a, b) { a <= b ? 1 : 0 },
85
+ gt: ->(a, b) { a > b ? 1 : 0 },
86
+ ge: ->(a, b) { a >= b ? 1 : 0 }
87
+ }.freeze
88
+
89
+ # One subscript step of a __builtin_offsetof designator whose index is not
90
+ # a constant expression: its contribution to the offset is the value of
91
+ # `index` (an expression node the caller lowers) times `scale`, the
92
+ # element's byte size.
93
+ OffsetofTerm = Data.define(:index, :scale)
94
+
95
+ def self.evaluate(node, sizeof_expr: nil, pointer_int: nil)
96
+ new(sizeof_expr: sizeof_expr, pointer_int: pointer_int).evaluate(node)
97
+ end
98
+
99
+ # Splits an AST::BuiltinOffsetof into [constant_offset, terms]: the byte
100
+ # offset every member step and constant-indexed subscript contributes, and
101
+ # an OffsetofTerm for each subscript whose index is not constant. See
102
+ # #offsetof_plan.
103
+ def self.offsetof_plan(node, sizeof_expr: nil, pointer_int: nil)
104
+ new(sizeof_expr: sizeof_expr, pointer_int: pointer_int).offsetof_plan(node)
105
+ end
106
+
107
+ # `sizeof_expr`, when supplied, resolves a `sizeof <expression>` operand
108
+ # (AST::SizeofExpr) to its byte size. The evaluator itself carries no
109
+ # symbol table, so it cannot infer an expression's type on its own; a
110
+ # caller that can (the IR generator, which knows every declaration's type)
111
+ # passes a resolver here so a static initializer or other constant context
112
+ # can fold "sizeof x". Without one, `sizeof <expression>` stays a
113
+ # non-constant, as it is in a context with no type information (an array
114
+ # bound folded during parsing).
115
+ # `pointer_int`, when supplied, folds a pointer→integer cast whose operand
116
+ # is an address constant of a load-time-known absolute value — the
117
+ # "(size_t)&((T*)0)->member" offsetof idiom, whose value is the member's
118
+ # byte offset. The address-constant machinery lives in the IR generator, so
119
+ # a caller that has it (a static initializer) passes a resolver rather than
120
+ # duplicating it here; without one such a cast stays a non-constant.
121
+ def initialize(sizeof_expr: nil, pointer_int: nil)
122
+ @sizeof_expr = sizeof_expr
123
+ @pointer_int = pointer_int
124
+ end
125
+
126
+ def evaluate(node)
127
+ case node
128
+ when AST::IntLit
129
+ node.value
130
+ when AST::Unary
131
+ evaluate_unary(node)
132
+ when AST::Binary
133
+ evaluate_binary(node)
134
+ when AST::LogicalAnd
135
+ evaluate_logical_and(node)
136
+ when AST::LogicalOr
137
+ evaluate_logical_or(node)
138
+ when AST::Conditional
139
+ evaluate(node.condition).zero? ? evaluate(node.else_expr) : evaluate(node.then_expr)
140
+ when AST::Cast
141
+ evaluate_cast(node)
142
+ when AST::SizeofExpr
143
+ evaluate_sizeof_expr(node)
144
+ when AST::SizeofType
145
+ evaluate_sizeof_type(node)
146
+ when AST::AlignofType
147
+ evaluate_alignof_type(node)
148
+ when AST::BuiltinOffsetof
149
+ evaluate_builtin_offsetof(node)
150
+ when AST::BuiltinConstantP
151
+ evaluate_builtin_constant_p(node)
152
+ when AST::BuiltinBitScan
153
+ evaluate_builtin_bit_scan(node)
154
+ else
155
+ # Every other node — VariableRef, Call, Assignment,
156
+ # CompoundAssignment, IncDec, MemberAccess, Subscript, StringLit,
157
+ # Comma — is not a constant-expression here.
158
+ raise NotConstant, node.token
159
+ end
160
+ end
161
+
162
+ # Walks a __builtin_offsetof designator once and reports it as
163
+ # [constant_offset, terms]: the bytes every member step and
164
+ # constant-indexed subscript contributes, plus one OffsetofTerm per
165
+ # subscript whose index is not a constant expression.
166
+ #
167
+ # gcc's __builtin_offsetof admits such a subscript and computes the offset
168
+ # at run time (measured: "offsetof(struct S, d[n])" with a variable n
169
+ # compiles and returns 8, 16, 32 for n = 0, 1, 3 on an 8-byte element),
170
+ # which ISO offsetof does not, so a caller that can emit code (the IR
171
+ # generator) takes the terms and lowers them while a caller that needs a
172
+ # constant (#evaluate_builtin_offsetof, and through it every array bound,
173
+ # case label and static initializer) rejects them.
174
+ def offsetof_plan(node)
175
+ type = node.type
176
+ offset = 0
177
+ terms = []
178
+ node.designator.each do |step|
179
+ case step
180
+ when AST::OffsetofMember
181
+ offset, type = offsetof_member_step(type, step, offset)
182
+ when AST::OffsetofIndex
183
+ offset, type = offsetof_index_step(type, step, offset, terms)
184
+ end
185
+ end
186
+ [offset, terms]
187
+ end
188
+
189
+ private
190
+
191
+ def evaluate_unary(node)
192
+ case node.op
193
+ when :neg then -evaluate(node.operand)
194
+ when :not then evaluate(node.operand).zero? ? 1 : 0
195
+ else raise NotConstant, node.token # :addr, :deref
196
+ end
197
+ end
198
+
199
+ def evaluate_binary(node)
200
+ return evaluate_division(node) if node.op == :div || node.op == :mod
201
+ return evaluate_pointer_subtraction(node) if node.op == :sub
202
+
203
+ operation = BINARY_OPERATIONS[node.op]
204
+ raise NotConstant, node.token unless operation
205
+
206
+ operation.call(evaluate(node.lhs), evaluate(node.rhs))
207
+ end
208
+
209
+ # Subtraction is ordinary integer arithmetic unless that fails and both
210
+ # operands are addresses this evaluator can place on its own (see
211
+ # #pointer_target) — the traditional "((size_t)(char *)&((t *)0)->m -
212
+ # (char *)0)" offsetof idiom, and its relatives with any pointed-to type,
213
+ # not only "char *". C's pointer-difference rule (6.5.6p9) then applies:
214
+ # the byte difference is divided by the shared pointed-to type's size,
215
+ # which must be identical on both sides and have one ("void *" and an
216
+ # incomplete type do not) for the division to mean anything. When
217
+ # #pointer_difference cannot place the difference this way it returns
218
+ # nil, and the original NotConstant from the ordinary attempt is what
219
+ # reaches the caller instead.
220
+ def evaluate_pointer_subtraction(node)
221
+ BINARY_OPERATIONS[:sub].call(evaluate(node.lhs), evaluate(node.rhs))
222
+ rescue NotConstant
223
+ difference = pointer_difference(node)
224
+ raise if difference.nil?
225
+
226
+ difference
227
+ end
228
+
229
+ # The [byte-difference / pointed-to-size] quotient of "lhs - rhs" when
230
+ # both sides are addresses #pointer_target can place, or nil when either
231
+ # side is not one or the two pointed-to types do not share a size. A
232
+ # non-zero remainder never happens for two addresses this evaluator
233
+ # itself derived from the same struct layout, but is still checked
234
+ # rather than silently truncated, surfacing as an (unfolded) NotConstant.
235
+ def pointer_difference(node)
236
+ lhs = pointer_target(node.lhs)
237
+ return nil if lhs.nil?
238
+
239
+ rhs = pointer_target(node.rhs)
240
+ return nil if rhs.nil?
241
+
242
+ lhs_address, lhs_type = lhs
243
+ rhs_address, rhs_type = rhs
244
+ return nil unless sized?(lhs_type) && sized?(rhs_type) && lhs_type.size == rhs_type.size
245
+
246
+ byte_difference = lhs_address - rhs_address
247
+ raise NotConstant, node.token unless (byte_difference % lhs_type.size).zero?
248
+
249
+ byte_difference / lhs_type.size
250
+ end
251
+
252
+ # Division and remainder truncate toward zero (6.5.5p6), unlike Ruby's
253
+ # own "/" and "%", which floor. -7 / 2 is -3, not -4, and -7 % 2 is -1,
254
+ # not 1.
255
+ def evaluate_division(node)
256
+ lhs = evaluate(node.lhs)
257
+ rhs = evaluate(node.rhs)
258
+ raise DivisionByZero, node.token if rhs.zero?
259
+
260
+ quotient = lhs.abs / rhs.abs
261
+ quotient = -quotient if (lhs.negative?) != (rhs.negative?)
262
+ node.op == :div ? quotient : lhs - (quotient * rhs)
263
+ end
264
+
265
+ # Short-circuits exactly like the run-time operator: the right operand
266
+ # is not evaluated (and so cannot raise DivisionByZero) once the left
267
+ # settles the result on its own.
268
+ def evaluate_logical_and(node)
269
+ return 0 if evaluate(node.lhs).zero?
270
+
271
+ evaluate(node.rhs).zero? ? 0 : 1
272
+ end
273
+
274
+ def evaluate_logical_or(node)
275
+ return 1 unless evaluate(node.lhs).zero?
276
+
277
+ evaluate(node.rhs).zero? ? 0 : 1
278
+ end
279
+
280
+ # A cast to an integer type wraps the operand's value into that type's
281
+ # width and signedness; any other destination (a pointer, a struct) is
282
+ # not something this evaluator folds. When the operand is itself a
283
+ # floating-point constant (a literal, or a negated literal — a nested
284
+ # cast such as "(long)(double)1e2" is not chased any further), 6.3.1.4p1
285
+ # truncates it toward zero before the wrap; #evaluate never sees a Float
286
+ # for any other node, so the arithmetic above stays purely integral.
287
+ def evaluate_cast(node)
288
+ raise NotConstant, node.token unless node.type.integer?
289
+
290
+ float_value = float_constant_value(node.operand)
291
+ return wrap_to_type(float_value.truncate, node.type) unless float_value.nil?
292
+
293
+ wrap_to_type(evaluate_integer_or_address(node.operand), node.type)
294
+ end
295
+
296
+ # The integer value of a cast-to-integer operand: an integer constant
297
+ # expression normally, but a pointer→integer cast has a pointer operand the
298
+ # evaluator does not fold as an ordinary expression. One shape it folds
299
+ # itself is the traditional "(size_t)&((T*)0)->m" offsetof idiom, whose
300
+ # address is a member offset from a constant base and so needs nothing but
301
+ # the struct layout the evaluator already reads (see
302
+ # #absolute_pointer_value). When that does not apply and a @pointer_int
303
+ # resolver is supplied, the operand is offered there instead; the resolver
304
+ # returns the pointer's absolute integer value or re-raises NotConstant.
305
+ def evaluate_integer_or_address(operand)
306
+ evaluate(operand)
307
+ rescue NotConstant
308
+ absolute = absolute_pointer_value(operand)
309
+ return absolute unless absolute.nil?
310
+ raise unless @pointer_int
311
+
312
+ @pointer_int.call(operand)
313
+ end
314
+
315
+ # The absolute integer value of a pointer-valued operand this evaluator can
316
+ # place on its own — "&((T *)N)->m" and its relatives, where the address-of
317
+ # applies to a designator rooted at a pointer cast of an integer constant,
318
+ # or such a cast written on its own. That is the offsetof idiom a header
319
+ # writes as "((size_t)&((T *)0)->m)", whose value is the member's byte
320
+ # offset; folding it here rather than through a resolver makes it a
321
+ # constant in every context, the parser's (a _Static_assert, an enumerator,
322
+ # a bit-field width, an array bound) as much as the generator's. Anything
323
+ # else is nil, an address only known at link time (a global's) included,
324
+ # which the @pointer_int resolver still handles.
325
+ def absolute_pointer_value(node)
326
+ case node
327
+ when AST::Unary
328
+ return nil unless node.op == :addr
329
+
330
+ address, = designator_address(node.operand)
331
+ address
332
+ when AST::Cast
333
+ pointer_target(node)&.first
334
+ end
335
+ end
336
+
337
+ # The [absolute address, pointed-to type] a pointer-valued expression
338
+ # denotes when its value is a compile-time constant: a cast to pointer type
339
+ # over an integer constant (or over another such pointer, as
340
+ # "(T *)(void *)0" writes it), or the address of a designator #designator_address
341
+ # can place. An array-typed operand never reaches here — the idiom's base is
342
+ # always a cast — so no array-to-pointer decay is modelled.
343
+ def pointer_target(node)
344
+ case node
345
+ when AST::Cast
346
+ return nil unless node.type.pointer?
347
+
348
+ address = constant_integer(node.operand) || pointer_target(node.operand)&.first
349
+ address.nil? ? nil : [address, node.type.target]
350
+ when AST::Unary
351
+ return nil unless node.op == :addr
352
+
353
+ designator_address(node.operand)
354
+ when AST::Binary
355
+ pointer_offset_target(node)
356
+ end
357
+ end
358
+
359
+ # "pointer + integer" and "pointer - integer" (6.5.6p8) over a pointer
360
+ # #pointer_target already places: the address moves by the integer
361
+ # operand times the pointed-to type's size, on whichever side of "+" the
362
+ # pointer operand is ("p + 1" and "1 + p" are both valid), or only the
363
+ # left side of "-" ("1 - p" is not a pointer expression at all). nil when
364
+ # neither operand is a foldable pointer, the other is not an integer
365
+ # constant, or the pointed-to type has no size to stride by.
366
+ def pointer_offset_target(node)
367
+ return nil unless node.op == :add || node.op == :sub
368
+
369
+ lhs_pointer = pointer_target(node.lhs)
370
+ if lhs_pointer
371
+ offset = constant_integer(node.rhs)
372
+ return nil if offset.nil?
373
+
374
+ return pointer_advance(lhs_pointer, node.op == :sub ? -offset : offset)
375
+ end
376
+
377
+ return nil if node.op == :sub
378
+
379
+ rhs_pointer = pointer_target(node.rhs)
380
+ return nil if rhs_pointer.nil?
381
+
382
+ offset = constant_integer(node.lhs)
383
+ return nil if offset.nil?
384
+
385
+ pointer_advance(rhs_pointer, offset)
386
+ end
387
+
388
+ # [address + offset * pointed-to size, pointed-to type], or nil when the
389
+ # pointed-to type has no size to stride by (void, a function, an
390
+ # incomplete aggregate or array).
391
+ def pointer_advance(pointer, offset)
392
+ address, type = pointer
393
+ return nil unless sized?(type)
394
+
395
+ [address + (offset * type.size), type]
396
+ end
397
+
398
+ # The [absolute address, type] of a designator whose base is a pointer of
399
+ # constant value: "*(T *)N" is [N, T], a member access adds the member's
400
+ # offset (6.7.2.1's layout, the same one #offsetof_member_step walks) and a
401
+ # subscript adds the index times the element size. nil for every other
402
+ # designator — one rooted at a named object, or at a pointer whose value is
403
+ # not constant.
404
+ #
405
+ # Unlike the __builtin_offsetof steps this mirrors, a step that names no
406
+ # byte offset (an unknown member, a bit-field, a subscript of a non-array)
407
+ # yields nil rather than an OffsetofError: the expression is an ordinary
408
+ # one the surrounding context still type-checks, so leaving it unfolded
409
+ # keeps that context's own diagnostic instead of pre-empting it here.
410
+ def designator_address(node)
411
+ case node
412
+ when AST::Unary
413
+ node.op == :deref ? pointer_target(node.operand) : nil
414
+ when AST::MemberAccess
415
+ member_designator_address(node)
416
+ when AST::Subscript
417
+ subscript_designator_address(node)
418
+ end
419
+ end
420
+
421
+ # "base->m" over a constant pointer, or "base.m" over a designator already
422
+ # placed, adds the member's offset to the base address.
423
+ def member_designator_address(node)
424
+ base = node.arrow ? pointer_target(node.base) : designator_address(node.base)
425
+ return nil if base.nil?
426
+
427
+ address, type = base
428
+ return nil unless type.struct? && type.complete?
429
+
430
+ member = type.member(node.member)
431
+ return nil if member.nil? || member.bitfield?
432
+
433
+ [address + member.offset, member.type]
434
+ end
435
+
436
+ # "base[i]" strides by the element size: over a constant pointer ("((T *)N)[i]")
437
+ # the element is what it points to, over an array designator
438
+ # ("((T *)0)->a[i]") it is the array's element type. A non-constant index,
439
+ # or an element with no size, leaves the address unfolded.
440
+ def subscript_designator_address(node)
441
+ index = constant_integer(node.index)
442
+ return nil if index.nil?
443
+
444
+ address, element = subscript_base(node.target)
445
+ return nil if address.nil? || !sized?(element)
446
+
447
+ [address + (index * element.size), element]
448
+ end
449
+
450
+ # Whether `type` has a byte size a subscript can stride by — every type
451
+ # but void, a function, an incomplete aggregate and an unbounded array.
452
+ def sized?(type)
453
+ return false if type.void? || type.function? || incomplete_aggregate?(type)
454
+
455
+ !(type.array? && type.incomplete?)
456
+ end
457
+
458
+ # The [base address, element type] a subscript strides over, or nil when
459
+ # neither form applies.
460
+ def subscript_base(target)
461
+ pointer = pointer_target(target)
462
+ return pointer if pointer
463
+
464
+ address, type = designator_address(target)
465
+ address.nil? || !type.array? ? nil : [address, type.element]
466
+ end
467
+
468
+ # `node`'s value as an integer constant, or nil when it is not one. Used
469
+ # where a non-constant sub-expression only means the surrounding address
470
+ # stays unfolded, rather than being a failure to report.
471
+ def constant_integer(node)
472
+ evaluate(node)
473
+ rescue NotConstant, DivisionByZero
474
+ nil
475
+ end
476
+
477
+ # The Float a floating-point constant operand denotes, or nil when
478
+ # `node` is not one — a plain FloatLit, or a unary minus directly over
479
+ # one.
480
+ def float_constant_value(node)
481
+ case node
482
+ when AST::FloatLit
483
+ node.value
484
+ when AST::Unary
485
+ return nil unless node.op == :neg && node.operand.is_a?(AST::FloatLit)
486
+
487
+ -node.operand.value
488
+ end
489
+ end
490
+
491
+ def wrap_to_type(value, type)
492
+ return value.zero? ? 0 : 1 if type.bool?
493
+
494
+ bits = type.size * 8
495
+ wrapped = value & ((1 << bits) - 1)
496
+ return wrapped if type.unsigned? || wrapped < (1 << (bits - 1))
497
+
498
+ wrapped - (1 << bits)
499
+ end
500
+
501
+ # sizeof(expression) folds to the byte size of the operand's type, which
502
+ # only a caller carrying type information can supply (see #initialize): the
503
+ # resolver returns the size, applying the same "no size" rejections
504
+ # sizeof(type-name) does. Without a resolver it is a non-constant, so a
505
+ # context with no type table (an array bound folded while parsing) reports
506
+ # "not an integer constant" rather than a wrong value.
507
+ def evaluate_sizeof_expr(node)
508
+ raise NotConstant, node.token unless @sizeof_expr
509
+
510
+ @sizeof_expr.call(node)
511
+ end
512
+
513
+ # sizeof(type-name) folds to the type's byte size; an incomplete type
514
+ # (void, a struct/union never completed, or a forward-referenced enum) has
515
+ # none to fold.
516
+ def evaluate_sizeof_type(node)
517
+ type = node.type
518
+ raise NotConstant, node.token if type.void? || incomplete_aggregate?(type)
519
+
520
+ type.size
521
+ end
522
+
523
+ # _Alignof(type-name) folds to the type's alignment; a void, function or
524
+ # incomplete type has no alignment to fold, matching what the generator
525
+ # rejects for the same construct.
526
+ def evaluate_alignof_type(node)
527
+ type = node.type
528
+ if type.void? || type.function? || incomplete_aggregate?(type)
529
+ raise NotConstant, node.token
530
+ end
531
+
532
+ type.alignment
533
+ end
534
+
535
+ # __builtin_offsetof(type-name, member-designator) folds to the byte offset
536
+ # of the designated member, walking the designator one step at a time from
537
+ # the aggregate type. A member step adds the member's offset and descends
538
+ # into its type; a subscript step (over an array member) adds the index
539
+ # times the element size and descends into the element type. Every failure
540
+ # — a non-aggregate or incomplete type, a missing member, a subscript of a
541
+ # non-array, or a bit-field target with no addressable offset — is an
542
+ # OffsetofError carrying the diagnostic wording. The member lookup goes
543
+ # through Type::StructType#member, so an anonymous struct/union member is
544
+ # traversed transparently with its own offset already folded in.
545
+ def evaluate_builtin_offsetof(node)
546
+ offset, terms = offsetof_plan(node)
547
+ # A constant context has no place to evaluate a run-time index, so a
548
+ # deferred term is simply the non-constant expression it came from.
549
+ raise NotConstant, terms.first.index.token unless terms.empty?
550
+
551
+ offset
552
+ end
553
+
554
+ def offsetof_member_step(type, step, offset)
555
+ unless type.struct?
556
+ raise OffsetofError.new(step.token,
557
+ "request for member '#{step.name}' in something not a structure or union")
558
+ end
559
+ unless type.complete?
560
+ raise OffsetofError.new(step.token, "offsetof of incomplete type '#{type}'")
561
+ end
562
+
563
+ member = type.member(step.name)
564
+ raise OffsetofError.new(step.token, "no member named '#{step.name}' in '#{type}'") if member.nil?
565
+ if member.bitfield?
566
+ raise OffsetofError.new(step.token, "attempt to get the offset of a bit-field member '#{step.name}'")
567
+ end
568
+
569
+ [offset + member.offset, member.type]
570
+ end
571
+
572
+ # One "[i]" step. A constant index folds straight into the running offset;
573
+ # a non-constant one is deferred to `terms` for a caller that can lower it
574
+ # (an OffsetofError from a nested designator is a real diagnostic, not a
575
+ # deferrable index, so it passes through).
576
+ def offsetof_index_step(type, step, offset, terms)
577
+ unless type.array?
578
+ raise OffsetofError.new(step.token, "subscripted value in offsetof is not an array")
579
+ end
580
+
581
+ element = type.element
582
+ begin
583
+ [offset + evaluate(step.index) * element.size, element]
584
+ rescue OffsetofError
585
+ raise
586
+ rescue NotConstant
587
+ terms << OffsetofTerm.new(index: step.index, scale: element.size)
588
+ [offset, element]
589
+ end
590
+ end
591
+
592
+ # __builtin_constant_p(expr) folds to 1 when its operand is itself a
593
+ # constant-expression and 0 otherwise. The operand is probed by trying to
594
+ # evaluate it: success means it reduced to a constant (so 1), while a
595
+ # NotConstant (a variable, call, ...) or a DivisionByZero means it did not
596
+ # (so 0). It never propagates the failure — unlike an ordinary
597
+ # sub-expression, a non-constant operand here is a legitimate 0, not an
598
+ # error — so the probe swallows both. The operand is evaluated only to test
599
+ # foldability; the caller discards the value.
600
+ def evaluate_builtin_constant_p(node)
601
+ evaluate(node.expr)
602
+ 1
603
+ rescue NotConstant, DivisionByZero
604
+ 0
605
+ end
606
+
607
+ # __builtin_ctz/clz(x) folds to the trailing/leading zero-bit count of a
608
+ # constant operand, over its `width`-byte value, matching gcc so the same
609
+ # fold serves a HAVE_BUILTIN___BUILTIN_CLZLL-guarded constant context. A
610
+ # zero operand is undefined behavior (gcc), so it is left unfolded (a
611
+ # NotConstant) rather than given an arbitrary value.
612
+ def evaluate_builtin_bit_scan(node)
613
+ bits = node.width * 8
614
+ value = evaluate(node.operand) & ((1 << bits) - 1)
615
+ raise NotConstant, node.token if value.zero?
616
+
617
+ if node.direction == :forward
618
+ (value & -value).bit_length - 1 # trailing zero count
619
+ else
620
+ bits - value.bit_length # leading zero count
621
+ end
622
+ end
623
+
624
+ # Whether `type` is an incomplete tagged type with no size or alignment: a
625
+ # struct/union never completed, or an incomplete (forward-referenced) enum.
626
+ def incomplete_aggregate?(type)
627
+ (type.struct? && !type.complete?) || type.is_a?(Rubycc::Type::EnumType)
628
+ end
629
+ end
630
+ end
631
+ end