Skip to content
← Back

src/ir/emitter/srm_method_body_emitter.ghul

1
namespace IR.Emitter is
2
use System.Reflection.Metadata.BlobBuilder
3
use System.Reflection.Metadata.EntityHandle
4
use System.Reflection.Metadata.Ecma335.InstructionEncoder
5
use System.Reflection.Metadata.Ecma335.ControlFlowBuilder
6
use System.Reflection.Metadata.Ecma335.LabelHandle
7
use System.Reflection.Metadata.ILOpCode
8
9
use Semantic.Types.Type
10
11
// Instruction-level surface for one method body. Backed by SRM's
12
// InstructionEncoder; `flush` hands the encoded body to the owning
13
// assembly emitter's method body stream and returns the body offset
14
// that AddMethodDefinition takes directly (not a handle).
15
//
16
// Locals are addressed by name here even though the encoded
17
// instructions address them by slot index. Names like `result.0`
18
// are invented all over generate-il and the IR values, so taking
19
// them as given keeps the slot numbering in one place rather than
20
// threading it through every site that declares a local.
21
class SRM_METHOD_BODY_EMITTER is
22
_encoder: InstructionEncoder
23
24
_local_slots: Collections.MAP[string, int]
25
_local_types: Collections.LIST[Type]
26
27
// IR label to the encoder's own label. A branch can name a
28
// label the walk has not reached yet - every forward jump does,
29
// and a loop's exit is always one - so the handle is defined on
30
// first mention from either side and the encoder patches the
31
// offsets in when the body is serialized.
32
//
33
// Keyed on the label's rendered name, which carries its pass as
34
// well as its id. The id counter restarts per pass and per
35
// class, so compile-expressions and generate-il both issue an id
36
// 0; keying on the id alone merges the two into one handle, and
37
// a branch then lands wherever the other pass's label was
38
// marked.
39
_labels: Collections.MAP[string, LabelHandle]
40
41
// Protected-region boundaries. SRM takes four labels per
42
// region rather than a nesting, so each boundary has to be
43
// marked as the walk passes it and held until both ends are
44
// known. These labels have no
45
// IR.LABEL behind them - nothing branches to them - so they
46
// are numbered here rather than sharing `_labels`.
47
_region_labels: Collections.LIST[LabelHandle]
48
49
// Source positions for this body, in emission order, or null in
50
// a build that was not asked for debug information.
51
_sequence_points: Collections.LIST[SRM_SEQUENCE_POINT]?
52
53
// Open `@IL.output` regions awaiting their end offset (a stack so
54
// nested regions close innermost-first), and the completed ranges.
55
_il_output_stack: Collections.LIST[(string, int, int)]
56
_il_outputs: Collections.LIST[SRM_IL_OUTPUT_RANGE]?
57
58
init() is
59
_encoder = InstructionEncoder(BlobBuilder(32), ControlFlowBuilder())
60
61
_local_slots = Collections.MAP[string, int]()
62
_local_types = Collections.LIST[Type]()
63
_labels = Collections.MAP[string, LabelHandle]()
64
_region_labels = Collections.LIST[LabelHandle]()
65
_il_output_stack = Collections.LIST[(string, int, int)]()
66
si
67
68
// Marks the current position and returns a handle on it that
69
// `add_catch_region` / `add_finally_region` take. An int rather
70
// than the SRM label so the walk that decides where regions
71
// begin and end needs no metadata types of its own.
72
mark_region_boundary() -> int is
73
let handle = _encoder.define_label()
74
75
_encoder.mark_label(handle)
76
77
_region_labels.add(handle)
78
79
return _region_labels.count - 1
80
si
81
82
// Regions must be added innermost first: ECMA-335 requires the
83
// ordering and the runtime relies on it, but nothing validates
84
// it, so getting it wrong yields an assembly that loads and
85
// then runs the wrong handler. Each `try` adds its own region
86
// as it closes, which puts an inner one ahead of the outer one
87
// that contains it.
88
add_catch_region(
89
try_start: int,
90
try_end: int,
91
handler_start: int,
92
handler_end: int,
93
catch_type: EntityHandle
94
) is
95
// Only called for a method that has a try/catch, so the
96
// encoder was built with a control-flow builder.
97
_encoder.control_flow_builder!.add_catch_region(
98
_region_labels[try_start],
99
_region_labels[try_end],
100
_region_labels[handler_start],
101
_region_labels[handler_end],
102
catch_type)
103
si
104
105
add_finally_region(
106
try_start: int,
107
try_end: int,
108
handler_start: int,
109
handler_end: int
110
) is
111
// Only called for a method that has a try/finally, so the
112
// encoder was built with a control-flow builder.
113
_encoder.control_flow_builder!.add_finally_region(
114
_region_labels[try_start],
115
_region_labels[try_end],
116
_region_labels[handler_start],
117
_region_labels[handler_end])
118
si
119
120
_label(label: IR.LABEL) -> LabelHandle is
121
let key = "{label}"
122
123
if _labels.contains_key(key) then
124
return _labels[key]
125
fi
126
127
let handle = _encoder.define_label()
128
129
_labels[key] = handle
130
131
return handle
132
si
133
134
mark_label(label: IR.LABEL) is
135
_encoder.mark_label(_label(label))
136
si
137
138
branch(op_code: ILOpCode, label: IR.LABEL) is
139
_encoder.branch(op_code, _label(label))
140
si
141
142
// Declaring the same name twice is not an error: at least one
143
// value declares its temp on both arms of a branch. The first
144
// declaration wins, so the slot a later load resolves to is
145
// the one already in use.
146
declare_local(name: string, type: Type) -> int is
147
if _local_slots.contains_key(name) then
148
return _local_slots[name]
149
fi
150
151
let slot = _local_types.count
152
153
_local_types.add(type)
154
_local_slots[name] = slot
155
156
return slot
157
si
158
159
// A load of a name that was never declared means the value that
160
// declares it has no binary branch yet. Failing here names the
161
// local, which is a far shorter path to the culprit than the
162
// invalid program it would otherwise produce.
163
_slot(name: string) -> int is
164
assert _local_slots.contains_key(name) else
165
"local '{name}' is loaded but never declared"
166
167
return _local_slots[name]
168
si
169
170
has_locals: bool => _local_types.count > 0
171
local_types: Collections.List[Type] => _local_types
172
173
ldloc(name: string) is _encoder.load_local(_slot(name)); si
174
stloc(name: string) is _encoder.store_local(_slot(name)); si
175
ldloca(name: string) is _encoder.load_local_address(_slot(name)); si
176
177
ldarg(index: int) is _encoder.load_argument(index); si
178
starg(index: int) is _encoder.store_argument(index); si
179
ldarga(index: int) is _encoder.load_argument_address(index); si
180
181
ldstr(metadata: SRM_ASSEMBLY_EMITTER, value: string) is
182
_encoder.load_string(metadata.get_or_add_user_string(value))
183
si
184
185
ldc_i4(value: int) is _encoder.load_constant_i4(value); si
186
ldc_i8(value: long) is _encoder.load_constant_i8(value); si
187
ldc_r4(value: single) is _encoder.load_constant_r4(value); si
188
ldc_r8(value: double) is _encoder.load_constant_r8(value); si
189
ldnull() is _encoder.op_code(ILOpCode.LDNULL); si
190
191
call(target: EntityHandle) is _encoder.call(target); si
192
193
// Prefixes the following call with the type its receiver is
194
// reached through, which is how a call on a value whose type is
195
// a type parameter dispatches without the caller knowing whether
196
// that parameter will be a value or a reference type.
197
constrained(type: EntityHandle) is
198
_encoder.op_code(ILOpCode.CONSTRAINED)
199
_encoder.token(type)
200
si
201
call_virtual(target: EntityHandle) is _encoder.op_code(ILOpCode.CALLVIRT); _encoder.token(target); si
202
new_object(constructor: EntityHandle) is _encoder.op_code(ILOpCode.NEWOBJ); _encoder.token(constructor); si
203
204
ldfld(`field: EntityHandle) is _field_instruction(ILOpCode.LDFLD, `field); si
205
ldflda(`field: EntityHandle) is _field_instruction(ILOpCode.LDFLDA, `field); si
206
stfld(`field: EntityHandle) is _field_instruction(ILOpCode.STFLD, `field); si
207
ldsfld(`field: EntityHandle) is _field_instruction(ILOpCode.LDSFLD, `field); si
208
ldsflda(`field: EntityHandle) is _field_instruction(ILOpCode.LDSFLDA, `field); si
209
stsfld(`field: EntityHandle) is _field_instruction(ILOpCode.STSFLD, `field); si
210
211
_field_instruction(op_code: ILOpCode, `field: EntityHandle) is
212
_encoder.op_code(op_code)
213
_encoder.token(`field)
214
si
215
216
ldtoken(type: EntityHandle) is _encoder.op_code(ILOpCode.LDTOKEN); _encoder.token(type); si
217
218
box(type: EntityHandle) is _encoder.op_code(ILOpCode.BOX); _encoder.token(type); si
219
unbox_any(type: EntityHandle) is _encoder.op_code(ILOpCode.UNBOX_ANY); _encoder.token(type); si
220
cast_class(type: EntityHandle) is _encoder.op_code(ILOpCode.CASTCLASS); _encoder.token(type); si
221
is_instance(type: EntityHandle) is _encoder.op_code(ILOpCode.ISINST); _encoder.token(type); si
222
223
new_array(element_type: EntityHandle) is
224
_encoder.op_code(ILOpCode.NEWARR)
225
_encoder.token(element_type)
226
si
227
228
store_element(element_type: EntityHandle) is
229
_encoder.op_code(ILOpCode.STELEM)
230
_encoder.token(element_type)
231
si
232
233
load_element(element_type: EntityHandle) is
234
_encoder.op_code(ILOpCode.LDELEM)
235
_encoder.token(element_type)
236
si
237
238
load_object(type: EntityHandle) is _encoder.op_code(ILOpCode.LDOBJ); _encoder.token(type); si
239
store_object(type: EntityHandle) is _encoder.op_code(ILOpCode.STOBJ); _encoder.token(type); si
240
241
load_function_pointer(method: EntityHandle) is
242
_encoder.op_code(ILOpCode.LDFTN)
243
_encoder.token(method)
244
si
245
246
load_virtual_function_pointer(method: EntityHandle) is
247
_encoder.op_code(ILOpCode.LDVIRTFTN)
248
_encoder.token(method)
249
si
250
251
op(op_code: ILOpCode) is _encoder.op_code(op_code); si
252
253
ret() is _encoder.op_code(ILOpCode.RET); si
254
255
// Note that the instructions emitted from here on belong to a
256
// new source position. Called once per statement.
257
mark_sequence_point(location: Source.LOCATION) is
258
if !_sequence_points? then
259
_sequence_points = Collections.LIST[SRM_SEQUENCE_POINT]()
260
fi
261
262
let sequence_points = _sequence_points
263
264
let offset = _encoder.offset
265
266
// Two statements can start at the same offset when the
267
// first emits nothing. A sequence-point record after the
268
// first has to advance the offset - a zero delta is the
269
// document-change marker - so the later position replaces
270
// the earlier rather than being appended after it.
271
if sequence_points.count > 0 /\
272
sequence_points[sequence_points.count - 1].il_offset == offset
273
then
274
sequence_points.remove_at(sequence_points.count - 1)
275
fi
276
277
sequence_points.add(
278
SRM_SEQUENCE_POINT(
279
offset,
280
IO.Path.get_full_path(location.file_name),
281
location.start_line,
282
location.start_column,
283
location.end_line,
284
// A sequence point's end column is exclusive, and a
285
// LOCATION's is not.
286
location.end_column + 1))
287
si
288
289
// An `@IL.output` pragma opens a region whose emitted instructions
290
// the IL test runner extracts. Only the byte-offset range is
291
// recorded here; the ranges leave with the method as a custom
292
// attribute written by SRM_STRUCTURE_WALK._write_method.
293
begin_il_output(path: string, sequence: int) is
294
_il_output_stack.add((path, _encoder.offset, sequence))
295
si
296
297
end_il_output() is
298
let top = _il_output_stack[_il_output_stack.count - 1]
299
300
_il_output_stack.remove_at(_il_output_stack.count - 1)
301
302
if !_il_outputs? then
303
_il_outputs = Collections.LIST[SRM_IL_OUTPUT_RANGE]()
304
fi
305
306
_il_outputs.add(
307
SRM_IL_OUTPUT_RANGE(top.`0, top.`1, _encoder.offset, top.`2))
308
si
309
310
il_outputs: Collections.List[SRM_IL_OUTPUT_RANGE]? => _il_outputs
311
312
flush(assembly_emitter: SRM_ASSEMBLY_EMITTER) -> int is
313
let offset = assembly_emitter.add_method_body(_encoder, self)
314
315
if let points = _sequence_points then
316
assembly_emitter.set_sequence_points(offset, points)
317
fi
318
319
return offset
320
si
321
322
// The instruction bytes encoded so far. The body is not complete
323
// until it is flushed, which is what fixes branch offsets, so
324
// this is what has been emitted rather than what will be run.
325
encoded: ubyte[] => _encoder.code_builder.to_array()
326
si
327
si