| 1 | //===- yaml2goff - Convert YAML to a GOFF object file ---------------------===// |
| 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 | /// \file |
| 10 | /// The GOFF component of yaml2obj. |
| 11 | /// |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "llvm/ObjectYAML/ObjectYAML.h" |
| 15 | #include "llvm/ObjectYAML/yaml2obj.h" |
| 16 | #include "llvm/Support/ConvertEBCDIC.h" |
| 17 | #include "llvm/Support/Endian.h" |
| 18 | #include "llvm/Support/raw_ostream.h" |
| 19 | |
| 20 | using namespace llvm; |
| 21 | |
| 22 | namespace { |
| 23 | |
| 24 | // Common flag values on records. |
| 25 | enum { |
| 26 | // Flag: This record is continued. |
| 27 | Rec_Continued = 1, |
| 28 | |
| 29 | // Flag: This record is a continuation. |
| 30 | Rec_Continuation = 1 << (8 - 6 - 1), |
| 31 | }; |
| 32 | |
| 33 | template <typename ValueType> struct BinaryBeImpl { |
| 34 | ValueType Value; |
| 35 | BinaryBeImpl(ValueType V) : Value(V) {} |
| 36 | }; |
| 37 | |
| 38 | template <typename ValueType> |
| 39 | raw_ostream &operator<<(raw_ostream &OS, const BinaryBeImpl<ValueType> &BBE) { |
| 40 | char Buffer[sizeof(BBE.Value)]; |
| 41 | support::endian::write<ValueType, llvm::endianness::big, support::unaligned>( |
| 42 | Buffer, BBE.Value); |
| 43 | OS.write(Buffer, sizeof(BBE.Value)); |
| 44 | return OS; |
| 45 | } |
| 46 | |
| 47 | template <typename ValueType> BinaryBeImpl<ValueType> binaryBe(ValueType V) { |
| 48 | return BinaryBeImpl<ValueType>(V); |
| 49 | } |
| 50 | |
| 51 | struct ZerosImpl { |
| 52 | size_t NumBytes; |
| 53 | }; |
| 54 | |
| 55 | raw_ostream &operator<<(raw_ostream &OS, const ZerosImpl &Z) { |
| 56 | OS.write_zeros(NumZeros: Z.NumBytes); |
| 57 | return OS; |
| 58 | } |
| 59 | |
| 60 | ZerosImpl zeros(const size_t NumBytes) { return ZerosImpl{.NumBytes: NumBytes}; } |
| 61 | |
| 62 | // The GOFFOstream is responsible to write the data into the fixed physical |
| 63 | // records of the format. A user of this class announces the start of a new |
| 64 | // logical record and the size of its payload. While writing the payload, the |
| 65 | // physical records are created for the data. Possible fill bytes at the end of |
| 66 | // a physical record are written automatically. |
| 67 | class GOFFOstream : public raw_ostream { |
| 68 | public: |
| 69 | explicit GOFFOstream(raw_ostream &OS) |
| 70 | : OS(OS), LogicalRecords(0), RemainingSize(0), NewLogicalRecord(false) { |
| 71 | SetBufferSize(GOFF::PayloadLength); |
| 72 | } |
| 73 | |
| 74 | ~GOFFOstream() { finalize(); } |
| 75 | |
| 76 | void makeNewRecord(GOFF::RecordType Type, size_t Size) { |
| 77 | fillRecord(); |
| 78 | CurrentType = Type; |
| 79 | RemainingSize = Size; |
| 80 | if (size_t Gap = (RemainingSize % GOFF::PayloadLength)) |
| 81 | RemainingSize += GOFF::PayloadLength - Gap; |
| 82 | NewLogicalRecord = true; |
| 83 | ++LogicalRecords; |
| 84 | } |
| 85 | |
| 86 | void finalize() { fillRecord(); } |
| 87 | |
| 88 | uint32_t logicalRecords() { return LogicalRecords; } |
| 89 | |
| 90 | private: |
| 91 | // The underlying raw_ostream. |
| 92 | raw_ostream &OS; |
| 93 | |
| 94 | // The number of logical records emitted so far. |
| 95 | uint32_t LogicalRecords; |
| 96 | |
| 97 | // The remaining size of this logical record, including fill bytes. |
| 98 | size_t RemainingSize; |
| 99 | |
| 100 | // The type of the current (logical) record. |
| 101 | GOFF::RecordType CurrentType; |
| 102 | |
| 103 | // Signals start of new record. |
| 104 | bool NewLogicalRecord; |
| 105 | |
| 106 | // Return the number of bytes left to write until next physical record. |
| 107 | // Please note that we maintain the total number of bytes left, not the |
| 108 | // written size. |
| 109 | size_t bytesToNextPhysicalRecord() { |
| 110 | size_t Bytes = RemainingSize % GOFF::PayloadLength; |
| 111 | return Bytes ? Bytes : GOFF::PayloadLength; |
| 112 | } |
| 113 | |
| 114 | // Write the record prefix of a physical record, using the current record |
| 115 | // type. |
| 116 | static void writeRecordPrefix(raw_ostream &OS, GOFF::RecordType Type, |
| 117 | size_t RemainingSize, |
| 118 | uint8_t Flags = Rec_Continuation) { |
| 119 | uint8_t TypeAndFlags = Flags | (Type << 4); |
| 120 | if (RemainingSize > GOFF::RecordLength) |
| 121 | TypeAndFlags |= Rec_Continued; |
| 122 | OS << binaryBe(V: static_cast<unsigned char>(GOFF::PTVPrefix)) |
| 123 | << binaryBe(V: static_cast<unsigned char>(TypeAndFlags)) |
| 124 | << binaryBe(V: static_cast<unsigned char>(0)); |
| 125 | } |
| 126 | |
| 127 | // Fill the last physical record of a logical record with zero bytes. |
| 128 | void fillRecord() { |
| 129 | assert((GetNumBytesInBuffer() <= RemainingSize) && |
| 130 | "More bytes in buffer than expected" ); |
| 131 | size_t Remains = RemainingSize - GetNumBytesInBuffer(); |
| 132 | if (Remains) { |
| 133 | assert((Remains < GOFF::RecordLength) && |
| 134 | "Attempting to fill more than one physical record" ); |
| 135 | raw_ostream::write_zeros(NumZeros: Remains); |
| 136 | } |
| 137 | flush(); |
| 138 | assert(RemainingSize == 0 && "Not fully flushed" ); |
| 139 | assert(GetNumBytesInBuffer() == 0 && "Buffer not fully empty" ); |
| 140 | } |
| 141 | |
| 142 | // See raw_ostream::write_impl. |
| 143 | void write_impl(const char *Ptr, size_t Size) override { |
| 144 | assert((RemainingSize >= Size) && "Attempt to write too much data" ); |
| 145 | assert(RemainingSize && "Logical record overflow" ); |
| 146 | if (!(RemainingSize % GOFF::PayloadLength)) { |
| 147 | writeRecordPrefix(OS, Type: CurrentType, RemainingSize, |
| 148 | Flags: NewLogicalRecord ? 0 : Rec_Continuation); |
| 149 | NewLogicalRecord = false; |
| 150 | } |
| 151 | assert(!NewLogicalRecord && |
| 152 | "New logical record not on physical record boundary" ); |
| 153 | |
| 154 | size_t Idx = 0; |
| 155 | while (Size > 0) { |
| 156 | size_t BytesToWrite = bytesToNextPhysicalRecord(); |
| 157 | if (BytesToWrite > Size) |
| 158 | BytesToWrite = Size; |
| 159 | OS.write(Ptr: Ptr + Idx, Size: BytesToWrite); |
| 160 | Idx += BytesToWrite; |
| 161 | Size -= BytesToWrite; |
| 162 | RemainingSize -= BytesToWrite; |
| 163 | if (Size) { |
| 164 | writeRecordPrefix(OS, Type: CurrentType, RemainingSize); |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | // Return the current position within the stream, not counting the bytes |
| 170 | // currently in the buffer. |
| 171 | uint64_t current_pos() const override { return OS.tell(); } |
| 172 | }; |
| 173 | |
| 174 | class GOFFState { |
| 175 | void writeHeader(GOFFYAML::FileHeader &FileHdr); |
| 176 | void writeEnd(); |
| 177 | |
| 178 | void reportError(const Twine &Msg) { |
| 179 | ErrHandler(Msg); |
| 180 | HasError = true; |
| 181 | } |
| 182 | |
| 183 | GOFFState(raw_ostream &OS, GOFFYAML::Object &Doc, |
| 184 | yaml::ErrorHandler ErrHandler) |
| 185 | : GW(OS), Doc(Doc), ErrHandler(ErrHandler), HasError(false) {} |
| 186 | |
| 187 | ~GOFFState() { GW.finalize(); } |
| 188 | |
| 189 | bool writeObject(); |
| 190 | |
| 191 | public: |
| 192 | static bool writeGOFF(raw_ostream &OS, GOFFYAML::Object &Doc, |
| 193 | yaml::ErrorHandler ErrHandler); |
| 194 | |
| 195 | private: |
| 196 | GOFFOstream GW; |
| 197 | GOFFYAML::Object &Doc; |
| 198 | yaml::ErrorHandler ErrHandler; |
| 199 | bool HasError; |
| 200 | }; |
| 201 | |
| 202 | void GOFFState::(GOFFYAML::FileHeader &FileHdr) { |
| 203 | SmallString<16> CCSIDName; |
| 204 | if (std::error_code EC = |
| 205 | ConverterEBCDIC::convertToEBCDIC(Source: FileHdr.CharacterSetName, Result&: CCSIDName)) |
| 206 | reportError(Msg: "Conversion error on " + FileHdr.CharacterSetName); |
| 207 | if (CCSIDName.size() > 16) { |
| 208 | reportError(Msg: "CharacterSetName too long" ); |
| 209 | CCSIDName.resize(N: 16); |
| 210 | } |
| 211 | SmallString<16> LangProd; |
| 212 | if (std::error_code EC = ConverterEBCDIC::convertToEBCDIC( |
| 213 | Source: FileHdr.LanguageProductIdentifier, Result&: LangProd)) |
| 214 | reportError(Msg: "Conversion error on " + FileHdr.LanguageProductIdentifier); |
| 215 | if (LangProd.size() > 16) { |
| 216 | reportError(Msg: "LanguageProductIdentifier too long" ); |
| 217 | LangProd.resize(N: 16); |
| 218 | } |
| 219 | |
| 220 | GW.makeNewRecord(Type: GOFF::RT_HDR, Size: GOFF::PayloadLength); |
| 221 | GW << binaryBe(V: FileHdr.TargetEnvironment) // TargetEnvironment |
| 222 | << binaryBe(V: FileHdr.TargetOperatingSystem) // TargetOperatingSystem |
| 223 | << zeros(NumBytes: 2) // Reserved |
| 224 | << binaryBe(V: FileHdr.CCSID) // CCSID |
| 225 | << CCSIDName // CharacterSetName |
| 226 | << zeros(NumBytes: 16 - CCSIDName.size()) // Fill bytes |
| 227 | << LangProd // LanguageProductIdentifier |
| 228 | << zeros(NumBytes: 16 - LangProd.size()) // Fill bytes |
| 229 | << binaryBe(V: FileHdr.ArchitectureLevel); // ArchitectureLevel |
| 230 | // The module propties are optional. Figure out if we need to write them. |
| 231 | uint16_t ModPropLen = 0; |
| 232 | if (FileHdr.TargetSoftwareEnvironment) |
| 233 | ModPropLen = 3; |
| 234 | else if (FileHdr.InternalCCSID) |
| 235 | ModPropLen = 2; |
| 236 | if (ModPropLen) { |
| 237 | GW << binaryBe(V: ModPropLen) << zeros(NumBytes: 6); |
| 238 | if (ModPropLen >= 2) |
| 239 | GW << binaryBe(V: FileHdr.InternalCCSID.value_or(u: 0)); |
| 240 | if (ModPropLen >= 3) |
| 241 | GW << binaryBe(V: FileHdr.TargetSoftwareEnvironment.value_or(u: 0)); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | void GOFFState::writeEnd() { |
| 246 | GW.makeNewRecord(Type: GOFF::RT_END, Size: GOFF::PayloadLength); |
| 247 | GW << binaryBe(V: uint8_t(0)) // No entry point |
| 248 | << binaryBe(V: uint8_t(0)) // No AMODE |
| 249 | << zeros(NumBytes: 3) // Reserved |
| 250 | << binaryBe(V: GW.logicalRecords()); |
| 251 | // No entry point yet. Automatically fill remaining space with zero bytes. |
| 252 | GW.finalize(); |
| 253 | } |
| 254 | |
| 255 | bool GOFFState::writeObject() { |
| 256 | writeHeader(FileHdr&: Doc.Header); |
| 257 | if (HasError) |
| 258 | return false; |
| 259 | writeEnd(); |
| 260 | return true; |
| 261 | } |
| 262 | |
| 263 | bool GOFFState::writeGOFF(raw_ostream &OS, GOFFYAML::Object &Doc, |
| 264 | yaml::ErrorHandler ErrHandler) { |
| 265 | GOFFState State(OS, Doc, ErrHandler); |
| 266 | return State.writeObject(); |
| 267 | } |
| 268 | } // namespace |
| 269 | |
| 270 | namespace llvm { |
| 271 | namespace yaml { |
| 272 | |
| 273 | bool yaml2goff(llvm::GOFFYAML::Object &Doc, raw_ostream &Out, |
| 274 | ErrorHandler ErrHandler) { |
| 275 | return GOFFState::writeGOFF(OS&: Out, Doc, ErrHandler); |
| 276 | } |
| 277 | |
| 278 | } // namespace yaml |
| 279 | } // namespace llvm |
| 280 | |