1 | //===- YAMLRemarkSerializer.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 the implementation of the YAML remark serializer using |
10 | // LLVM's YAMLTraits. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "llvm/Remarks/YAMLRemarkSerializer.h" |
15 | #include "llvm/Remarks/Remark.h" |
16 | #include "llvm/Support/FileSystem.h" |
17 | #include <optional> |
18 | |
19 | using namespace llvm; |
20 | using namespace llvm::remarks; |
21 | |
22 | // Use the same keys whether we use a string table or not (respectively, T is an |
23 | // unsigned or a StringRef). |
24 | template <typename T> |
25 | static void (yaml::IO &io, T PassName, T , |
26 | std::optional<RemarkLocation> RL, T FunctionName, |
27 | std::optional<uint64_t> Hotness, |
28 | ArrayRef<Argument> Args) { |
29 | io.mapRequired("Pass" , PassName); |
30 | io.mapRequired("Name" , RemarkName); |
31 | io.mapOptional(Key: "DebugLoc" , Val&: RL); |
32 | io.mapRequired("Function" , FunctionName); |
33 | io.mapOptional(Key: "Hotness" , Val&: Hotness); |
34 | io.mapOptional(Key: "Args" , Val&: Args); |
35 | } |
36 | |
37 | namespace llvm { |
38 | namespace yaml { |
39 | |
40 | template <> struct MappingTraits<remarks::Remark *> { |
41 | static void (IO &io, remarks::Remark *&) { |
42 | assert(io.outputting() && "input not yet implemented" ); |
43 | |
44 | if (io.mapTag(Tag: "!Passed" , Default: (Remark->RemarkType == Type::Passed))) |
45 | ; |
46 | else if (io.mapTag(Tag: "!Missed" , Default: (Remark->RemarkType == Type::Missed))) |
47 | ; |
48 | else if (io.mapTag(Tag: "!Analysis" , Default: (Remark->RemarkType == Type::Analysis))) |
49 | ; |
50 | else if (io.mapTag(Tag: "!AnalysisFPCommute" , |
51 | Default: (Remark->RemarkType == Type::AnalysisFPCommute))) |
52 | ; |
53 | else if (io.mapTag(Tag: "!AnalysisAliasing" , |
54 | Default: (Remark->RemarkType == Type::AnalysisAliasing))) |
55 | ; |
56 | else if (io.mapTag(Tag: "!Failure" , Default: (Remark->RemarkType == Type::Failure))) |
57 | ; |
58 | else |
59 | llvm_unreachable("Unknown remark type" ); |
60 | |
61 | if (auto *Serializer = dyn_cast<YAMLStrTabRemarkSerializer>( |
62 | Val: reinterpret_cast<RemarkSerializer *>(io.getContext()))) { |
63 | assert(Serializer->StrTab && "YAMLStrTabSerializer with no StrTab." ); |
64 | StringTable &StrTab = *Serializer->StrTab; |
65 | unsigned PassID = StrTab.add(Str: Remark->PassName).first; |
66 | unsigned NameID = StrTab.add(Str: Remark->RemarkName).first; |
67 | unsigned FunctionID = StrTab.add(Str: Remark->FunctionName).first; |
68 | mapRemarkHeader(io, PassName: PassID, RemarkName: NameID, RL: Remark->Loc, FunctionName: FunctionID, |
69 | Hotness: Remark->Hotness, Args: Remark->Args); |
70 | } else { |
71 | mapRemarkHeader(io, PassName: Remark->PassName, RemarkName: Remark->RemarkName, RL: Remark->Loc, |
72 | FunctionName: Remark->FunctionName, Hotness: Remark->Hotness, Args: Remark->Args); |
73 | } |
74 | } |
75 | }; |
76 | |
77 | template <> struct MappingTraits<RemarkLocation> { |
78 | static void (IO &io, RemarkLocation &RL) { |
79 | assert(io.outputting() && "input not yet implemented" ); |
80 | |
81 | StringRef File = RL.SourceFilePath; |
82 | unsigned Line = RL.SourceLine; |
83 | unsigned Col = RL.SourceColumn; |
84 | |
85 | if (auto *Serializer = dyn_cast<YAMLStrTabRemarkSerializer>( |
86 | Val: reinterpret_cast<RemarkSerializer *>(io.getContext()))) { |
87 | assert(Serializer->StrTab && "YAMLStrTabSerializer with no StrTab." ); |
88 | StringTable &StrTab = *Serializer->StrTab; |
89 | unsigned FileID = StrTab.add(Str: File).first; |
90 | io.mapRequired(Key: "File" , Val&: FileID); |
91 | } else { |
92 | io.mapRequired(Key: "File" , Val&: File); |
93 | } |
94 | |
95 | io.mapRequired(Key: "Line" , Val&: Line); |
96 | io.mapRequired(Key: "Column" , Val&: Col); |
97 | } |
98 | |
99 | static const bool = true; |
100 | }; |
101 | |
102 | /// Helper struct for multiline string block literals. Use this type to preserve |
103 | /// newlines in strings. |
104 | struct StringBlockVal { |
105 | StringRef Value; |
106 | StringBlockVal(StringRef R) : Value(R) {} |
107 | }; |
108 | |
109 | template <> struct BlockScalarTraits<StringBlockVal> { |
110 | static void output(const StringBlockVal &S, void *Ctx, raw_ostream &OS) { |
111 | return ScalarTraits<StringRef>::output(S.Value, Ctx, OS); |
112 | } |
113 | |
114 | static StringRef input(StringRef Scalar, void *Ctx, StringBlockVal &S) { |
115 | return ScalarTraits<StringRef>::input(Scalar, Ctx, S.Value); |
116 | } |
117 | }; |
118 | |
119 | /// ArrayRef is not really compatible with the YAMLTraits. Everything should be |
120 | /// immutable in an ArrayRef, while the SequenceTraits expect a mutable version |
121 | /// for inputting, but we're only using the outputting capabilities here. |
122 | /// This is a hack, but still nicer than having to manually call the YAMLIO |
123 | /// internal methods. |
124 | /// Keep this in this file so that it doesn't get misused from YAMLTraits.h. |
125 | template <typename T> struct SequenceTraits<ArrayRef<T>> { |
126 | static size_t size(IO &io, ArrayRef<T> &seq) { return seq.size(); } |
127 | static Argument &element(IO &io, ArrayRef<T> &seq, size_t index) { |
128 | assert(io.outputting() && "input not yet implemented" ); |
129 | // The assert above should make this "safer" to satisfy the YAMLTraits. |
130 | return const_cast<T &>(seq[index]); |
131 | } |
132 | }; |
133 | |
134 | /// Implement this as a mapping for now to get proper quotation for the value. |
135 | template <> struct MappingTraits<Argument> { |
136 | static void (IO &io, Argument &A) { |
137 | assert(io.outputting() && "input not yet implemented" ); |
138 | |
139 | if (auto *Serializer = dyn_cast<YAMLStrTabRemarkSerializer>( |
140 | Val: reinterpret_cast<RemarkSerializer *>(io.getContext()))) { |
141 | assert(Serializer->StrTab && "YAMLStrTabSerializer with no StrTab." ); |
142 | StringTable &StrTab = *Serializer->StrTab; |
143 | auto ValueID = StrTab.add(Str: A.Val).first; |
144 | io.mapRequired(Key: A.Key.data(), Val&: ValueID); |
145 | } else if (StringRef(A.Val).count(C: '\n') > 1) { |
146 | StringBlockVal S(A.Val); |
147 | io.mapRequired(Key: A.Key.data(), Val&: S); |
148 | } else { |
149 | io.mapRequired(Key: A.Key.data(), Val&: A.Val); |
150 | } |
151 | io.mapOptional(Key: "DebugLoc" , Val&: A.Loc); |
152 | } |
153 | }; |
154 | |
155 | } // end namespace yaml |
156 | } // end namespace llvm |
157 | |
158 | LLVM_YAML_IS_SEQUENCE_VECTOR(Argument) |
159 | |
160 | YAMLRemarkSerializer::(raw_ostream &OS, SerializerMode Mode, |
161 | std::optional<StringTable> StrTabIn) |
162 | : YAMLRemarkSerializer(Format::YAML, OS, Mode, std::move(StrTabIn)) {} |
163 | |
164 | YAMLRemarkSerializer::(Format SerializerFormat, |
165 | raw_ostream &OS, SerializerMode Mode, |
166 | std::optional<StringTable> StrTabIn) |
167 | : RemarkSerializer(SerializerFormat, OS, Mode), |
168 | YAMLOutput(OS, reinterpret_cast<void *>(this)) { |
169 | StrTab = std::move(StrTabIn); |
170 | } |
171 | |
172 | void YAMLRemarkSerializer::(const Remark &) { |
173 | // Again, YAMLTraits expect a non-const object for inputting, but we're not |
174 | // using that here. |
175 | auto R = const_cast<remarks::Remark *>(&Remark); |
176 | YAMLOutput << R; |
177 | } |
178 | |
179 | std::unique_ptr<MetaSerializer> YAMLRemarkSerializer::( |
180 | raw_ostream &OS, std::optional<StringRef> ExternalFilename) { |
181 | return std::make_unique<YAMLMetaSerializer>(args&: OS, args&: ExternalFilename); |
182 | } |
183 | |
184 | void YAMLStrTabRemarkSerializer::(const Remark &) { |
185 | // In standalone mode, for the serializer with a string table, emit the |
186 | // metadata first and set DidEmitMeta to avoid emitting it again. |
187 | if (Mode == SerializerMode::Standalone && !DidEmitMeta) { |
188 | std::unique_ptr<MetaSerializer> MetaSerializer = |
189 | metaSerializer(OS, /*ExternalFilename=*/std::nullopt); |
190 | MetaSerializer->emit(); |
191 | DidEmitMeta = true; |
192 | } |
193 | |
194 | // Then do the usual remark emission. |
195 | YAMLRemarkSerializer::emit(Remark); |
196 | } |
197 | |
198 | std::unique_ptr<MetaSerializer> YAMLStrTabRemarkSerializer::( |
199 | raw_ostream &OS, std::optional<StringRef> ExternalFilename) { |
200 | assert(StrTab); |
201 | return std::make_unique<YAMLStrTabMetaSerializer>(args&: OS, args&: ExternalFilename, |
202 | args&: *StrTab); |
203 | } |
204 | |
205 | static void emitMagic(raw_ostream &OS) { |
206 | // Emit the magic number. |
207 | OS << remarks::Magic; |
208 | // Explicitly emit a '\0'. |
209 | OS.write(C: '\0'); |
210 | } |
211 | |
212 | static void emitVersion(raw_ostream &OS) { |
213 | // Emit the version number: little-endian uint64_t. |
214 | std::array<char, 8> Version; |
215 | support::endian::write64le(P: Version.data(), V: remarks::CurrentRemarkVersion); |
216 | OS.write(Ptr: Version.data(), Size: Version.size()); |
217 | } |
218 | |
219 | static void (raw_ostream &OS, |
220 | std::optional<const StringTable *> StrTab) { |
221 | // Emit the string table in the section. |
222 | uint64_t StrTabSize = StrTab ? (*StrTab)->SerializedSize : 0; |
223 | // Emit the total size of the string table (the size itself excluded): |
224 | // little-endian uint64_t. |
225 | // Note: even if no string table is used, emit 0. |
226 | std::array<char, 8> StrTabSizeBuf; |
227 | support::endian::write64le(P: StrTabSizeBuf.data(), V: StrTabSize); |
228 | OS.write(Ptr: StrTabSizeBuf.data(), Size: StrTabSizeBuf.size()); |
229 | if (StrTab) |
230 | (*StrTab)->serialize(OS); |
231 | } |
232 | |
233 | static void emitExternalFile(raw_ostream &OS, StringRef Filename) { |
234 | // Emit the null-terminated absolute path to the remark file. |
235 | SmallString<128> FilenameBuf = Filename; |
236 | sys::fs::make_absolute(path&: FilenameBuf); |
237 | assert(!FilenameBuf.empty() && "The filename can't be empty." ); |
238 | OS.write(Ptr: FilenameBuf.data(), Size: FilenameBuf.size()); |
239 | OS.write(C: '\0'); |
240 | } |
241 | |
242 | void YAMLMetaSerializer::() { |
243 | emitMagic(OS); |
244 | emitVersion(OS); |
245 | emitStrTab(OS, StrTab: std::nullopt); |
246 | if (ExternalFilename) |
247 | emitExternalFile(OS, Filename: *ExternalFilename); |
248 | } |
249 | |
250 | void YAMLStrTabMetaSerializer::() { |
251 | emitMagic(OS); |
252 | emitVersion(OS); |
253 | emitStrTab(OS, StrTab: &StrTab); |
254 | if (ExternalFilename) |
255 | emitExternalFile(OS, Filename: *ExternalFilename); |
256 | } |
257 | |