| //===- lib/MC/GOFFObjectWriter.cpp - GOFF File Writer ---------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file implements GOFF object file writer information. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/BinaryFormat/GOFF.h" |
| #include "llvm/MC/MCAsmBackend.h" |
| #include "llvm/MC/MCAssembler.h" |
| #include "llvm/MC/MCGOFFAttributes.h" |
| #include "llvm/MC/MCGOFFObjectWriter.h" |
| #include "llvm/MC/MCSectionGOFF.h" |
| #include "llvm/MC/MCSymbolGOFF.h" |
| #include "llvm/MC/MCValue.h" |
| #include "llvm/Support/ConvertEBCDIC.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/Endian.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| using namespace llvm; |
| |
| #define DEBUG_TYPE "goff-writer" |
| |
| namespace { |
| // Common flag values on records. |
| |
| // Flag: This record is continued. |
| constexpr uint8_t RecContinued = GOFF::Flags(7, 1, 1); |
| |
| // Flag: This record is a continuation. |
| constexpr uint8_t RecContinuation = GOFF::Flags(6, 1, 1); |
| |
| // The GOFFOstream is responsible to write the data into the fixed physical |
| // records of the format. A user of this class announces the begin of a new |
| // logical record. 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. In principle, the GOFFOstream is agnostic of the endianness of |
| // the payload. However, it also supports writing data in big endian byte order. |
| // |
| // The physical records use the flag field to indicate if the there is a |
| // successor and predecessor record. To be able to set these flags while |
| // writing, the basic implementation idea is to always buffer the last seen |
| // physical record. |
| class GOFFOstream { |
| /// The underlying raw_pwrite_stream. |
| raw_pwrite_stream &OS; |
| |
| /// The number of logical records emitted so far. |
| uint32_t LogicalRecords = 0; |
| |
| /// The number of physical records emitted so far. |
| uint32_t PhysicalRecords = 0; |
| |
| /// The size of the buffer. Same as the payload size of a physical record. |
| static constexpr uint8_t BufferSize = GOFF::PayloadLength; |
| |
| /// Current position in buffer. |
| char *BufferPtr = Buffer; |
| |
| /// Static allocated buffer for the stream. |
| char Buffer[BufferSize]; |
| |
| /// The type of the current logical record, and the flags (aka continued and |
| /// continuation indicators) for the previous (physical) record. |
| uint8_t TypeAndFlags = 0; |
| |
| public: |
| GOFFOstream(raw_pwrite_stream &OS); |
| ~GOFFOstream(); |
| |
| raw_pwrite_stream &getOS() { return OS; } |
| size_t getWrittenSize() const { return PhysicalRecords * GOFF::RecordLength; } |
| uint32_t getNumLogicalRecords() { return LogicalRecords; } |
| |
| /// Write the specified bytes. |
| void write(const char *Ptr, size_t Size); |
| |
| /// Write zeroes, up to a maximum of 16 bytes. |
| void write_zeros(unsigned NumZeros); |
| |
| /// Support for endian-specific data. |
| template <typename value_type> void writebe(value_type Value) { |
| Value = |
| support::endian::byte_swap<value_type>(Value, llvm::endianness::big); |
| write((const char *)&Value, sizeof(value_type)); |
| } |
| |
| /// Begin a new logical record. Implies finalizing the previous record. |
| void newRecord(GOFF::RecordType Type); |
| |
| /// Ends a logical record. |
| void finalizeRecord(); |
| |
| private: |
| /// Updates the continued/continuation flags, and writes the record prefix of |
| /// a physical record. |
| void updateFlagsAndWritePrefix(bool IsContinued); |
| |
| /// Returns the remaining size in the buffer. |
| size_t getRemainingSize(); |
| }; |
| } // namespace |
| |
| GOFFOstream::GOFFOstream(raw_pwrite_stream &OS) : OS(OS) {} |
| |
| GOFFOstream::~GOFFOstream() { finalizeRecord(); } |
| |
| void GOFFOstream::updateFlagsAndWritePrefix(bool IsContinued) { |
| // Update the flags based on the previous state and the flag IsContinued. |
| if (TypeAndFlags & RecContinued) |
| TypeAndFlags |= RecContinuation; |
| if (IsContinued) |
| TypeAndFlags |= RecContinued; |
| else |
| TypeAndFlags &= ~RecContinued; |
| |
| OS << static_cast<unsigned char>(GOFF::PTVPrefix) // Record Type |
| << static_cast<unsigned char>(TypeAndFlags) // Continuation |
| << static_cast<unsigned char>(0); // Version |
| |
| ++PhysicalRecords; |
| } |
| |
| size_t GOFFOstream::getRemainingSize() { |
| return size_t(&Buffer[BufferSize] - BufferPtr); |
| } |
| |
| void GOFFOstream::write(const char *Ptr, size_t Size) { |
| size_t RemainingSize = getRemainingSize(); |
| |
| // Data fits into the buffer. |
| if (LLVM_LIKELY(Size <= RemainingSize)) { |
| memcpy(BufferPtr, Ptr, Size); |
| BufferPtr += Size; |
| return; |
| } |
| |
| // Otherwise the buffer is partially filled or full, and data does not fit |
| // into it. |
| updateFlagsAndWritePrefix(/*IsContinued=*/true); |
| OS.write(Buffer, size_t(BufferPtr - Buffer)); |
| if (RemainingSize > 0) { |
| OS.write(Ptr, RemainingSize); |
| Ptr += RemainingSize; |
| Size -= RemainingSize; |
| } |
| |
| while (Size > BufferSize) { |
| updateFlagsAndWritePrefix(/*IsContinued=*/true); |
| OS.write(Ptr, BufferSize); |
| Ptr += BufferSize; |
| Size -= BufferSize; |
| } |
| |
| // The remaining bytes fit into the buffer. |
| memcpy(Buffer, Ptr, Size); |
| BufferPtr = &Buffer[Size]; |
| } |
| |
| void GOFFOstream::write_zeros(unsigned NumZeros) { |
| assert(NumZeros <= 16 && "Range for zeros too large"); |
| |
| // Handle the common case first: all fits in the buffer. |
| size_t RemainingSize = getRemainingSize(); |
| if (LLVM_LIKELY(RemainingSize >= NumZeros)) { |
| memset(BufferPtr, 0, NumZeros); |
| BufferPtr += NumZeros; |
| return; |
| } |
| |
| // Otherwise some field value is cleared. |
| static char Zeros[16] = { |
| 0, |
| }; |
| write(Zeros, NumZeros); |
| } |
| |
| void GOFFOstream::newRecord(GOFF::RecordType Type) { |
| finalizeRecord(); |
| TypeAndFlags = Type << 4; |
| ++LogicalRecords; |
| } |
| |
| void GOFFOstream::finalizeRecord() { |
| if (Buffer == BufferPtr) |
| return; |
| updateFlagsAndWritePrefix(/*IsContinued=*/false); |
| OS.write(Buffer, size_t(BufferPtr - Buffer)); |
| OS.write_zeros(getRemainingSize()); |
| BufferPtr = Buffer; |
| } |
| |
| namespace { |
| // A GOFFSymbol holds all the data required for writing an ESD record. |
| class GOFFSymbol { |
| public: |
| std::string Name; |
| uint32_t EsdId; |
| uint32_t ParentEsdId; |
| uint64_t Offset = 0; // Offset of the symbol into the section. LD only. |
| // Offset is only 32 bit, the larger type is used to |
| // enable error checking. |
| GOFF::ESDSymbolType SymbolType; |
| GOFF::ESDNameSpaceId NameSpace = GOFF::ESD_NS_ProgramManagementBinder; |
| |
| GOFF::BehavioralAttributes BehavAttrs; |
| GOFF::SymbolFlags SymbolFlags; |
| uint32_t SortKey = 0; |
| uint32_t SectionLength = 0; |
| uint32_t ADAEsdId = 0; |
| uint32_t EASectionEDEsdId = 0; |
| uint32_t EASectionOffset = 0; |
| uint8_t FillByteValue = 0; |
| |
| GOFFSymbol() : EsdId(0), ParentEsdId(0) {} |
| |
| GOFFSymbol(StringRef Name, uint32_t EsdID, const GOFF::SDAttr &Attr) |
| : Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(0), |
| SymbolType(GOFF::ESD_ST_SectionDefinition) { |
| BehavAttrs.setTaskingBehavior(Attr.TaskingBehavior); |
| BehavAttrs.setBindingScope(Attr.BindingScope); |
| } |
| |
| GOFFSymbol(StringRef Name, uint32_t EsdID, uint32_t ParentEsdID, |
| const GOFF::EDAttr &Attr) |
| : Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(ParentEsdID), |
| SymbolType(GOFF::ESD_ST_ElementDefinition) { |
| this->NameSpace = Attr.NameSpace; |
| // We always set a fill byte value. |
| this->FillByteValue = Attr.FillByteValue; |
| SymbolFlags.setFillBytePresence(1); |
| SymbolFlags.setReservedQwords(Attr.ReservedQwords); |
| // TODO Do we need/should set the "mangled" flag? |
| BehavAttrs.setReadOnly(Attr.IsReadOnly); |
| BehavAttrs.setRmode(Attr.Rmode); |
| BehavAttrs.setTextStyle(Attr.TextStyle); |
| BehavAttrs.setBindingAlgorithm(Attr.BindAlgorithm); |
| BehavAttrs.setLoadingBehavior(Attr.LoadBehavior); |
| BehavAttrs.setAlignment(Attr.Alignment); |
| } |
| |
| GOFFSymbol(StringRef Name, uint32_t EsdID, uint32_t ParentEsdID, |
| GOFF::ESDNameSpaceId NameSpace, const GOFF::LDAttr &Attr) |
| : Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(ParentEsdID), |
| SymbolType(GOFF::ESD_ST_LabelDefinition), NameSpace(NameSpace) { |
| SymbolFlags.setRenameable(Attr.IsRenamable); |
| BehavAttrs.setExecutable(Attr.Executable); |
| BehavAttrs.setBindingStrength(Attr.BindingStrength); |
| BehavAttrs.setLinkageType(Attr.Linkage); |
| BehavAttrs.setAmode(Attr.Amode); |
| BehavAttrs.setBindingScope(Attr.BindingScope); |
| } |
| |
| GOFFSymbol(StringRef Name, uint32_t EsdID, uint32_t ParentEsdID, |
| const GOFF::EDAttr &EDAttr, const GOFF::PRAttr &Attr) |
| : Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(ParentEsdID), |
| SymbolType(GOFF::ESD_ST_PartReference), NameSpace(EDAttr.NameSpace) { |
| SymbolFlags.setRenameable(Attr.IsRenamable); |
| BehavAttrs.setExecutable(Attr.Executable); |
| BehavAttrs.setLinkageType(Attr.Linkage); |
| BehavAttrs.setBindingScope(Attr.BindingScope); |
| BehavAttrs.setAlignment(EDAttr.Alignment); |
| } |
| |
| GOFFSymbol(StringRef Name, uint32_t EsdID, uint32_t ParentEsdID, |
| const GOFF::ERAttr &Attr) |
| : Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(ParentEsdID), |
| SymbolType(GOFF::ESD_ST_ExternalReference), |
| NameSpace(GOFF::ESD_NS_NormalName) { |
| BehavAttrs.setExecutable(Attr.Executable); |
| BehavAttrs.setBindingStrength(Attr.BindingStrength); |
| BehavAttrs.setLinkageType(Attr.Linkage); |
| BehavAttrs.setAmode(Attr.Amode); |
| BehavAttrs.setBindingScope(Attr.BindingScope); |
| } |
| }; |
| |
| class GOFFWriter { |
| GOFFOstream OS; |
| MCAssembler &Asm; |
| MCSectionGOFF *RootSD; |
| |
| /// Saved relocation data collected in recordRelocations(). |
| std::vector<GOFFRelocationEntry> &Relocations; |
| |
| void writeHeader(); |
| void writeSymbol(const GOFFSymbol &Symbol); |
| void writeText(const MCSectionGOFF *MC); |
| void writeRelocations(); |
| void writeEnd(); |
| |
| void defineSectionSymbols(const MCSectionGOFF &Section); |
| void defineLabel(const MCSymbolGOFF &Symbol); |
| void defineExtern(const MCSymbolGOFF &Symbol); |
| void defineSymbols(); |
| |
| public: |
| GOFFWriter(raw_pwrite_stream &OS, MCAssembler &Asm, MCSectionGOFF *RootSD, |
| std::vector<GOFFRelocationEntry> &Relocations); |
| uint64_t writeObject(); |
| }; |
| } // namespace |
| |
| GOFFWriter::GOFFWriter(raw_pwrite_stream &OS, MCAssembler &Asm, |
| MCSectionGOFF *RootSD, |
| std::vector<GOFFRelocationEntry> &Relocations) |
| : OS(OS), Asm(Asm), RootSD(RootSD), Relocations(Relocations) {} |
| |
| void GOFFWriter::defineSectionSymbols(const MCSectionGOFF &Section) { |
| if (Section.isSD()) { |
| GOFFSymbol SD(Section.getName(), Section.getOrdinal(), |
| Section.getSDAttributes()); |
| writeSymbol(SD); |
| } |
| |
| if (Section.isED()) { |
| GOFFSymbol ED(Section.getName(), Section.getOrdinal(), |
| Section.getParent()->getOrdinal(), Section.getEDAttributes()); |
| ED.SectionLength = Asm.getSectionAddressSize(Section); |
| writeSymbol(ED); |
| } |
| |
| if (Section.isPR()) { |
| MCSectionGOFF *Parent = Section.getParent(); |
| GOFFSymbol PR(Section.getName(), Section.getOrdinal(), Parent->getOrdinal(), |
| Parent->getEDAttributes(), Section.getPRAttributes()); |
| PR.SectionLength = Asm.getSectionAddressSize(Section); |
| if (Section.requiresNonZeroLength()) { |
| // We cannot have a zero-length section for data. If we do, |
| // artificially inflate it. Use 2 bytes to avoid odd alignments. Note: |
| // if this is ever changed, you will need to update the code in |
| // SystemZAsmPrinter::emitCEEMAIN and SystemZAsmPrinter::emitCELQMAIN to |
| // generate -1 if there is no ADA |
| if (!PR.SectionLength) |
| PR.SectionLength = 2; |
| } |
| writeSymbol(PR); |
| } |
| } |
| |
| void GOFFWriter::defineLabel(const MCSymbolGOFF &Symbol) { |
| MCSectionGOFF &Section = static_cast<MCSectionGOFF &>(Symbol.getSection()); |
| GOFFSymbol LD(Symbol.getName(), Symbol.getIndex(), Section.getOrdinal(), |
| Section.getEDAttributes().NameSpace, |
| GOFF::LDAttr{false, Symbol.getCodeData(), |
| Symbol.getBindingStrength(), Symbol.getLinkage(), |
| GOFF::ESD_AMODE_64, Symbol.getBindingScope()}); |
| if (Symbol.getADA()) |
| LD.ADAEsdId = Symbol.getADA()->getOrdinal(); |
| LD.Offset = Asm.getSymbolOffset(Symbol); |
| writeSymbol(LD); |
| } |
| |
| void GOFFWriter::defineExtern(const MCSymbolGOFF &Symbol) { |
| GOFFSymbol ER(Symbol.getName(), Symbol.getIndex(), RootSD->getOrdinal(), |
| GOFF::ERAttr{Symbol.getCodeData(), Symbol.getBindingStrength(), |
| Symbol.getLinkage(), GOFF::ESD_AMODE_64, |
| Symbol.getBindingScope()}); |
| writeSymbol(ER); |
| } |
| |
| void GOFFWriter::defineSymbols() { |
| unsigned Ordinal = 0; |
| // Process all sections. |
| for (MCSection &S : Asm) { |
| auto &Section = static_cast<MCSectionGOFF &>(S); |
| Section.setOrdinal(++Ordinal); |
| defineSectionSymbols(Section); |
| } |
| |
| // Process all symbols |
| for (const MCSymbol &Sym : Asm.symbols()) { |
| if (Sym.isTemporary()) |
| continue; |
| auto &Symbol = static_cast<const MCSymbolGOFF &>(Sym); |
| if (!Symbol.isDefined()) { |
| Symbol.setIndex(++Ordinal); |
| defineExtern(Symbol); |
| } else if (Symbol.isInEDSection()) { |
| Symbol.setIndex(++Ordinal); |
| defineLabel(Symbol); |
| } |
| } |
| } |
| |
| void GOFFWriter::writeHeader() { |
| OS.newRecord(GOFF::RT_HDR); |
| OS.write_zeros(1); // Reserved |
| OS.writebe<uint32_t>(0); // Target Hardware Environment |
| OS.writebe<uint32_t>(0); // Target Operating System Environment |
| OS.write_zeros(2); // Reserved |
| OS.writebe<uint16_t>(0); // CCSID |
| OS.write_zeros(16); // Character Set name |
| OS.write_zeros(16); // Language Product Identifier |
| OS.writebe<uint32_t>(1); // Architecture Level |
| OS.writebe<uint16_t>(0); // Module Properties Length |
| OS.write_zeros(6); // Reserved |
| } |
| |
| void GOFFWriter::writeSymbol(const GOFFSymbol &Symbol) { |
| if (Symbol.Offset >= (((uint64_t)1) << 31)) |
| report_fatal_error("ESD offset out of range"); |
| |
| // All symbol names are in EBCDIC. |
| SmallString<256> Name; |
| ConverterEBCDIC::convertToEBCDIC(Symbol.Name, Name); |
| |
| // Check length here since this number is technically signed but we need uint |
| // for writing to records. |
| if (Name.size() >= GOFF::MaxDataLength) |
| report_fatal_error("Symbol max name length exceeded"); |
| uint16_t NameLength = Name.size(); |
| |
| OS.newRecord(GOFF::RT_ESD); |
| OS.writebe<uint8_t>(Symbol.SymbolType); // Symbol Type |
| OS.writebe<uint32_t>(Symbol.EsdId); // ESDID |
| OS.writebe<uint32_t>(Symbol.ParentEsdId); // Parent or Owning ESDID |
| OS.writebe<uint32_t>(0); // Reserved |
| OS.writebe<uint32_t>( |
| static_cast<uint32_t>(Symbol.Offset)); // Offset or Address |
| OS.writebe<uint32_t>(0); // Reserved |
| OS.writebe<uint32_t>(Symbol.SectionLength); // Length |
| OS.writebe<uint32_t>(Symbol.EASectionEDEsdId); // Extended Attribute ESDID |
| OS.writebe<uint32_t>(Symbol.EASectionOffset); // Extended Attribute Offset |
| OS.writebe<uint32_t>(0); // Reserved |
| OS.writebe<uint8_t>(Symbol.NameSpace); // Name Space ID |
| OS.writebe<uint8_t>(Symbol.SymbolFlags); // Flags |
| OS.writebe<uint8_t>(Symbol.FillByteValue); // Fill-Byte Value |
| OS.writebe<uint8_t>(0); // Reserved |
| OS.writebe<uint32_t>(Symbol.ADAEsdId); // ADA ESDID |
| OS.writebe<uint32_t>(Symbol.SortKey); // Sort Priority |
| OS.writebe<uint64_t>(0); // Reserved |
| for (auto F : Symbol.BehavAttrs.Attr) |
| OS.writebe<uint8_t>(F); // Behavioral Attributes |
| OS.writebe<uint16_t>(NameLength); // Name Length |
| OS.write(Name.data(), NameLength); // Name |
| } |
| |
| namespace { |
| /// Adapter stream to write a text section. |
| class TextStream : public raw_ostream { |
| /// The underlying GOFFOstream. |
| GOFFOstream &OS; |
| |
| /// The buffer size is the maximum number of bytes in a TXT section. |
| static constexpr size_t BufferSize = GOFF::MaxDataLength; |
| |
| /// Static allocated buffer for the stream, used by the raw_ostream class. The |
| /// buffer is sized to hold the payload of a logical TXT record. |
| char Buffer[BufferSize]; |
| |
| /// The offset for the next TXT record. This is equal to the number of bytes |
| /// written. |
| size_t Offset; |
| |
| /// The Esdid of the GOFF section. |
| const uint32_t EsdId; |
| |
| /// The record style. |
| const GOFF::ESDTextStyle RecordStyle; |
| |
| /// See raw_ostream::write_impl. |
| void write_impl(const char *Ptr, size_t Size) override; |
| |
| uint64_t current_pos() const override { return Offset; } |
| |
| public: |
| explicit TextStream(GOFFOstream &OS, uint32_t EsdId, |
| GOFF::ESDTextStyle RecordStyle) |
| : OS(OS), Offset(0), EsdId(EsdId), RecordStyle(RecordStyle) { |
| SetBuffer(Buffer, sizeof(Buffer)); |
| } |
| |
| ~TextStream() override { flush(); } |
| }; |
| } // namespace |
| |
| void TextStream::write_impl(const char *Ptr, size_t Size) { |
| size_t WrittenLength = 0; |
| |
| // We only have signed 32bits of offset. |
| if (Offset + Size > std::numeric_limits<int32_t>::max()) |
| report_fatal_error("TXT section too large"); |
| |
| while (WrittenLength < Size) { |
| size_t ToWriteLength = |
| std::min(Size - WrittenLength, size_t(GOFF::MaxDataLength)); |
| |
| OS.newRecord(GOFF::RT_TXT); |
| OS.writebe<uint8_t>(GOFF::Flags(4, 4, RecordStyle)); // Text Record Style |
| OS.writebe<uint32_t>(EsdId); // Element ESDID |
| OS.writebe<uint32_t>(0); // Reserved |
| OS.writebe<uint32_t>(static_cast<uint32_t>(Offset)); // Offset |
| OS.writebe<uint32_t>(0); // Text Field True Length |
| OS.writebe<uint16_t>(0); // Text Encoding |
| OS.writebe<uint16_t>(ToWriteLength); // Data Length |
| OS.write(Ptr + WrittenLength, ToWriteLength); // Data |
| |
| WrittenLength += ToWriteLength; |
| Offset += ToWriteLength; |
| } |
| } |
| |
| void GOFFWriter::writeText(const MCSectionGOFF *Section) { |
| // A BSS section contains only zeros, no need to write this. |
| if (Section->isBSS()) |
| return; |
| |
| TextStream S(OS, Section->getOrdinal(), Section->getTextStyle()); |
| Asm.writeSectionData(S, Section); |
| } |
| |
| namespace { |
| // RelocDataItemBuffer provides a static buffer for relocation data items. |
| class RelocDataItemBuffer { |
| char Buffer[GOFF::MaxDataLength]; |
| char *Ptr; |
| |
| public: |
| RelocDataItemBuffer() : Ptr(Buffer) {} |
| const char *data() { return Buffer; } |
| size_t size() { return Ptr - Buffer; } |
| void reset() { Ptr = Buffer; } |
| bool fits(size_t S) { return size() + S < GOFF::MaxDataLength; } |
| template <typename T> void writebe(T Val) { |
| assert(fits(sizeof(T)) && "Out-of-bounds write"); |
| support::endian::write<T, llvm::endianness::big>(Ptr, Val); |
| Ptr += sizeof(T); |
| } |
| }; |
| } // namespace |
| |
| void GOFFWriter::writeRelocations() { |
| // Set the IDs in the relocation entries. |
| for (auto &RelocEntry : Relocations) { |
| auto GetRptr = [](const MCSymbolGOFF *Sym) -> uint32_t { |
| if (Sym->isTemporary()) |
| return static_cast<MCSectionGOFF &>(Sym->getSection()) |
| .getBeginSymbol() |
| ->getIndex(); |
| return Sym->getIndex(); |
| }; |
| |
| RelocEntry.PEsdId = RelocEntry.Pptr->getOrdinal(); |
| RelocEntry.REsdId = GetRptr(RelocEntry.Rptr); |
| } |
| |
| // Sort relocation data items by the P pointer to save space. |
| std::sort( |
| Relocations.begin(), Relocations.end(), |
| [](const GOFFRelocationEntry &Left, const GOFFRelocationEntry &Right) { |
| return std::tie(Left.PEsdId, Left.REsdId, Left.POffset) < |
| std::tie(Right.PEsdId, Right.REsdId, Right.POffset); |
| }); |
| |
| // Construct the compressed relocation data items, and write them out. |
| RelocDataItemBuffer Buffer; |
| for (auto I = Relocations.begin(), E = Relocations.end(); I != E;) { |
| Buffer.reset(); |
| |
| uint32_t PrevResdId = -1; |
| uint32_t PrevPesdId = -1; |
| uint64_t PrevPOffset = -1; |
| for (; I != E; ++I) { |
| const GOFFRelocationEntry &Rel = *I; |
| |
| bool SameREsdId = (Rel.REsdId == PrevResdId); |
| bool SamePEsdId = (Rel.PEsdId == PrevPesdId); |
| bool SamePOffset = (Rel.POffset == PrevPOffset); |
| bool EightByteOffset = ((Rel.POffset >> 32) & 0xffffffff); |
| |
| // Calculate size of relocation data item, and check if it still fits into |
| // the record. |
| size_t ItemSize = 8; // Smallest size of a relocation data item. |
| if (!SameREsdId) |
| ItemSize += 4; |
| if (!SamePEsdId) |
| ItemSize += 4; |
| if (!SamePOffset) |
| ItemSize += (EightByteOffset ? 8 : 4); |
| if (!Buffer.fits(ItemSize)) |
| break; |
| |
| GOFF::Flags RelocFlags[6]; |
| RelocFlags[0].set(0, 1, SameREsdId); |
| RelocFlags[0].set(1, 1, SamePEsdId); |
| RelocFlags[0].set(2, 1, SamePOffset); |
| RelocFlags[0].set(6, 1, EightByteOffset); |
| |
| RelocFlags[1].set(0, 4, Rel.ReferenceType); |
| RelocFlags[1].set(4, 4, Rel.ReferentType); |
| |
| RelocFlags[2].set(0, 7, Rel.Action); |
| RelocFlags[2].set(7, 1, Rel.FetchStore); |
| |
| RelocFlags[4].set(0, 8, Rel.TargetLength); |
| |
| for (auto F : RelocFlags) |
| Buffer.writebe<uint8_t>(F); |
| Buffer.writebe<uint16_t>(0); // Reserved. |
| if (!SameREsdId) |
| Buffer.writebe<uint32_t>(Rel.REsdId); |
| if (!SamePEsdId) |
| Buffer.writebe<uint32_t>(Rel.PEsdId); |
| if (!SamePOffset) { |
| if (EightByteOffset) |
| Buffer.writebe<uint64_t>(Rel.POffset); |
| else |
| Buffer.writebe<uint32_t>(Rel.POffset); |
| } |
| |
| PrevResdId = Rel.REsdId; |
| PrevPesdId = Rel.PEsdId; |
| PrevPOffset = Rel.POffset; |
| } |
| |
| OS.newRecord(GOFF::RT_RLD); |
| OS.writebe<uint8_t>(0); // Reserved. |
| OS.writebe<uint16_t>(Buffer.size()); // Length (of the relocation data). |
| OS.write(Buffer.data(), Buffer.size()); // Relocation Directory Data Items. |
| } |
| } |
| |
| void GOFFWriter::writeEnd() { |
| uint8_t F = GOFF::END_EPR_None; |
| uint8_t AMODE = 0; |
| uint32_t ESDID = 0; |
| |
| // TODO Set Flags/AMODE/ESDID for entry point. |
| |
| OS.newRecord(GOFF::RT_END); |
| OS.writebe<uint8_t>(GOFF::Flags(6, 2, F)); // Indicator flags |
| OS.writebe<uint8_t>(AMODE); // AMODE |
| OS.write_zeros(3); // Reserved |
| // The record count is the number of logical records. In principle, this value |
| // is available as OS.logicalRecords(). However, some tools rely on this field |
| // being zero. |
| OS.writebe<uint32_t>(0); // Record Count |
| OS.writebe<uint32_t>(ESDID); // ESDID (of entry point) |
| } |
| |
| uint64_t GOFFWriter::writeObject() { |
| writeHeader(); |
| |
| defineSymbols(); |
| |
| for (const MCSection &Section : Asm) |
| writeText(static_cast<const MCSectionGOFF *>(&Section)); |
| |
| writeRelocations(); |
| |
| writeEnd(); |
| |
| // Make sure all records are written. |
| OS.finalizeRecord(); |
| |
| LLVM_DEBUG(dbgs() << "Wrote " << OS.getNumLogicalRecords() |
| << " logical records."); |
| |
| return OS.getWrittenSize(); |
| } |
| |
| GOFFObjectWriter::GOFFObjectWriter( |
| std::unique_ptr<MCGOFFObjectTargetWriter> MOTW, raw_pwrite_stream &OS) |
| : TargetObjectWriter(std::move(MOTW)), OS(OS) {} |
| |
| GOFFObjectWriter::~GOFFObjectWriter() = default; |
| |
| void GOFFObjectWriter::recordRelocation(const MCFragment &F, |
| const MCFixup &Fixup, MCValue Target, |
| uint64_t &FixedValue) { |
| const MCFixupKindInfo &FKI = |
| Asm->getBackend().getFixupKindInfo(Fixup.getKind()); |
| const uint32_t Length = FKI.TargetSize / 8; |
| assert(FKI.TargetSize % 8 == 0 && "Target Size not multiple of 8"); |
| const uint64_t FixupOffset = Asm->getFragmentOffset(F) + Fixup.getOffset(); |
| |
| unsigned RelocType = TargetObjectWriter->getRelocType(Target, Fixup); |
| |
| const MCSectionGOFF *PSection = static_cast<MCSectionGOFF *>(F.getParent()); |
| const auto &A = *static_cast<const MCSymbolGOFF *>(Target.getAddSym()); |
| const MCSymbolGOFF *B = static_cast<const MCSymbolGOFF *>(Target.getSubSym()); |
| if (RelocType == MCGOFFObjectTargetWriter::Reloc_Type_RICon) { |
| if (A.isUndefined()) { |
| Asm->reportError( |
| Fixup.getLoc(), |
| Twine("symbol ") |
| .concat(A.getName()) |
| .concat(" must be defined for a relative immediate relocation")); |
| return; |
| } |
| if (&A.getSection() != PSection) { |
| Asm->reportError(Fixup.getLoc(), |
| Twine("relative immediate relocation section mismatch: ") |
| .concat(A.getSection().getName()) |
| .concat(" of symbol ") |
| .concat(A.getName()) |
| .concat(" <-> ") |
| .concat(PSection->getName())); |
| return; |
| } |
| if (B) { |
| Asm->reportError( |
| Fixup.getLoc(), |
| Twine("subtractive symbol ") |
| .concat(B->getName()) |
| .concat(" not supported for a relative immediate relocation")); |
| return; |
| } |
| FixedValue = Asm->getSymbolOffset(A) - FixupOffset + Target.getConstant(); |
| return; |
| } |
| FixedValue = Target.getConstant(); |
| |
| // The symbol only has a section-relative offset if it is a temporary symbol. |
| FixedValue += A.isTemporary() ? Asm->getSymbolOffset(A) : 0; |
| A.setUsedInReloc(); |
| if (B) { |
| FixedValue -= B->isTemporary() ? Asm->getSymbolOffset(*B) : 0; |
| B->setUsedInReloc(); |
| } |
| |
| // UseQCon causes class offsets versus absolute addresses to be used. This |
| // is analogous to using QCONs in older OBJ object file format. |
| bool UseQCon = RelocType == MCGOFFObjectTargetWriter::Reloc_Type_QCon; |
| |
| GOFF::RLDFetchStore FetchStore = |
| (RelocType == MCGOFFObjectTargetWriter::Reloc_Type_RCon || |
| RelocType == MCGOFFObjectTargetWriter::Reloc_Type_VCon) |
| ? GOFF::RLDFetchStore::RLD_FS_Store |
| : GOFF::RLDFetchStore::RLD_FS_Fetch; |
| assert((FetchStore == GOFF::RLDFetchStore::RLD_FS_Fetch || B == nullptr) && |
| "No dependent relocations expected"); |
| |
| enum GOFF::RLDReferenceType ReferenceType = GOFF::RLD_RT_RAddress; |
| enum GOFF::RLDReferentType ReferentType = GOFF::RLD_RO_Label; |
| if (UseQCon) { |
| ReferenceType = GOFF::RLD_RT_ROffset; |
| ReferentType = GOFF::RLD_RO_Class; |
| } |
| if (RelocType == MCGOFFObjectTargetWriter::Reloc_Type_RCon) |
| ReferenceType = GOFF::RLD_RT_RTypeConstant; |
| |
| auto DumpReloc = [&PSection, &ReferenceType, &FixupOffset, |
| &FixedValue](const char *N, const MCSymbolGOFF *Sym) { |
| const char *Con; |
| switch (ReferenceType) { |
| case GOFF::RLDReferenceType::RLD_RT_RAddress: |
| Con = "ACon"; |
| break; |
| case GOFF::RLDReferenceType::RLD_RT_ROffset: |
| Con = "QCon"; |
| break; |
| case GOFF::RLDReferenceType::RLD_RT_RTypeConstant: |
| Con = "VCon"; |
| break; |
| default: |
| Con = "(unknown)"; |
| } |
| dbgs() << "Reloc " << N << ": " << Con << " Rptr: " << Sym->getName() |
| << " Pptr: " << PSection->getName() << " Offset: " << FixupOffset |
| << " Fixed Imm: " << FixedValue << "\n"; |
| }; |
| (void)DumpReloc; |
| |
| // Save relocation data for later writing. |
| LLVM_DEBUG(DumpReloc("A", &A)); |
| Relocations.emplace_back(PSection, &A, ReferenceType, ReferentType, |
| GOFF::RLD_ACT_Add, FetchStore, FixupOffset, Length); |
| if (B) { |
| LLVM_DEBUG(DumpReloc("B", B)); |
| Relocations.emplace_back( |
| PSection, B, ReferenceType, ReferentType, GOFF::RLD_ACT_Subtract, |
| GOFF::RLDFetchStore::RLD_FS_Fetch, FixupOffset, Length); |
| } |
| } |
| |
| uint64_t GOFFObjectWriter::writeObject() { |
| uint64_t Size = GOFFWriter(OS, *Asm, RootSD, Relocations).writeObject(); |
| return Size; |
| } |
| |
| std::unique_ptr<MCObjectWriter> |
| llvm::createGOFFObjectWriter(std::unique_ptr<MCGOFFObjectTargetWriter> MOTW, |
| raw_pwrite_stream &OS) { |
| return std::make_unique<GOFFObjectWriter>(std::move(MOTW), OS); |
| } |