Skip to content
← Back

src/ir/emitter/srm_attribute_blob_encoder.ghul

1
namespace IR.Emitter is
2
use Collections
3
4
use System.Reflection.Metadata.BlobBuilder
5
6
use Semantic.Types.Type
7
8
// Encodes a custom attribute's value blob: ECMA-335 II.23.3.
9
//
10
// The bytes are encoded from the resolved arguments the attribute
11
// carries, never from any rendering of them: a rendering read back
12
// as data would be taking presentation for fact, and would drift
13
// the first time the rendering changed.
14
//
15
// Every fixed argument is encoded against its parameter's declared
16
// type rather than against the value's own, because the blob has no
17
// room to say which type it meant: the reader walks the constructor
18
// signature alongside the bytes. The one exception is a parameter
19
// typed `object`, whose value is preceded by a tag naming the type
20
// it was boxed from.
21
class SRM_ATTRIBUTE_BLOB_ENCODER is
22
// ECMA-335 II.23.1.16 element type codes, as far as a value blob
23
// can carry them.
24
BOOLEAN: ubyte static => 2ub
25
CHAR: ubyte static => 3ub
26
I1: ubyte static => 4ub
27
U1: ubyte static => 5ub
28
I2: ubyte static => 6ub
29
U2: ubyte static => 7ub
30
I4: ubyte static => 8ub
31
U4: ubyte static => 9ub
32
I8: ubyte static => 10ub
33
U8: ubyte static => 11ub
34
R4: ubyte static => 12ub
35
R8: ubyte static => 13ub
36
STRING: ubyte static => 14ub
37
SZARRAY: ubyte static => 29ub
38
39
// II.23.3's own additions, which exist only inside a value blob.
40
TYPE: ubyte static => 80ub
41
BOXED: ubyte static => 81ub
42
FIELD: ubyte static => 83ub
43
PROPERTY: ubyte static => 84ub
44
ENUM: ubyte static => 85ub
45
46
_lookup: Semantic.Lookups.InnateSymbolLookup
47
48
init(lookup: Semantic.Lookups.InnateSymbolLookup) is
49
super.init()
50
51
_lookup = lookup
52
si
53
54
// The whole blob for an attribute application, or null if any
55
// argument is not a constant this encoder can express.
56
//
57
// The resolver has already rejected an inexpressible argument
58
// with a diagnostic, so a null here means the two disagree about
59
// what is expressible rather than that the source was bad.
60
encode(attribute: Semantic.CUSTOM_ATTRIBUTE) -> ubyte[]? is
61
let buffer = BlobBuilder(32)
62
63
buffer.write_u_int16(1us)
64
65
let parameters = attribute.constructor.arguments
66
let positional = attribute.positional
67
68
for i in 0..parameters.count do
69
if i >= positional.count then
70
return null
71
fi
72
73
if !_write_fixed(buffer, parameters[i], positional[i]) then
74
return null
75
fi
76
od
77
78
let named = attribute.named
79
80
buffer.write_u_int16(cast ushort(named.count))
81
82
for argument in named do
83
if !_write_named(buffer, argument) then
84
return null
85
fi
86
od
87
88
return buffer.to_array()
89
si
90
91
// A `NullableAttribute`, `TupleElementNamesAttribute` or any
92
// other framework attribute the compiler synthesises rather than
93
// resolving from a pragma: the arguments are known here, so the
94
// blob is built from them directly.
95
encode_string_argument(value: string?) -> ubyte[] static is
96
let buffer = BlobBuilder(32)
97
98
buffer.write_u_int16(1us)
99
buffer.write_serialized_string(value)
100
buffer.write_u_int16(0us)
101
102
return buffer.to_array()
103
si
104
105
encode_string_arguments(values: List[string]) -> ubyte[] static is
106
let buffer = BlobBuilder(64)
107
108
buffer.write_u_int16(1us)
109
110
for value in values do
111
buffer.write_serialized_string(value)
112
od
113
114
buffer.write_u_int16(0us)
115
116
return buffer.to_array()
117
si
118
119
encode_byte_argument(value: int) -> ubyte[] static is
120
let buffer = BlobBuilder(8)
121
122
buffer.write_u_int16(1us)
123
buffer.write_byte(cast ubyte(value))
124
buffer.write_u_int16(0us)
125
126
return buffer.to_array()
127
si
128
129
encode_byte_array_argument(values: List[int]) -> ubyte[] static is
130
let buffer = BlobBuilder(16)
131
132
buffer.write_u_int16(1us)
133
buffer.write_int32(values.count)
134
135
for value in values do
136
buffer.write_byte(cast ubyte(value))
137
od
138
139
buffer.write_u_int16(0us)
140
141
return buffer.to_array()
142
si
143
144
// A null entry is an unnamed element, which the blob carries as
145
// a null string rather than as an empty one - an empty name
146
// would read back as an element actually called "".
147
encode_string_array_argument(values: List[string?]) -> ubyte[] static is
148
let buffer = BlobBuilder(32)
149
150
buffer.write_u_int16(1us)
151
buffer.write_int32(values.count)
152
153
for value in values do
154
buffer.write_serialized_string(value)
155
od
156
157
buffer.write_u_int16(0us)
158
159
return buffer.to_array()
160
si
161
162
// The four parallel arrays an `@IL.output` ranges attribute carries:
163
// the pragma's file name, the start and end instruction offsets, and
164
// the source-order sequence - one entry per marked statement. Each
165
// array carries its own length prefix, as every fixed array argument
166
// does, and the trailing zero is the named-arguments count.
167
encode_il_output_ranges(ranges: List[SRM_IL_OUTPUT_RANGE]) -> ubyte[] static is
168
let buffer = BlobBuilder(64)
169
170
buffer.write_u_int16(1us)
171
172
buffer.write_int32(ranges.count)
173
for range in ranges do
174
buffer.write_serialized_string(range.path)
175
od
176
177
buffer.write_int32(ranges.count)
178
for range in ranges do
179
buffer.write_int32(range.start_offset)
180
od
181
182
buffer.write_int32(ranges.count)
183
for range in ranges do
184
buffer.write_int32(range.end_offset)
185
od
186
187
buffer.write_int32(ranges.count)
188
for range in ranges do
189
buffer.write_int32(range.sequence)
190
od
191
192
buffer.write_u_int16(0us)
193
194
return buffer.to_array()
195
si
196
197
_write_named(
198
buffer: BlobBuilder,
199
argument: Semantic.NAMED_ATTRIBUTE_ARGUMENT
200
) -> bool is
201
buffer.write_byte(if argument.is_field then FIELD else PROPERTY fi)
202
203
if !_write_field_or_prop_type(buffer, argument.member_type) then
204
return false
205
fi
206
207
buffer.write_serialized_string(argument.il_name)
208
209
return _write_fixed(buffer, argument.member_type, argument.value)
210
si
211
212
// The type tag a named argument carries ahead of its value,
213
// because a reader has no signature to walk for one.
214
_write_field_or_prop_type(buffer: BlobBuilder, type: Type) -> bool is
215
if _is_object(type) then
216
buffer.write_byte(BOXED)
217
218
return true
219
fi
220
221
if let array: Semantic.Types.ARRAY = type then
222
buffer.write_byte(SZARRAY)
223
224
return _write_field_or_prop_type(buffer, array.arguments[0])
225
fi
226
227
if _is_string(type) then
228
buffer.write_byte(STRING)
229
230
return true
231
fi
232
233
if _is_type(type) then
234
buffer.write_byte(TYPE)
235
236
return true
237
fi
238
239
if _is_enum(type) then
240
buffer.write_byte(ENUM)
241
buffer.write_serialized_string(_serialized_type_name(type))
242
243
return true
244
fi
245
246
let code = _element_type(type)
247
248
if !code? then
249
return false
250
fi
251
252
buffer.write_byte(code)
253
254
return true
255
si
256
257
// One value, encoded as the slot's declared type says to read it.
258
_write_fixed(buffer: BlobBuilder, type: Type?, value: IR.Values.Value?) -> bool is
259
if !type? \/ !value? then
260
return false
261
fi
262
263
if _is_object(type) then
264
return _write_boxed(buffer, value)
265
fi
266
267
if let array: Semantic.Types.ARRAY = type then
268
return _write_array(buffer, array.arguments[0], value)
269
fi
270
271
if _is_string(type) then
272
if isa IR.Values.NULL(value) then
273
buffer.write_serialized_string(null)
274
275
return true
276
fi
277
278
if let text: IR.Values.Literal.STRING = value then
279
buffer.write_serialized_string(text.value)
280
281
return true
282
fi
283
284
return false
285
fi
286
287
if _is_type(type) then
288
if isa IR.Values.NULL(value) then
289
buffer.write_serialized_string(null)
290
291
return true
292
fi
293
294
if let typeof_value: IR.Values.TYPEOF = value then
295
buffer.write_serialized_string(
296
_serialized_type_name(typeof_value.typeof_type))
297
298
return true
299
fi
300
301
return false
302
fi
303
304
return _write_scalar(buffer, type, value)
305
si
306
307
// An array's length prefix is a full four bytes rather than a
308
// compressed integer, and all-ones marks a null array as
309
// distinct from an empty one.
310
_write_array(buffer: BlobBuilder, element_type: Type, value: IR.Values.Value) -> bool is
311
if isa IR.Values.NULL(value) then
312
buffer.write_u_int32(4294967295u)
313
314
return true
315
fi
316
317
let sequence = cast IR.Values.SEQUENCE?(value)
318
319
if !sequence? then
320
return false
321
fi
322
323
buffer.write_int32(sequence.values.count)
324
325
for element in sequence.values do
326
if !_write_fixed(buffer, element_type, element) then
327
return false
328
fi
329
od
330
331
return true
332
si
333
334
// A value in an `object` slot describes itself: the tag says
335
// what type the reader should box it back to. A null reference
336
// has no type to name, and is encoded as a null string - which
337
// is what a C# `[Foo((object) null)]` produces.
338
_write_boxed(buffer: BlobBuilder, value: IR.Values.Value) -> bool is
339
if isa IR.Values.NULL(value) then
340
buffer.write_byte(STRING)
341
buffer.write_serialized_string(null)
342
343
return true
344
fi
345
346
if let text: IR.Values.Literal.STRING = value then
347
buffer.write_byte(STRING)
348
buffer.write_serialized_string(text.value)
349
350
return true
351
fi
352
353
if let typeof_value: IR.Values.TYPEOF = value then
354
buffer.write_byte(TYPE)
355
buffer.write_serialized_string(
356
_serialized_type_name(typeof_value.typeof_type))
357
358
return true
359
fi
360
361
if let sequence: IR.Values.SEQUENCE = value then
362
buffer.write_byte(SZARRAY)
363
364
if !_write_field_or_prop_type(buffer, sequence.element_type) then
365
return false
366
fi
367
368
return _write_array(buffer, sequence.element_type, value)
369
fi
370
371
let value_type = value.type
372
373
if !value_type? then
374
return false
375
fi
376
377
if !_write_field_or_prop_type(buffer, value_type) then
378
return false
379
fi
380
381
return _write_scalar(buffer, value_type, value)
382
si
383
384
// A bool, char, integer or float. An enum is its underlying
385
// value: which enum it was is carried by the signature, or by
386
// the tag a named or boxed argument writes ahead of it.
387
_write_scalar(buffer: BlobBuilder, type: Type, value: IR.Values.Value) -> bool is
388
let constant = _constant_of(value)
389
390
if !constant? then
391
return false
392
fi
393
394
let code = _element_type(type) ?? I4
395
396
if code == BOOLEAN then
397
buffer.write_byte(if _as_signed(constant) == 0L then 0ub else 1ub fi)
398
elif code == R4 then
399
buffer.write_single(cast single(_as_real(constant)))
400
elif code == R8 then
401
buffer.write_double(_as_real(constant))
402
elif code == U1 then
403
buffer.write_byte(cast ubyte(_as_signed(constant)))
404
elif code == U2 \/ code == CHAR then
405
buffer.write_u_int16(cast ushort(_as_signed(constant)))
406
elif code == U4 then
407
buffer.write_u_int32(cast uint(_as_signed(constant)))
408
elif code == U8 then
409
buffer.write_u_int64(cast ulong(_as_signed(constant)))
410
elif code == I1 then
411
buffer.write_s_byte(cast byte(_as_signed(constant)))
412
elif code == I2 then
413
buffer.write_int16(cast short(_as_signed(constant)))
414
elif code == I4 then
415
buffer.write_int32(cast int(_as_signed(constant)))
416
elif code == I8 then
417
buffer.write_int64(_as_signed(constant))
418
else
419
return false
420
fi
421
422
return true
423
si
424
425
// The integral constant, widened. A narrower target takes the
426
// low bytes of it, which is what the attribute blob holds.
427
_as_signed(constant: IR.Values.Literal.CONSTANT) -> long static =>
428
case constant
429
when i4: IR.Values.Literal.CONSTANT.I4 then cast long(i4.value)
430
when i8: IR.Values.Literal.CONSTANT.I8 then i8.value
431
when r4: IR.Values.Literal.CONSTANT.R4 then cast long(r4.value)
432
when r8: IR.Values.Literal.CONSTANT.R8 then cast long(r8.value)
433
esac
434
435
_as_real(constant: IR.Values.Literal.CONSTANT) -> double static =>
436
case constant
437
when i4: IR.Values.Literal.CONSTANT.I4 then cast double(i4.value)
438
when i8: IR.Values.Literal.CONSTANT.I8 then cast double(i8.value)
439
when r4: IR.Values.Literal.CONSTANT.R4 then cast double(r4.value)
440
when r8: IR.Values.Literal.CONSTANT.R8 then r8.value
441
esac
442
443
// The constant a value denotes: a literal, or the unary minus
444
// applied to one - `-5` is a negation over `5` rather than a
445
// negative literal.
446
_constant_of(value: IR.Values.Value) -> IR.Values.Literal.CONSTANT? is
447
if let number: IR.Values.Literal.NUMBER = value then
448
return number.constant
449
fi
450
451
if let negation: IR.Values.Call.INNATE = value then
452
if negation.function.name =~ "-" /\ negation.arguments.count == 1 then
453
if let number: IR.Values.Literal.NUMBER = negation.arguments[0] then
454
return _negate(number.constant)
455
fi
456
fi
457
fi
458
459
return null
460
si
461
462
_negate(constant: IR.Values.Literal.CONSTANT) -> IR.Values.Literal.CONSTANT static =>
463
case constant
464
when i4: IR.Values.Literal.CONSTANT.I4 then IR.Values.Literal.CONSTANT.I4(-i4.value)
465
when i8: IR.Values.Literal.CONSTANT.I8 then IR.Values.Literal.CONSTANT.I8(-i8.value)
466
when r4: IR.Values.Literal.CONSTANT.R4 then IR.Values.Literal.CONSTANT.R4(-r4.value)
467
when r8: IR.Values.Literal.CONSTANT.R8 then IR.Values.Literal.CONSTANT.R8(-r8.value)
468
esac
469
470
471
472
473
// A source literal keeps the shape it was written in: a sign, an
474
// optional radix prefix, and digits that may be grouped.
475
_split_literal(text: string) -> (negative: bool, digits: string, radix: int) static is
476
let negative = text.starts_with("-")
477
let unsigned = if negative then text.substring(1) else text fi
478
let stripped = unsigned.replace("_", "")
479
480
if stripped.starts_with("0x") \/ stripped.starts_with("0X") then
481
return (negative = negative, digits = stripped.substring(2), radix = 16)
482
fi
483
484
return (negative = negative, digits = stripped, radix = 10)
485
si
486
487
_parse_magnitude(digits: string, radix: int) -> ulong? static is
488
if digits.length == 0 then
489
return null
490
fi
491
492
try
493
return System.Convert.to_u_int64(digits, radix)
494
catch e: System.Exception
495
return null
496
yrt
497
si
498
499
// The name a blob carries for a type. The attribute resolver
500
// rejects a typeof argument the reflection name grammar cannot
501
// carry, so a null here is a compiler defect - an attribute
502
// reached emission without passing that gate - and stops the
503
// build rather than encoding a blob no reader could resolve.
504
_serialized_type_name(type: Type) -> string is
505
let name = Semantic.DotNet.REFLECTION_TYPE_NAME.try_serialize(type)
506
507
assert name? else "typeof argument type has no serializable name: {type}"
508
509
return name
510
si
511
512
_is_string(type: Type) -> bool => type.matches(_lookup.get_string_type())
513
514
_is_object(type: Type) -> bool => type.matches(_lookup.get_object_type())
515
516
_is_type(type: Type) -> bool => type.matches(_lookup.get_type_type())
517
518
_is_enum(type: Type) -> bool =>
519
type.symbol.symbol_kind == Semantic.Symbols.SymbolKind.ENUM
520
521
// The element type code for a primitive, or null for anything
522
// else. An enum answers for its underlying type, which the
523
// compiler models as `int` throughout.
524
_element_type(type: Type) -> ubyte? is
525
if type.matches(_lookup.get_bool_type()) then return BOOLEAN; fi
526
if type.matches(_lookup.get_char_type()) then return CHAR; fi
527
if type.matches(_lookup.get_byte_type()) then return I1; fi
528
if type.matches(_lookup.get_ubyte_type()) then return U1; fi
529
if type.matches(_lookup.get_short_type()) then return I2; fi
530
if type.matches(_lookup.get_ushort_type()) then return U2; fi
531
if type.matches(_lookup.get_int_type()) then return I4; fi
532
if type.matches(_lookup.get_uint_type()) then return U4; fi
533
if type.matches(_lookup.get_long_type()) then return I8; fi
534
if type.matches(_lookup.get_ulong_type()) then return U8; fi
535
if type.matches(_lookup.get_single_type()) then return R4; fi
536
if type.matches(_lookup.get_double_type()) then return R8; fi
537
538
if _is_enum(type) then
539
return I4
540
fi
541
542
return null
543
si
544
si
545
si