Class: Rubycc::Front::ConstantEvaluator

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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