Class: Rubycc::Front::ConstantEvaluator
- Inherits:
-
Object
- Object
- Rubycc::Front::ConstantEvaluator
- Defined in:
- lib/rubycc/front/constant_evaluator.rb
Overview
Evaluates a constant-expression (ISO C 6.6, "conditional-expression" with no assignment, increment/decrement, function call or comma reachable at run time) to a Ruby Integer. Given an expression AST node it walks the node straight away, with no dependency on the Parser or on any symbol table: every enum constant and typedef name the parser might have folded into the tree is already gone by the time this sees it (an enum reference is an ordinary IntLit, see AST::Program), so evaluating the AST alone is enough. A case label, an enumerator, a global initializer and an array bound all reduce their expression through this one evaluator; the preprocessor's "#if" (a later step) is meant to reuse it unchanged, which is why it never touches source positions beyond the token each node already carries.
M1 keeps the arithmetic itself simple: every intermediate result is an
unbounded Ruby Integer, and a declared type's width/signedness is only
brought to bear at a Cast (see #evaluate_cast) — ordinary arithmetic
never wraps the way a real unsigned int would overflow. No construct
this subset admits in a constant expression actually depends on that
wraparound yet, so the gap is left open rather than modelled.
Defined Under Namespace
Classes: DivisionByZero, NotConstant, OffsetofError, OffsetofTerm
Constant Summary collapse
- BINARY_OPERATIONS =
{ add: ->(a, b) { a + b }, sub: ->(a, b) { a - b }, mul: ->(a, b) { a * b }, and: ->(a, b) { a & b }, or: ->(a, b) { a | b }, xor: ->(a, b) { a ^ b }, shl: ->(a, b) { a << b }, # An arithmetic (sign-extending) right shift, which is exactly what # Ruby's Integer#>> already does for an arbitrary-precision value. shr: ->(a, b) { a >> b }, eq: ->(a, b) { a == b ? 1 : 0 }, ne: ->(a, b) { a == b ? 0 : 1 }, lt: ->(a, b) { a < b ? 1 : 0 }, le: ->(a, b) { a <= b ? 1 : 0 }, gt: ->(a, b) { a > b ? 1 : 0 }, ge: ->(a, b) { a >= b ? 1 : 0 } }.freeze
Class Method Summary collapse
- .evaluate(node, sizeof_expr: nil, pointer_int: nil) ⇒ Object
-
.offsetof_plan(node, sizeof_expr: nil, pointer_int: nil) ⇒ Object
Splits an AST::BuiltinOffsetof into [constant_offset, terms]: the byte offset every member step and constant-indexed subscript contributes, and an OffsetofTerm for each subscript whose index is not constant.
Instance Method Summary collapse
- #evaluate(node) ⇒ Object
-
#initialize(sizeof_expr: nil, pointer_int: nil) ⇒ ConstantEvaluator
constructor
sizeof_expr, when supplied, resolves asizeof <expression>operand (AST::SizeofExpr) to its byte size. -
#offsetof_plan(node) ⇒ Object
Walks a __builtin_offsetof designator once and reports it as [constant_offset, terms]: the bytes every member step and constant-indexed subscript contributes, plus one OffsetofTerm per subscript whose index is not a constant expression.
Constructor Details
#initialize(sizeof_expr: nil, pointer_int: nil) ⇒ ConstantEvaluator
sizeof_expr, when supplied, resolves a sizeof <expression> operand
(AST::SizeofExpr) to its byte size. The evaluator itself carries no
symbol table, so it cannot infer an expression's type on its own; a
caller that can (the IR generator, which knows every declaration's type)
passes a resolver here so a static initializer or other constant context
can fold "sizeof x". Without one, sizeof <expression> stays a
non-constant, as it is in a context with no type information (an array
bound folded during parsing).
pointer_int, when supplied, folds a pointer→integer cast whose operand
is an address constant of a load-time-known absolute value — the
"(size_t)&((T*)0)->member" offsetof idiom, whose value is the member's
byte offset. The address-constant machinery lives in the IR generator, so
a caller that has it (a static initializer) passes a resolver rather than
duplicating it here; without one such a cast stays a non-constant.
121 122 123 124 |
# File 'lib/rubycc/front/constant_evaluator.rb', line 121 def initialize(sizeof_expr: nil, pointer_int: nil) @sizeof_expr = sizeof_expr @pointer_int = pointer_int end |
Class Method Details
.evaluate(node, sizeof_expr: nil, pointer_int: nil) ⇒ Object
95 96 97 |
# File 'lib/rubycc/front/constant_evaluator.rb', line 95 def self.evaluate(node, sizeof_expr: nil, pointer_int: nil) new(sizeof_expr: sizeof_expr, pointer_int: pointer_int).evaluate(node) end |
.offsetof_plan(node, sizeof_expr: nil, pointer_int: nil) ⇒ Object
Splits an AST::BuiltinOffsetof into [constant_offset, terms]: the byte offset every member step and constant-indexed subscript contributes, and an OffsetofTerm for each subscript whose index is not constant. See #offsetof_plan.
103 104 105 |
# File 'lib/rubycc/front/constant_evaluator.rb', line 103 def self.offsetof_plan(node, sizeof_expr: nil, pointer_int: nil) new(sizeof_expr: sizeof_expr, pointer_int: pointer_int).offsetof_plan(node) end |
Instance Method Details
#evaluate(node) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/rubycc/front/constant_evaluator.rb', line 126 def evaluate(node) case node when AST::IntLit node.value when AST::Unary evaluate_unary(node) when AST::Binary evaluate_binary(node) when AST::LogicalAnd evaluate_logical_and(node) when AST::LogicalOr evaluate_logical_or(node) when AST::Conditional evaluate(node.condition).zero? ? evaluate(node.else_expr) : evaluate(node.then_expr) when AST::Cast evaluate_cast(node) when AST::SizeofExpr evaluate_sizeof_expr(node) when AST::SizeofType evaluate_sizeof_type(node) when AST::AlignofType evaluate_alignof_type(node) when AST::BuiltinOffsetof evaluate_builtin_offsetof(node) when AST::BuiltinConstantP evaluate_builtin_constant_p(node) when AST::BuiltinBitScan evaluate_builtin_bit_scan(node) else # Every other node — VariableRef, Call, Assignment, # CompoundAssignment, IncDec, MemberAccess, Subscript, StringLit, # Comma — is not a constant-expression here. raise NotConstant, node.token end end |
#offsetof_plan(node) ⇒ Object
Walks a __builtin_offsetof designator once and reports it as [constant_offset, terms]: the bytes every member step and constant-indexed subscript contributes, plus one OffsetofTerm per subscript whose index is not a constant expression.
gcc's __builtin_offsetof admits such a subscript and computes the offset at run time (measured: "offsetof(struct S, d)" with a variable n compiles and returns 8, 16, 32 for n = 0, 1, 3 on an 8-byte element), which ISO offsetof does not, so a caller that can emit code (the IR generator) takes the terms and lowers them while a caller that needs a constant (#evaluate_builtin_offsetof, and through it every array bound, case label and static initializer) rejects them.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/rubycc/front/constant_evaluator.rb', line 174 def offsetof_plan(node) type = node.type offset = 0 terms = [] node.designator.each do |step| case step when AST::OffsetofMember offset, type = offsetof_member_step(type, step, offset) when AST::OffsetofIndex offset, type = offsetof_index_step(type, step, offset, terms) end end [offset, terms] end |