Class: Rubycc::IR::Generator

Inherits:
Object
  • Object
show all
Defined in:
lib/rubycc/ir/generator.rb

Overview

Lowers the AST into IR. A straightforward post-order walk that allocates a fresh virtual register for every computed value, tracking each expression's static type so pointer operations can be type-checked and lowered. No optimization.

Defined Under Namespace

Classes: AddressConstant, Local, NotAddressConstant, ObjectRecord

Constant Summary collapse

PAD_GP_PIECE =

The two alignment-pad ABI pieces (see AAPCS64::Placer#pad_gp/#pad_stack). A pad reserves one integer register (:pad) or one stack eightbyte (:pad_stack) ahead of a 16-byte-aligned aggregate so it starts on an aligned boundary; it moves no data, so its offset and size go unread — only its kind, which steers the backend's counter over it.

AbiPiece.new(offset: 0, size: 8, kind: :pad)
PAD_STACK_PIECE =
AbiPiece.new(offset: 0, size: 8, kind: :pad_stack)
STACK_OBJECT_ALIGNMENT =

The boundaries an automatic object is guaranteed to land on. Both backends build the frame from a 16-byte-aligned base and then place each stack object (an aggregate or a 128-bit integer) a 16-byte-rounded distance from it, while a scalar lives in one cell of the 8-byte virtual-register run. An _Alignas asking for more than that would need a prologue that realigns the stack pointer at run time, which neither backend emits, so #reject_overaligned_automatic refuses the declaration rather than letting it compile to a weaker boundary than it asked for.

16
VREG_SLOT_ALIGNMENT =
8

Instance Method Summary collapse

Constructor Details

#initialize(plain_char: Type::Char, convention: CallConvention::SYSTEM_V_AMD64) ⇒ Generator

plain_char is the Rubycc::Type of a plain char on the target being generated for, matching what the parser resolved the char specifier to. The generator needs it because a string literal's type is written here rather than by the parser: its element type is plain char (6.4.5p6), so a byte read out of one sign- or zero-extends following the target's plain-char signedness. It defaults to the signed instance, the x86-64 System V choice, for a caller with no target in hand.

convention is the target's CallConvention, which fixes how many registers an argument list may draw on before it spills to the stack. It defaults to System V AMD64 for the same reason plain_char does.



89
90
91
92
# File 'lib/rubycc/ir/generator.rb', line 89

def initialize(plain_char: Type::Char, convention: CallConvention::SYSTEM_V_AMD64)
  @plain_char = plain_char
  @convention = convention
end

Instance Method Details

#generate(program, pic: false) ⇒ Object

Returns an IR::Program: an IR::Function per AST::FunctionDef plus the translation unit's read-only string pool. Prototypes (AST::FunctionDecl) contribute only a signature-table entry and emit no code. The table is filled in source order so a definition can reference itself (recursion) or an earlier prototype (mutual recursion), while a call to a still-unknown name is diagnosed as an implicit declaration.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/rubycc/ir/generator.rb', line 100

def generate(program, pic: false)
  # Position-independent code mode (-fPIC): when set, a reference that takes
  # the address of a file-scope object or function this translation unit
  # does not itself define is lowered through the Global Offset Table
  # (:got_addr) instead of a PC-relative :global_addr / :func_addr, so a
  # definition in another shared object can interpose on it. A symbol
  # defined here, a `static`, and a string literal keep the PC-relative
  # form. When false the lowering is byte-for-byte the non-PIC one.
  @pic = pic
  # name -> { param_types:, return_type:, variadic:, defined: }.
  # `param_types` is the array of parameter Rubycc::Types (its length being
  # the fixed arity — for a variadic function, only the named parameters);
  # `return_type` is the declared Rubycc::Type of a call to this function;
  # `variadic` is true for a "..."-terminated prototype (its calls admit
  # extra, promoted arguments past the fixed ones); `defined` distinguishes
  # a prototype from a completed definition so redefinitions can be
  # rejected.
  @signatures = {}
  # gcc provides memcpy as a builtin, and the parser rewrites
  # __builtin_memcpy(...) into a plain call to "memcpy". Seed its prototype
  # up front — void *memcpy(void *, const void *, unsigned long) — so such a
  # call compiles even when the translation unit never declares memcpy (no
  # <string.h>); a later, identical string.h prototype merges in without
  # conflict, and the reference resolves to libc's memcpy at link time.
  @signatures["memcpy"] = {
    param_types: [Type::Pointer.new(Type::Void), Type::Pointer.new(Type::Void), Type::ULong],
    return_type: Type::Pointer.new(Type::Void),
    variadic: false,
    defined: false
  }
  # The translation-unit-wide string pool: `@strings` holds each interned
  # byte string in id order, `@string_ids` maps content back to its id so
  # identical literals collapse to one entry (and one .rodata address).
  @strings = []
  @string_ids = {}
  # File-scope variables: `@global_bindings` maps each name to its Local
  # binding (the outermost scope every function shares), while `@globals`
  # holds the IR::Global descriptors in source order for the compiler to
  # lay out into .data/.bss.
  @global_bindings = {}
  @globals = []
  # File-scope objects that reserve storage, keyed by name: each ObjectRecord
  # tracks the merged state of a run of tentative/real definitions (6.9.2) —
  # its type, linkage, whether any declaration has initialized it, and the
  # index of its single IR::Global entry. A bare `extern` reference reserves
  # nothing and gets no record. A repeated declaration merges into the
  # record (types must agree); a second *initialized* definition is the real
  # redefinition error, and an object emitted tentatively in .bss is upgraded
  # in place to .data when a later declaration supplies an initializer.
  @object_records = {}
  # A monotonic counter that names each block-scope `static` uniquely as
  # "<var>.<n>". A '.' cannot appear in a C identifier, so these names
  # never collide with a real symbol; the counter runs over the whole
  # translation unit in source order, keeping the output deterministic (N4).
  @static_local_count = 0
  ir_functions = []
  # Declarations are processed in source order, so a function may only
  # reference a global or callee already declared above it (C's
  # declaration-before-use rule), and a name reused across the global and
  # function namespaces is rejected as a redefinition.
  program.functions.each do |decl|
    case decl
    when Front::AST::GlobalDecl
      declare_global(decl)
    when Front::AST::FunctionDecl
      # A prototype's storage class (`static`/`extern`) is recorded on the
      # AST but drives no behavior here: a declaration reserves nothing and
      # M1 does not diagnose a static/extern mismatch against the eventual
      # definition, so a prototype only contributes a signature.
      declare_function(decl.name, decl.return_type, decl.params.map(&:abi_type),
                       variadic: decl.variadic, defined: false, token: decl.token)
    when Front::AST::FunctionDef
      # A signature is what *callers* must agree with, so it is built from
      # the types the parameters are passed as. The two differ only for an
      # old-style definition, whose narrow parameters arrive promoted
      # (see AST::Parameter#abi_type).
      declare_function(decl.name, decl.return_type, decl.params.map(&:abi_type),
                       variadic: decl.variadic, defined: true, token: decl.token)
      # `static` gives the definition internal linkage (an STB_LOCAL text
      # symbol); an absent or `extern` specifier leaves it external.
      linkage = decl.storage == :static ? :internal : :external
      ir_functions << gen_function(decl, linkage)
    end
  end
  Program.new(ir_functions, @strings, @globals, array_entries(program, ir_functions),
              program.visibility_attributes)
end