1//===- BitstreamRemarkParser.cpp ------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file provides utility methods used by clients that want to use the
10// parser for remark diagnostics in LLVM.
11//
12//===----------------------------------------------------------------------===//
13
14#include "BitstreamRemarkParser.h"
15#include "llvm/Support/MemoryBuffer.h"
16#include "llvm/Support/Path.h"
17#include <optional>
18
19using namespace llvm;
20using namespace llvm::remarks;
21
22namespace {
23
24template <typename... Ts> Error error(char const *Fmt, const Ts &...Vals) {
25 std::string Buffer;
26 raw_string_ostream OS(Buffer);
27 OS << formatv(Fmt, Vals...);
28 return make_error<StringError>(
29 Args: std::move(Buffer),
30 Args: std::make_error_code(e: std::errc::illegal_byte_sequence));
31}
32
33} // namespace
34
35Error BitstreamBlockParserHelperBase::unknownRecord(unsigned AbbrevID) {
36 return error(Fmt: "Unknown record entry ({}).", Vals: AbbrevID);
37}
38
39Error BitstreamBlockParserHelperBase::unexpectedRecord(StringRef RecordName) {
40 return error(Fmt: "Unexpected record entry ({}).", Vals: RecordName);
41}
42
43Error BitstreamBlockParserHelperBase::malformedRecord(StringRef RecordName) {
44 return error(Fmt: "Malformed record entry ({}).", Vals: RecordName);
45}
46
47Error BitstreamBlockParserHelperBase::unexpectedBlock(unsigned Code) {
48 return error(Fmt: "Unexpected subblock ({}).", Vals: Code);
49}
50
51static Expected<unsigned> expectSubBlock(BitstreamCursor &Stream) {
52 Expected<BitstreamEntry> Next = Stream.advance();
53 if (!Next)
54 return Next.takeError();
55 switch (Next->Kind) {
56 case BitstreamEntry::SubBlock:
57 return Next->ID;
58 case BitstreamEntry::Record:
59 case BitstreamEntry::EndBlock:
60 return error(Fmt: "Expected subblock, but got unexpected record.");
61 case BitstreamEntry::Error:
62 return error(Fmt: "Expected subblock, but got unexpected end of bitstream.");
63 }
64 llvm_unreachable("Unexpected BitstreamEntry");
65}
66
67Error BitstreamBlockParserHelperBase::expectBlock() {
68 auto MaybeBlockID = expectSubBlock(Stream);
69 if (!MaybeBlockID)
70 return MaybeBlockID.takeError();
71 if (*MaybeBlockID != BlockID)
72 return error(Fmt: "Expected {} block, but got unexpected block ({}).", Vals: BlockName,
73 Vals: *MaybeBlockID);
74 return Error::success();
75}
76
77Error BitstreamBlockParserHelperBase::enterBlock() {
78 if (Stream.EnterSubBlock(BlockID))
79 return error(Fmt: "Error while entering {} block.", Vals: BlockName);
80 return Error::success();
81}
82
83Error BitstreamMetaParserHelper::parseRecord(unsigned Code) {
84 // Note: 2 is used here because it's the max number of fields we have per
85 // record.
86 SmallVector<uint64_t, 2> Record;
87 StringRef Blob;
88 Expected<unsigned> RecordID = Stream.readRecord(AbbrevID: Code, Vals&: Record, Blob: &Blob);
89 if (!RecordID)
90 return RecordID.takeError();
91
92 switch (*RecordID) {
93 case RECORD_META_CONTAINER_INFO: {
94 if (Record.size() != 2)
95 return malformedRecord(RecordName: MetaContainerInfoName);
96 Container = {.Version: Record[0], .Type: Record[1]};
97 // Error immediately if container version is outdated, so the user sees an
98 // explanation instead of a parser error.
99 if (Container->Version != CurrentContainerVersion) {
100 return ::error(
101 Fmt: "Unsupported remark container version (expected: {}, read: {}). "
102 "Please upgrade/downgrade your toolchain to read this container.",
103 Vals: CurrentContainerVersion, Vals: Container->Version);
104 }
105 break;
106 }
107 case RECORD_META_REMARK_VERSION: {
108 if (Record.size() != 1)
109 return malformedRecord(RecordName: MetaRemarkVersionName);
110 RemarkVersion = Record[0];
111 // Error immediately if remark version is outdated, so the user sees an
112 // explanation instead of a parser error.
113 if (*RemarkVersion != CurrentRemarkVersion) {
114 return ::error(
115 Fmt: "Unsupported remark version in container (expected: {}, read: {}). "
116 "Please upgrade/downgrade your toolchain to read this container.",
117 Vals: CurrentRemarkVersion, Vals: *RemarkVersion);
118 }
119 break;
120 }
121 case RECORD_META_STRTAB: {
122 if (Record.size() != 0)
123 return malformedRecord(RecordName: MetaStrTabName);
124 StrTabBuf = Blob;
125 break;
126 }
127 case RECORD_META_EXTERNAL_FILE: {
128 if (Record.size() != 0)
129 return malformedRecord(RecordName: MetaExternalFileName);
130 ExternalFilePath = Blob;
131 break;
132 }
133 default:
134 return unknownRecord(AbbrevID: *RecordID);
135 }
136 return Error::success();
137}
138
139Error BitstreamRemarkParserHelper::parseRecord(unsigned Code) {
140 Record.clear();
141 Expected<unsigned> MaybeRecordID =
142 Stream.readRecord(AbbrevID: Code, Vals&: Record, Blob: &RecordBlob);
143 if (!MaybeRecordID)
144 return MaybeRecordID.takeError();
145 RecordID = *MaybeRecordID;
146 return handleRecord();
147}
148
149Error BitstreamRemarkParserHelper::handleRecord() {
150 switch (RecordID) {
151 case RECORD_REMARK_HEADER: {
152 if (Record.size() != 4)
153 return malformedRecord(RecordName: RemarkHeaderName);
154 Type = Record[0];
155 RemarkNameIdx = Record[1];
156 PassNameIdx = Record[2];
157 FunctionNameIdx = Record[3];
158 break;
159 }
160 case RECORD_REMARK_DEBUG_LOC: {
161 if (Record.size() != 3)
162 return malformedRecord(RecordName: RemarkDebugLocName);
163 Loc = {.SourceFileNameIdx: Record[0], .SourceLine: Record[1], .SourceColumn: Record[2]};
164 break;
165 }
166 case RECORD_REMARK_HOTNESS: {
167 if (Record.size() != 1)
168 return malformedRecord(RecordName: RemarkHotnessName);
169 Hotness = Record[0];
170 break;
171 }
172 case RECORD_REMARK_ARG_WITH_DEBUGLOC: {
173 if (Record.size() != 5)
174 return malformedRecord(RecordName: RemarkArgWithDebugLocName);
175 auto &Arg = Args.emplace_back(Args&: Record[0], Args&: Record[1]);
176 Arg.Loc = {.SourceFileNameIdx: Record[2], .SourceLine: Record[3], .SourceColumn: Record[4]};
177 break;
178 }
179 case RECORD_REMARK_ARG_WITHOUT_DEBUGLOC: {
180 if (Record.size() != 2)
181 return malformedRecord(RecordName: RemarkArgWithoutDebugLocName);
182 Args.emplace_back(Args&: Record[0], Args&: Record[1]);
183 break;
184 }
185 default:
186 return unknownRecord(AbbrevID: RecordID);
187 }
188 return Error::success();
189}
190
191Error BitstreamRemarkParserHelper::parseNext() {
192 Type.reset();
193 RemarkNameIdx.reset();
194 PassNameIdx.reset();
195 FunctionNameIdx.reset();
196 Hotness.reset();
197 Loc.reset();
198 Args.clear();
199
200 return parseBlock();
201}
202
203Error BitstreamParserHelper::expectMagic() {
204 std::array<char, 4> Result;
205 for (unsigned I = 0; I < 4; ++I)
206 if (Expected<unsigned> R = Stream.Read(NumBits: 8))
207 Result[I] = *R;
208 else
209 return R.takeError();
210
211 StringRef MagicNumber{Result.data(), Result.size()};
212 if (MagicNumber != remarks::ContainerMagic)
213 return error(Fmt: "Unknown magic number: expecting {}, got {}.",
214 Vals: remarks::ContainerMagic, Vals: MagicNumber);
215 return Error::success();
216}
217
218Error BitstreamParserHelper::parseBlockInfoBlock() {
219 Expected<BitstreamEntry> Next = Stream.advance();
220 if (!Next)
221 return Next.takeError();
222 if (Next->Kind != BitstreamEntry::SubBlock ||
223 Next->ID != llvm::bitc::BLOCKINFO_BLOCK_ID)
224 return error(
225 Fmt: "Error while parsing BLOCKINFO_BLOCK: expecting [ENTER_SUBBLOCK, "
226 "BLOCKINFO_BLOCK, ...].");
227
228 Expected<std::optional<BitstreamBlockInfo>> MaybeBlockInfo =
229 Stream.ReadBlockInfoBlock();
230 if (!MaybeBlockInfo)
231 return MaybeBlockInfo.takeError();
232
233 if (!*MaybeBlockInfo)
234 return error(Fmt: "Missing BLOCKINFO_BLOCK.");
235
236 BlockInfo = **MaybeBlockInfo;
237
238 Stream.setBlockInfo(&BlockInfo);
239 return Error::success();
240}
241
242Error BitstreamParserHelper::parseMeta() {
243 if (Error E = expectMagic())
244 return E;
245 if (Error E = parseBlockInfoBlock())
246 return E;
247
248 // Parse early meta block.
249 if (Error E = MetaHelper.expectBlock())
250 return E;
251 if (Error E = MetaHelper.parseBlock())
252 return E;
253
254 // Skip all Remarks blocks.
255 while (!Stream.AtEndOfStream()) {
256 auto MaybeBlockID = expectSubBlock(Stream);
257 if (!MaybeBlockID)
258 return MaybeBlockID.takeError();
259 if (*MaybeBlockID == META_BLOCK_ID)
260 break;
261 if (*MaybeBlockID != REMARK_BLOCK_ID)
262 return error(Fmt: "Unexpected block between meta blocks.");
263 // Remember first remark block.
264 if (!RemarkStartBitPos)
265 RemarkStartBitPos = Stream.GetCurrentBitNo();
266 if (Error E = Stream.SkipBlock())
267 return E;
268 }
269
270 // Late meta block is optional if there are no remarks.
271 if (Stream.AtEndOfStream())
272 return Error::success();
273
274 // Parse late meta block.
275 if (Error E = MetaHelper.parseBlock())
276 return E;
277 return Error::success();
278}
279
280Error BitstreamParserHelper::parseRemark() {
281 if (RemarkStartBitPos) {
282 RemarkStartBitPos.reset();
283 } else {
284 auto MaybeBlockID = expectSubBlock(Stream);
285 if (!MaybeBlockID)
286 return MaybeBlockID.takeError();
287 if (*MaybeBlockID != REMARK_BLOCK_ID)
288 return make_error<EndOfFileError>();
289 }
290 return RemarksHelper->parseNext();
291}
292
293Expected<std::unique_ptr<BitstreamRemarkParser>>
294remarks::createBitstreamParserFromMeta(
295 StringRef Buf, std::optional<StringRef> ExternalFilePrependPath) {
296 auto Parser = std::make_unique<BitstreamRemarkParser>(args&: Buf);
297
298 if (ExternalFilePrependPath)
299 Parser->ExternalFilePrependPath = std::string(*ExternalFilePrependPath);
300
301 return std::move(Parser);
302}
303
304BitstreamRemarkParser::BitstreamRemarkParser(StringRef Buf)
305 : RemarkParser(Format::Bitstream), ParserHelper(Buf) {}
306
307Expected<std::unique_ptr<Remark>> BitstreamRemarkParser::next() {
308 if (!IsMetaReady) {
309 // Container is completely empty.
310 if (ParserHelper->Stream.AtEndOfStream())
311 return make_error<EndOfFileError>();
312
313 if (Error E = parseMeta())
314 return std::move(E);
315 IsMetaReady = true;
316
317 // Container has meta, but no remarks blocks.
318 if (!ParserHelper->RemarkStartBitPos)
319 return error(
320 Fmt: "Container is non-empty, but does not contain any remarks blocks.");
321
322 if (Error E =
323 ParserHelper->Stream.JumpToBit(BitNo: *ParserHelper->RemarkStartBitPos))
324 return std::move(E);
325 ParserHelper->RemarksHelper.emplace(args&: ParserHelper->Stream);
326 }
327
328 if (Error E = ParserHelper->parseRemark())
329 return std::move(E);
330 return processRemark();
331}
332
333Error BitstreamRemarkParser::parseMeta() {
334 if (Error E = ParserHelper->parseMeta())
335 return E;
336 if (Error E = processCommonMeta())
337 return E;
338
339 switch (ContainerType) {
340 case BitstreamRemarkContainerType::RemarksFileExternal:
341 return processExternalFilePath();
342 case BitstreamRemarkContainerType::RemarksFile:
343 return processFileContainerMeta();
344 }
345 llvm_unreachable("Unknown BitstreamRemarkContainerType enum");
346}
347
348Error BitstreamRemarkParser::processCommonMeta() {
349 auto &Helper = ParserHelper->MetaHelper;
350 if (!Helper.Container)
351 return Helper.error(Fmt: "Missing container info.");
352 auto &Container = *Helper.Container;
353 ContainerVersion = Container.Version;
354 // Always >= BitstreamRemarkContainerType::First since it's unsigned.
355 if (Container.Type > static_cast<uint8_t>(BitstreamRemarkContainerType::Last))
356 return Helper.error(Fmt: "Invalid container type.");
357 ContainerType = static_cast<BitstreamRemarkContainerType>(Container.Type);
358 return Error::success();
359}
360
361Error BitstreamRemarkParser::processFileContainerMeta() {
362 if (Error E = processRemarkVersion())
363 return E;
364 if (Error E = processStrTab())
365 return E;
366 return Error::success();
367}
368
369Error BitstreamRemarkParser::processStrTab() {
370 auto &Helper = ParserHelper->MetaHelper;
371 if (!Helper.StrTabBuf)
372 return Helper.error(Fmt: "Missing string table.");
373 // Parse and assign the string table.
374 StrTab.emplace(args&: *Helper.StrTabBuf);
375 return Error::success();
376}
377
378Error BitstreamRemarkParser::processRemarkVersion() {
379 auto &Helper = ParserHelper->MetaHelper;
380 if (!Helper.RemarkVersion)
381 return Helper.error(Fmt: "Missing remark version.");
382 RemarkVersion = *Helper.RemarkVersion;
383 return Error::success();
384}
385
386Error BitstreamRemarkParser::processExternalFilePath() {
387 auto &Helper = ParserHelper->MetaHelper;
388 if (!Helper.ExternalFilePath)
389 return Helper.error(Fmt: "Missing external file path.");
390
391 SmallString<80> FullPath(ExternalFilePrependPath);
392 sys::path::append(path&: FullPath, a: *Helper.ExternalFilePath);
393
394 // External file: open the external file, parse it, check if its metadata
395 // matches the one from the separate metadata, then replace the current
396 // parser with the one parsing the remarks.
397 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
398 MemoryBuffer::getFile(Filename: FullPath);
399 if (std::error_code EC = BufferOrErr.getError())
400 return createFileError(F: FullPath, EC);
401
402 TmpRemarkBuffer = std::move(*BufferOrErr);
403
404 // Don't try to parse the file if it's empty.
405 if (TmpRemarkBuffer->getBufferSize() == 0)
406 return make_error<EndOfFileError>();
407
408 // Create a separate parser used for parsing the separate file.
409 ParserHelper.emplace(args: TmpRemarkBuffer->getBuffer());
410 if (Error E = parseMeta())
411 return E;
412
413 if (ContainerType != BitstreamRemarkContainerType::RemarksFile)
414 return ParserHelper->MetaHelper.error(
415 Fmt: "Wrong container type in external file.");
416
417 return Error::success();
418}
419
420Expected<std::unique_ptr<Remark>> BitstreamRemarkParser::processRemark() {
421 auto &Helper = *ParserHelper->RemarksHelper;
422 std::unique_ptr<Remark> Result = std::make_unique<Remark>();
423 Remark &R = *Result;
424
425 if (!StrTab)
426 return Helper.error(Fmt: "Missing string table.");
427
428 if (!Helper.Type)
429 return Helper.error(Fmt: "Missing remark type.");
430
431 // Always >= Type::First since it's unsigned.
432 if (*Helper.Type > static_cast<uint8_t>(Type::Last))
433 return Helper.error(Fmt: "Unknown remark type.");
434
435 R.RemarkType = static_cast<Type>(*Helper.Type);
436
437 if (!Helper.RemarkNameIdx)
438 return Helper.error(Fmt: "Missing remark name.");
439
440 if (Expected<StringRef> RemarkName = (*StrTab)[*Helper.RemarkNameIdx])
441 R.RemarkName = *RemarkName;
442 else
443 return RemarkName.takeError();
444
445 if (!Helper.PassNameIdx)
446 return Helper.error(Fmt: "Missing remark pass.");
447
448 if (Expected<StringRef> PassName = (*StrTab)[*Helper.PassNameIdx])
449 R.PassName = *PassName;
450 else
451 return PassName.takeError();
452
453 if (!Helper.FunctionNameIdx)
454 return Helper.error(Fmt: "Missing remark function name.");
455
456 if (Expected<StringRef> FunctionName = (*StrTab)[*Helper.FunctionNameIdx])
457 R.FunctionName = *FunctionName;
458 else
459 return FunctionName.takeError();
460
461 if (Helper.Loc) {
462 Expected<StringRef> SourceFileName =
463 (*StrTab)[Helper.Loc->SourceFileNameIdx];
464 if (!SourceFileName)
465 return SourceFileName.takeError();
466 R.Loc.emplace();
467 R.Loc->SourceFilePath = *SourceFileName;
468 R.Loc->SourceLine = Helper.Loc->SourceLine;
469 R.Loc->SourceColumn = Helper.Loc->SourceColumn;
470 }
471
472 if (Helper.Hotness)
473 R.Hotness = *Helper.Hotness;
474
475 for (const BitstreamRemarkParserHelper::Argument &Arg : Helper.Args) {
476 if (!Arg.KeyIdx)
477 return Helper.error(Fmt: "Missing key in remark argument.");
478 if (!Arg.ValueIdx)
479 return Helper.error(Fmt: "Missing value in remark argument.");
480
481 // We have at least a key and a value, create an entry.
482 auto &RArg = R.Args.emplace_back();
483
484 if (Expected<StringRef> Key = (*StrTab)[*Arg.KeyIdx])
485 RArg.Key = *Key;
486 else
487 return Key.takeError();
488
489 if (Expected<StringRef> Value = (*StrTab)[*Arg.ValueIdx])
490 RArg.Val = *Value;
491 else
492 return Value.takeError();
493
494 if (Arg.Loc) {
495 if (Expected<StringRef> SourceFileName =
496 (*StrTab)[Arg.Loc->SourceFileNameIdx]) {
497 RArg.Loc.emplace();
498 RArg.Loc->SourceFilePath = *SourceFileName;
499 RArg.Loc->SourceLine = Arg.Loc->SourceLine;
500 RArg.Loc->SourceColumn = Arg.Loc->SourceColumn;
501 } else
502 return SourceFileName.takeError();
503 }
504 }
505
506 return std::move(Result);
507}
508