| //===- 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/ADT/IndexedMap.h" |
| #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, llvm::endianness::big, support::unaligned>( |
| Buffer, BBE.Value); |
| 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() { 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 << 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 ? *FileHdr.InternalCCSID : 0); |
| if (ModPropLen >= 3) |
| GW << binaryBe(FileHdr.TargetSoftwareEnvironment |
| ? *FileHdr.TargetSoftwareEnvironment |
| : 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 |