//===- 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 struct BinaryBeImpl { ValueType Value; BinaryBeImpl(ValueType V) : Value(V) {} }; template raw_ostream &operator<<(raw_ostream &OS, const BinaryBeImpl &BBE) { char Buffer[sizeof(BBE.Value)]; support::endian::write( Buffer, BBE.Value); OS.write(Buffer, sizeof(BBE.Value)); return OS; } template BinaryBeImpl binaryBe(ValueType V) { return BinaryBeImpl(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(GOFF::PTVPrefix)) << binaryBe(static_cast(TypeAndFlags)) << binaryBe(static_cast(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