blob: 3b629cdb87fc1dce62021bf8dee0415e4c67a003 [file] [log] [blame]
//===- 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/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/Casting.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);
}
};
class GOFFWriter {
GOFFOstream OS;
MCAssembler &Asm;
void writeHeader();
void writeSymbol(const GOFFSymbol &Symbol);
void writeText(const MCSectionGOFF *MC);
void writeEnd();
void defineSectionSymbols(const MCSectionGOFF &Section);
void defineLabel(const MCSymbolGOFF &Symbol);
void defineSymbols();
public:
GOFFWriter(raw_pwrite_stream &OS, MCAssembler &Asm);
uint64_t writeObject();
};
} // namespace
GOFFWriter::GOFFWriter(raw_pwrite_stream &OS, MCAssembler &Asm)
: OS(OS), Asm(Asm) {}
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, Symbol.getLDAttributes());
if (Symbol.getADA())
LD.ADAEsdId = Symbol.getADA()->getOrdinal();
writeSymbol(LD);
}
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.hasLDAttributes()) {
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() { 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);
}
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));
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() {}
uint64_t GOFFObjectWriter::writeObject() {
uint64_t Size = GOFFWriter(OS, *Asm).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);
}