| //===- yaml2goff - Convert YAML to a GOFF object file ---------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// The GOFF component of yaml2obj. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/ObjectYAML/ObjectYAML.h" | |
| #include "llvm/ObjectYAML/yaml2obj.h" | |
| #include "llvm/Support/ConvertEBCDIC.h" | |
| #include "llvm/Support/Endian.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| using namespace llvm; | |
| namespace { | |
| // Common flag values on records. | |
| enum { | |
| // Flag: This record is continued. | |
| Rec_Continued = 1, | |
| // Flag: This record is a continuation. | |
| Rec_Continuation = 1 << (8 - 6 - 1), | |
| }; | |
| template <typename ValueType> struct BinaryBeImpl { | |
| ValueType Value; | |
| BinaryBeImpl(ValueType V) : Value(V) {} | |
| }; | |
| template <typename ValueType> | |
| raw_ostream &operator<<(raw_ostream &OS, const BinaryBeImpl<ValueType> &BBE) { | |
| char Buffer[sizeof(BBE.Value)]; | |
| support::endian::write<ValueType, support::unaligned>(Buffer, BBE.Value, | |
| llvm::endianness::big); | |
| OS.write(Buffer, sizeof(BBE.Value)); | |
| return OS; | |
| } | |
| template <typename ValueType> BinaryBeImpl<ValueType> binaryBe(ValueType V) { | |
| return BinaryBeImpl<ValueType>(V); | |
| } | |
| struct ZerosImpl { | |
| size_t NumBytes; | |
| }; | |
| raw_ostream &operator<<(raw_ostream &OS, const ZerosImpl &Z) { | |
| OS.write_zeros(Z.NumBytes); | |
| return OS; | |
| } | |
| ZerosImpl zeros(const size_t NumBytes) { return ZerosImpl{NumBytes}; } | |
| // The GOFFOstream is responsible to write the data into the fixed physical | |
| // records of the format. A user of this class announces the start of a new | |
| // logical record and the size of its payload. While writing the payload, the | |
| // physical records are created for the data. Possible fill bytes at the end of | |
| // a physical record are written automatically. | |
| class GOFFOstream : public raw_ostream { | |
| public: | |
| explicit GOFFOstream(raw_ostream &OS) | |
| : OS(OS), LogicalRecords(0), RemainingSize(0), NewLogicalRecord(false) { | |
| SetBufferSize(GOFF::PayloadLength); | |
| } | |
| ~GOFFOstream() override { finalize(); } | |
| void makeNewRecord(GOFF::RecordType Type, size_t Size) { | |
| fillRecord(); | |
| CurrentType = Type; | |
| RemainingSize = Size; | |
| if (size_t Gap = (RemainingSize % GOFF::PayloadLength)) | |
| RemainingSize += GOFF::PayloadLength - Gap; | |
| NewLogicalRecord = true; | |
| ++LogicalRecords; | |
| } | |
| void finalize() { fillRecord(); } | |
| uint32_t logicalRecords() { return LogicalRecords; } | |
| private: | |
| // The underlying raw_ostream. | |
| raw_ostream &OS; | |
| // The number of logical records emitted so far. | |
| uint32_t LogicalRecords; | |
| // The remaining size of this logical record, including fill bytes. | |
| size_t RemainingSize; | |
| // The type of the current (logical) record. | |
| GOFF::RecordType CurrentType; | |
| // Signals start of new record. | |
| bool NewLogicalRecord; | |
| // Return the number of bytes left to write until next physical record. | |
| // Please note that we maintain the total number of bytes left, not the | |
| // written size. | |
| size_t bytesToNextPhysicalRecord() { | |
| size_t Bytes = RemainingSize % GOFF::PayloadLength; | |
| return Bytes ? Bytes : GOFF::PayloadLength; | |
| } | |
| // Write the record prefix of a physical record, using the current record | |
| // type. | |
| static void writeRecordPrefix(raw_ostream &OS, GOFF::RecordType Type, | |
| size_t RemainingSize, | |
| uint8_t Flags = Rec_Continuation) { | |
| uint8_t TypeAndFlags = Flags | (Type << 4); | |
| if (RemainingSize > GOFF::RecordLength) | |
| TypeAndFlags |= Rec_Continued; | |
| OS << binaryBe(static_cast<unsigned char>(GOFF::PTVPrefix)) | |
| << binaryBe(static_cast<unsigned char>(TypeAndFlags)) | |
| << binaryBe(static_cast<unsigned char>(0)); | |
| } | |
| // Fill the last physical record of a logical record with zero bytes. | |
| void fillRecord() { | |
| assert((GetNumBytesInBuffer() <= RemainingSize) && | |
| "More bytes in buffer than expected"); | |
| size_t Remains = RemainingSize - GetNumBytesInBuffer(); | |
| if (Remains) { | |
| assert((Remains < GOFF::RecordLength) && | |
| "Attempting to fill more than one physical record"); | |
| raw_ostream::write_zeros(Remains); | |
| } | |
| flush(); | |
| assert(RemainingSize == 0 && "Not fully flushed"); | |
| assert(GetNumBytesInBuffer() == 0 && "Buffer not fully empty"); | |
| } | |
| // See raw_ostream::write_impl. | |
| void write_impl(const char *Ptr, size_t Size) override { | |
| assert((RemainingSize >= Size) && "Attempt to write too much data"); | |
| assert(RemainingSize && "Logical record overflow"); | |
| if (!(RemainingSize % GOFF::PayloadLength)) { | |
| writeRecordPrefix(OS, CurrentType, RemainingSize, | |
| NewLogicalRecord ? 0 : Rec_Continuation); | |
| NewLogicalRecord = false; | |
| } | |
| assert(!NewLogicalRecord && | |
| "New logical record not on physical record boundary"); | |
| size_t Idx = 0; | |
| while (Size > 0) { | |
| size_t BytesToWrite = bytesToNextPhysicalRecord(); | |
| if (BytesToWrite > Size) | |
| BytesToWrite = Size; | |
| OS.write(Ptr + Idx, BytesToWrite); | |
| Idx += BytesToWrite; | |
| Size -= BytesToWrite; | |
| RemainingSize -= BytesToWrite; | |
| if (Size) { | |
| writeRecordPrefix(OS, CurrentType, RemainingSize); | |
| } | |
| } | |
| } | |
| // Return the current position within the stream, not counting the bytes | |
| // currently in the buffer. | |
| uint64_t current_pos() const override { return OS.tell(); } | |
| }; | |
| class GOFFState { | |
| void writeHeader(GOFFYAML::FileHeader &FileHdr); | |
| void writeEnd(); | |
| void reportError(const Twine &Msg) { | |
| ErrHandler(Msg); | |
| HasError = true; | |
| } | |
| GOFFState(raw_ostream &OS, GOFFYAML::Object &Doc, | |
| yaml::ErrorHandler ErrHandler) | |
| : GW(OS), Doc(Doc), ErrHandler(ErrHandler), HasError(false) {} | |
| ~GOFFState() { GW.finalize(); } | |
| bool writeObject(); | |
| public: | |
| static bool writeGOFF(raw_ostream &OS, GOFFYAML::Object &Doc, | |
| yaml::ErrorHandler ErrHandler); | |
| private: | |
| GOFFOstream GW; | |
| GOFFYAML::Object &Doc; | |
| yaml::ErrorHandler ErrHandler; | |
| bool HasError; | |
| }; | |
| void GOFFState::writeHeader(GOFFYAML::FileHeader &FileHdr) { | |
| SmallString<16> CCSIDName; | |
| if (std::error_code EC = | |
| ConverterEBCDIC::convertToEBCDIC(FileHdr.CharacterSetName, CCSIDName)) | |
| reportError("Conversion error on " + FileHdr.CharacterSetName); | |
| if (CCSIDName.size() > 16) { | |
| reportError("CharacterSetName too long"); | |
| CCSIDName.resize(16); | |
| } | |
| SmallString<16> LangProd; | |
| if (std::error_code EC = ConverterEBCDIC::convertToEBCDIC( | |
| FileHdr.LanguageProductIdentifier, LangProd)) | |
| reportError("Conversion error on " + FileHdr.LanguageProductIdentifier); | |
| if (LangProd.size() > 16) { | |
| reportError("LanguageProductIdentifier too long"); | |
| LangProd.resize(16); | |
| } | |
| GW.makeNewRecord(GOFF::RT_HDR, GOFF::PayloadLength); | |
| GW << zeros(1) // Reserved | |
| << binaryBe(FileHdr.TargetEnvironment) // TargetEnvironment | |
| << binaryBe(FileHdr.TargetOperatingSystem) // TargetOperatingSystem | |
| << zeros(2) // Reserved | |
| << binaryBe(FileHdr.CCSID) // CCSID | |
| << CCSIDName // CharacterSetName | |
| << zeros(16 - CCSIDName.size()) // Fill bytes | |
| << LangProd // LanguageProductIdentifier | |
| << zeros(16 - LangProd.size()) // Fill bytes | |
| << binaryBe(FileHdr.ArchitectureLevel); // ArchitectureLevel | |
| // The module propties are optional. Figure out if we need to write them. | |
| uint16_t ModPropLen = 0; | |
| if (FileHdr.TargetSoftwareEnvironment) | |
| ModPropLen = 3; | |
| else if (FileHdr.InternalCCSID) | |
| ModPropLen = 2; | |
| if (ModPropLen) { | |
| GW << binaryBe(ModPropLen) << zeros(6); | |
| if (ModPropLen >= 2) | |
| GW << binaryBe(FileHdr.InternalCCSID.value_or(0)); | |
| if (ModPropLen >= 3) | |
| GW << binaryBe(FileHdr.TargetSoftwareEnvironment.value_or(0)); | |
| } | |
| } | |
| void GOFFState::writeEnd() { | |
| GW.makeNewRecord(GOFF::RT_END, GOFF::PayloadLength); | |
| GW << binaryBe(uint8_t(0)) // No entry point | |
| << binaryBe(uint8_t(0)) // No AMODE | |
| << zeros(3) // Reserved | |
| << binaryBe(GW.logicalRecords()); | |
| // No entry point yet. Automatically fill remaining space with zero bytes. | |
| GW.finalize(); | |
| } | |
| bool GOFFState::writeObject() { | |
| writeHeader(Doc.Header); | |
| if (HasError) | |
| return false; | |
| writeEnd(); | |
| return true; | |
| } | |
| bool GOFFState::writeGOFF(raw_ostream &OS, GOFFYAML::Object &Doc, | |
| yaml::ErrorHandler ErrHandler) { | |
| GOFFState State(OS, Doc, ErrHandler); | |
| return State.writeObject(); | |
| } | |
| } // namespace | |
| namespace llvm { | |
| namespace yaml { | |
| bool yaml2goff(llvm::GOFFYAML::Object &Doc, raw_ostream &Out, | |
| ErrorHandler ErrHandler) { | |
| return GOFFState::writeGOFF(Out, Doc, ErrHandler); | |
| } | |
| } // namespace yaml | |
| } // namespace llvm |