Skip to content
← Back

src/ir/emitter/srm_pdb_builder.ghul

1
namespace IR.Emitter is
2
use System.Reflection.Metadata.BlobBuilder
3
use System.Reflection.Metadata.BlobContentId
4
use System.Reflection.Metadata.DocumentHandle
5
use System.Reflection.Metadata.Ecma335.MetadataBuilder
6
use System.Reflection.Metadata.Ecma335.PortablePdbBuilder
7
8
// One source position the debugger or a coverage tool can stop at,
9
// paired with the instruction offset it starts at.
10
class SRM_SEQUENCE_POINT(
11
il_offset: int,
12
file_name: string,
13
start_line: int,
14
start_column: int,
15
end_line: int,
16
end_column: int
17
)
18
19
// A run of instructions an `@IL.output` pragma marks. The binary back
20
// end records one per marked statement and carries them out of the
21
// method as a custom attribute, which the IL test runner reads back to
22
// slice the disassembly to just the statement's instructions. The
23
// sequence number is compilation-global, so a test marking statements
24
// in more than one method can rebuild their source order.
25
class SRM_IL_OUTPUT_RANGE(
26
path: string,
27
start_offset: int,
28
end_offset: int,
29
sequence: int
30
)
31
32
// Builds the portable PDB that sits beside the emitted assembly.
33
//
34
// A PDB is its own metadata file with its own tables, so this holds
35
// a second MetadataBuilder rather than adding to the assembly's.
36
// The one hard coupling between them is MethodDebugInformation:
37
// its rows are not looked up by key, they are read positionally
38
// against MethodDef, so row N here has to describe method N there.
39
// Nothing checks - a missing row shifts every method after it onto
40
// another method's line numbers, which reads as a compiler that
41
// emits plausible but wrong debug info rather than as a broken
42
// file. `add_method_debug_information` is therefore called once per
43
// MethodDef row from the one place that adds them, including for
44
// the methods that have no sequence points at all.
45
class SRM_PDB_BUILDER is
46
_metadata: MetadataBuilder
47
_documents: Collections.MAP[string, DocumentHandle]
48
49
init() is
50
_metadata = MetadataBuilder(0, 0, 0, 0)
51
_documents = Collections.MAP[string, DocumentHandle]()
52
si
53
54
_EMPTY_HASH: ubyte[] static => []
55
56
_document(file_name: string) -> DocumentHandle is
57
let existing: DocumentHandle mut
58
59
if _documents.try_get_value(file_name, existing ref) then
60
return existing
61
fi
62
63
// No hash and no language: a hash commits the PDB to one
64
// byte-for-byte revision of the source, which is what a
65
// debugger checks before binding a breakpoint, and the
66
// compiler does not keep the bytes it read by the time the
67
// assembly is written. Line lookup and coverage attribution
68
// read the name and the sequence points, and need neither.
69
let handle =
70
_metadata.add_document(
71
_metadata.get_or_add_document_name(file_name),
72
_[System.Reflection.Metadata.GuidHandle],
73
_metadata.get_or_add_blob(_EMPTY_HASH),
74
_[System.Reflection.Metadata.GuidHandle])
75
76
_documents[file_name] = handle
77
78
return handle
79
si
80
81
// One row, whether or not there is anything to say - see the
82
// positional coupling above. A method with no sequence points
83
// (an abstract declaration, a compiler-synthesised member, any
84
// method at all in a non-debug build) gets an empty row.
85
add_method_debug_information(points: Collections.List[SRM_SEQUENCE_POINT]?) is
86
if !points? \/ points.count == 0 then
87
_metadata.add_method_debug_information(
88
_[DocumentHandle], _[System.Reflection.Metadata.BlobHandle])
89
90
return
91
fi
92
93
let initial_document = _document(points[0].file_name)
94
95
_metadata.add_method_debug_information(
96
initial_document,
97
_metadata.get_or_add_blob(_sequence_point_blob(points, initial_document)))
98
si
99
100
// The sequence-point blob, as ECMA-335 II.24.2.1 defines it:
101
// a header naming the method's local signature, then one record
102
// per point, each expressed as a delta from the one before so
103
// the common case costs a handful of bytes.
104
//
105
// The first record carries absolute values; every later one
106
// carries deltas, and its IL-offset delta must be non-zero,
107
// because a zero there is the marker for a document-change
108
// record instead.
109
_sequence_point_blob(
110
points: Collections.List[SRM_SEQUENCE_POINT],
111
initial_document: DocumentHandle
112
) -> ubyte[] is
113
let buffer = BlobBuilder(32)
114
115
// Local signature. The back end emits no StandAloneSig row
116
// for a body's locals, so there is none to name.
117
buffer.write_compressed_integer(0)
118
119
let previous mut = _[SRM_SEQUENCE_POINT?]
120
let current_document mut = initial_document
121
122
for point in points do
123
let document = _document(point.file_name)
124
125
if !(document =~ current_document) then
126
// A document-change record: the zero IL-offset
127
// delta that no ordinary record can carry, then the
128
// document being switched to.
129
//
130
// It changes which document the records after it
131
// belong to and nothing else. The deltas keep
132
// running from the last real sequence point, so the
133
// record following it is still a delta rather than
134
// absolute - resetting the baseline here would
135
// desynchronise every offset and line after it.
136
buffer.write_compressed_integer(0)
137
buffer.write_compressed_integer(
138
System.Reflection.Metadata.Ecma335.MetadataTokens.get_row_number(
139
cast System.Reflection.Metadata.EntityHandle(document)))
140
141
current_document = document
142
fi
143
144
let delta_lines = point.end_line - point.start_line
145
let delta_columns = point.end_column - point.start_column
146
147
if let seen = previous then
148
buffer.write_compressed_integer(point.il_offset - seen.il_offset)
149
else
150
buffer.write_compressed_integer(point.il_offset)
151
fi
152
153
buffer.write_compressed_integer(delta_lines)
154
155
// On a point that begins and ends on one line the
156
// column delta is that line's width, which cannot be
157
// negative, so it is unsigned. Across lines the end
158
// column can fall to the left of the start column, so
159
// it is signed.
160
if delta_lines == 0 then
161
buffer.write_compressed_integer(delta_columns)
162
else
163
buffer.write_compressed_signed_integer(delta_columns)
164
fi
165
166
if let seen = previous then
167
buffer.write_compressed_signed_integer(
168
point.start_line - seen.start_line)
169
buffer.write_compressed_signed_integer(
170
point.start_column - seen.start_column)
171
else
172
buffer.write_compressed_integer(point.start_line)
173
buffer.write_compressed_integer(point.start_column)
174
fi
175
176
previous = point
177
od
178
179
return buffer.to_array()
180
si
181
182
// Writes the PDB and hands back the identity the assembly's
183
// debug directory has to name for a runtime to pair the two.
184
// The serialized PDB, for a caller that wants the bytes rather
185
// than a file - reading them back through a metadata reader is
186
// how the blob encoding above is tested.
187
serialize(
188
blob: BlobBuilder,
189
type_system_row_counts: System.Collections.Immutable.ImmutableArray[int],
190
entry_point: System.Reflection.Metadata.MethodDefinitionHandle,
191
content_id_provider: (Collections.Iterable[System.Reflection.Metadata.Blob]) -> BlobContentId
192
) -> BlobContentId =>
193
PortablePdbBuilder(
194
_metadata, type_system_row_counts, entry_point, content_id_provider)
195
.serialize(blob)
196
197
write_to(
198
path: string,
199
type_system_row_counts: System.Collections.Immutable.ImmutableArray[int],
200
entry_point: System.Reflection.Metadata.MethodDefinitionHandle,
201
content_id_provider: (Collections.Iterable[System.Reflection.Metadata.Blob]) -> BlobContentId
202
) -> BlobContentId is
203
let blob = BlobBuilder(1024)
204
205
let content_id =
206
serialize(blob, type_system_row_counts, entry_point, content_id_provider)
207
208
let stream = IO.File.create(path)
209
blob.write_content_to(stream)
210
stream.close()
211
212
return content_id
213
si
214
si
215
si