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 mapRemarkHeader(yaml::IO &io, T PassName, T RemarkName, 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("DebugLoc", RL); 32 io.mapRequired("Function", FunctionName); 33 io.mapOptional("Hotness", Hotness); 34 io.mapOptional("Args", Args); 35 } 36 37 namespace llvm { 38 namespace yaml { 39 40 template <> struct MappingTraits<remarks::Remark *> { 41 static void mapping(IO &io, remarks::Remark *&Remark) { 42 assert(io.outputting() && "input not yet implemented"); 43 44 if (io.mapTag("!Passed", (Remark->RemarkType == Type::Passed))) 45 ; 46 else if (io.mapTag("!Missed", (Remark->RemarkType == Type::Missed))) 47 ; 48 else if (io.mapTag("!Analysis", (Remark->RemarkType == Type::Analysis))) 49 ; 50 else if (io.mapTag("!AnalysisFPCommute", 51 (Remark->RemarkType == Type::AnalysisFPCommute))) 52 ; 53 else if (io.mapTag("!AnalysisAliasing", 54 (Remark->RemarkType == Type::AnalysisAliasing))) 55 ; 56 else if (io.mapTag("!Failure", (Remark->RemarkType == Type::Failure))) 57 ; 58 else 59 llvm_unreachable("Unknown remark type"); 60 61 if (auto *Serializer = dyn_cast<YAMLStrTabRemarkSerializer>( 62 reinterpret_cast<RemarkSerializer *>(io.getContext()))) { 63 assert(Serializer->StrTab && "YAMLStrTabSerializer with no StrTab."); 64 StringTable &StrTab = *Serializer->StrTab; 65 unsigned PassID = StrTab.add(Remark->PassName).first; 66 unsigned NameID = StrTab.add(Remark->RemarkName).first; 67 unsigned FunctionID = StrTab.add(Remark->FunctionName).first; 68 mapRemarkHeader(io, PassID, NameID, Remark->Loc, FunctionID, 69 Remark->Hotness, Remark->Args); 70 } else { 71 mapRemarkHeader(io, Remark->PassName, Remark->RemarkName, Remark->Loc, 72 Remark->FunctionName, Remark->Hotness, Remark->Args); 73 } 74 } 75 }; 76 77 template <> struct MappingTraits<RemarkLocation> { 78 static void mapping(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 reinterpret_cast<RemarkSerializer *>(io.getContext()))) { 87 assert(Serializer->StrTab && "YAMLStrTabSerializer with no StrTab."); 88 StringTable &StrTab = *Serializer->StrTab; 89 unsigned FileID = StrTab.add(File).first; 90 io.mapRequired("File", FileID); 91 } else { 92 io.mapRequired("File", File); 93 } 94 95 io.mapRequired("Line", Line); 96 io.mapRequired("Column", Col); 97 } 98 99 static const bool flow = 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 mapping(IO &io, Argument &A) { 137 assert(io.outputting() && "input not yet implemented"); 138 139 if (auto *Serializer = dyn_cast<YAMLStrTabRemarkSerializer>( 140 reinterpret_cast<RemarkSerializer *>(io.getContext()))) { 141 assert(Serializer->StrTab && "YAMLStrTabSerializer with no StrTab."); 142 StringTable &StrTab = *Serializer->StrTab; 143 auto ValueID = StrTab.add(A.Val).first; 144 io.mapRequired(A.Key.data(), ValueID); 145 } else if (StringRef(A.Val).count('\n') > 1) { 146 StringBlockVal S(A.Val); 147 io.mapRequired(A.Key.data(), S); 148 } else { 149 io.mapRequired(A.Key.data(), A.Val); 150 } 151 io.mapOptional("DebugLoc", A.Loc); 152 } 153 }; 154 155 } // end namespace yaml 156 } // end namespace llvm 157 158 LLVM_YAML_IS_SEQUENCE_VECTOR(Argument) 159 160 YAMLRemarkSerializer::YAMLRemarkSerializer(raw_ostream &OS, SerializerMode Mode, 161 std::optional<StringTable> StrTabIn) 162 : YAMLRemarkSerializer(Format::YAML, OS, Mode, std::move(StrTabIn)) {} 163 164 YAMLRemarkSerializer::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::emit(const Remark &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::metaSerializer( 180 raw_ostream &OS, std::optional<StringRef> ExternalFilename) { 181 return std::make_unique<YAMLMetaSerializer>(OS, ExternalFilename); 182 } 183 184 void YAMLStrTabRemarkSerializer::emit(const Remark &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::metaSerializer( 199 raw_ostream &OS, std::optional<StringRef> ExternalFilename) { 200 assert(StrTab); 201 return std::make_unique<YAMLStrTabMetaSerializer>(OS, ExternalFilename, 202 *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('\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(Version.data(), remarks::CurrentRemarkVersion); 216 OS.write(Version.data(), Version.size()); 217 } 218 219 static void emitStrTab(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(StrTabSizeBuf.data(), StrTabSize); 228 OS.write(StrTabSizeBuf.data(), 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(FilenameBuf); 237 assert(!FilenameBuf.empty() && "The filename can't be empty."); 238 OS.write(FilenameBuf.data(), FilenameBuf.size()); 239 OS.write('\0'); 240 } 241 242 void YAMLMetaSerializer::emit() { 243 emitMagic(OS); 244 emitVersion(OS); 245 emitStrTab(OS, std::nullopt); 246 if (ExternalFilename) 247 emitExternalFile(OS, *ExternalFilename); 248 } 249 250 void YAMLStrTabMetaSerializer::emit() { 251 emitMagic(OS); 252 emitVersion(OS); 253 emitStrTab(OS, &StrTab); 254 if (ExternalFilename) 255 emitExternalFile(OS, *ExternalFilename); 256 } 257