blob: 3a18575f9757f782fb256576204cba44b14f8e23 [file] [edit]
//===- SystemZHLASMAsmStreamer.cpp - HLASM Assembly Text Output -----------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "SystemZHLASMAsmStreamer.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/BinaryFormat/GOFF.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCSectionGOFF.h"
#include "llvm/MC/MCSymbolGOFF.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Signals.h"
#include <sstream>
using namespace llvm;
void SystemZHLASMAsmStreamer::visitUsedSymbol(const MCSymbol &Sym) {
Assembler->registerSymbol(Sym);
}
void SystemZHLASMAsmStreamer::EmitEOL() {
// Comments are emitted on a new line before the instruction.
if (IsVerboseAsm)
EmitComment();
std::istringstream Stream(Str);
SmallVector<std::string> Lines;
std::string Line;
while (std::getline(Stream, Line, '\n'))
Lines.push_back(Line);
for (auto S : Lines) {
if (LLVM_LIKELY(S.length() < ContIndicatorColumn)) {
FOS << S;
// Each line in HLASM must fill the full 80 characters.
FOS.PadToColumn(InstLimit);
FOS << "\n";
} else {
// If last character before end of the line is not a space
// we must insert an additional non-space character that
// is not part of the statement coding. We just reuse
// the existing character by making the new substring start
// 1 character sooner, thus "duplicating" that character
// If The last character is a space. We insert an X instead.
std::string TmpSubStr = S.substr(0, ContIndicatorColumn);
if (!TmpSubStr.compare(ContIndicatorColumn - 1, 1, " "))
TmpSubStr.replace(ContIndicatorColumn - 1, 1, "X");
FOS << TmpSubStr;
FOS.PadToColumn(InstLimit);
FOS << "\n";
size_t Emitted = ContIndicatorColumn - 1;
while (Emitted < S.length()) {
if ((S.length() - Emitted) < ContLen)
TmpSubStr = S.substr(Emitted, S.length());
else {
TmpSubStr = S.substr(Emitted, ContLen);
if (!TmpSubStr.compare(ContLen - 1, 1, " "))
TmpSubStr.replace(ContLen - 1, 1, "X");
}
FOS.PadToColumn(ContStartColumn);
FOS << TmpSubStr;
FOS.PadToColumn(InstLimit);
FOS << "\n";
Emitted += ContLen - 1;
}
}
}
Str.clear();
}
static void emitXATTR(raw_ostream &OS, StringRef Name, MCSectionGOFF *ADA,
bool IsIndirectReference, GOFF::ESDLinkageType Linkage,
GOFF::ESDExecutable Executable,
GOFF::ESDBindingScope BindingScope) {
llvm::ListSeparator Sep(",");
OS << Name << " XATTR ";
OS << Sep << "LINKAGE(" << (Linkage == GOFF::ESD_LT_OS ? "OS" : "XPLINK")
<< ")";
const bool NotUnspecified = (Executable != GOFF::ESD_EXE_Unspecified);
if (NotUnspecified || IsIndirectReference) {
OS << Sep << "REFERENCE(";
llvm::ListSeparator SepRef(",");
if (NotUnspecified)
OS << SepRef << (Executable == GOFF::ESD_EXE_CODE ? "CODE" : "DATA");
if (IsIndirectReference)
OS << SepRef << "INDIRECT";
OS << ")";
}
// Emit PSECT only for code symbols.
if (ADA && Executable != GOFF::ESD_EXE_DATA)
OS << Sep << "PSECT(" << ADA->getName() << ")";
if (BindingScope != GOFF::ESD_BSC_Unspecified) {
OS << Sep << "SCOPE(";
switch (BindingScope) {
case GOFF::ESD_BSC_Section:
OS << "SECTION";
break;
case GOFF::ESD_BSC_Module:
OS << "MODULE";
break;
case GOFF::ESD_BSC_Library:
OS << "LIBRARY";
break;
case GOFF::ESD_BSC_ImportExport:
OS << "EXPORT";
break;
default:
break;
}
OS << ')';
}
}
static void emitCATTR(raw_ostream &OS, StringRef Name, GOFF::ESDRmode Rmode,
GOFF::ESDAlignment Alignment,
GOFF::ESDLoadingBehavior LoadBehavior,
GOFF::ESDExecutable Executable, bool IsReadOnly,
uint32_t SortKey, uint8_t FillByteValue,
StringRef PartName) {
OS << Name << " CATTR ";
OS << "ALIGN(" << static_cast<unsigned>(Alignment) << "),"
<< "FILL(" << static_cast<unsigned>(FillByteValue) << ")";
switch (LoadBehavior) {
case GOFF::ESD_LB_Deferred:
OS << ",DEFLOAD";
break;
case GOFF::ESD_LB_NoLoad:
OS << ",NOLOAD";
break;
default:
break;
}
switch (Executable) {
case GOFF::ESD_EXE_CODE:
OS << ",EXECUTABLE";
break;
case GOFF::ESD_EXE_DATA:
OS << ",NOTEXECUTABLE";
break;
default:
break;
}
if (IsReadOnly)
OS << ",READONLY";
if (Rmode != GOFF::ESD_RMODE_None) {
OS << ',';
OS << "RMODE(";
switch (Rmode) {
case GOFF::ESD_RMODE_None:
llvm_unreachable("");
case GOFF::ESD_RMODE_24:
OS << "24";
break;
case GOFF::ESD_RMODE_31:
OS << "31";
break;
case GOFF::ESD_RMODE_64:
OS << "64";
break;
}
OS << ')';
}
if (SortKey)
OS << ",PRIORITY(" << SortKey << ")";
if (!PartName.empty())
OS << ",PART(" << PartName << ")";
OS << '\n';
}
static void emitALIAS(raw_ostream &OS, StringRef Name, StringRef ExternalName) {
OS << Name << " ALIAS C'";
for (char C : ExternalName) {
if (C == '\'' || C == '&')
OS << C;
OS << C;
}
OS << "'\n";
}
void SystemZHLASMAsmStreamer::changeSection(MCSection *Section,
uint32_t Subsection) {
auto &Sec = *static_cast<MCSectionGOFF *>(Section);
auto EmitExternalName = [&Sec, this]() {
if (Sec.hasExternalName())
emitALIAS(OS, Sec.getName(), Sec.getExternalName());
};
switch (Sec.getSymbolType()) {
case GOFF::ESD_ST_SectionDefinition: {
OS << Sec.getName() << " CSECT\n";
Sec.setEmitted();
EmitExternalName();
break;
}
case GOFF::ESD_ST_ElementDefinition: {
changeSection(Sec.getParent(), Subsection);
if (!Sec.isEmitted()) {
GOFF::EDAttr ED = Sec.getEDAttributes();
emitCATTR(OS, Sec.getName(), ED.Rmode, Sec.getEDAlignment(),
ED.LoadBehavior, GOFF::ESD_EXE_Unspecified, ED.IsReadOnly, 0,
ED.FillByteValue, StringRef());
if (auto *BeginSym = static_cast<MCSymbolGOFF *>(Sec.getBeginSymbol())) {
if (BeginSym->getADA()) {
emitXATTR(OS, BeginSym->getName(), BeginSym->getADA(),
/*IsIndirectReference=*/false, GOFF::ESD_LT_XPLink,
GOFF::ESD_EXE_Unspecified, GOFF::ESD_BSC_Section);
OS << '\n';
}
}
Sec.setEmitted();
EmitExternalName();
} else
OS << Sec.getName() << " CATTR\n";
break;
}
case GOFF::ESD_ST_PartReference: {
MCSectionGOFF *ED = Sec.getParent();
changeSection(ED->getParent(), Subsection);
if (!Sec.isEmitted()) {
GOFF::EDAttr EDAttr = ED->getEDAttributes();
GOFF::PRAttr PRAttr = Sec.getPRAttributes();
emitCATTR(OS, ED->getName(), EDAttr.Rmode, ED->getEDAlignment(),
EDAttr.LoadBehavior, PRAttr.Executable, EDAttr.IsReadOnly,
PRAttr.SortKey, EDAttr.FillByteValue, Sec.getName());
MCSectionGOFF *ADA =
Sec.getBeginSymbol() != nullptr
? static_cast<MCSymbolGOFF *>(Sec.getBeginSymbol())->getADA()
: nullptr;
emitXATTR(OS, Sec.getName(), ADA, /*IsIndirectReference=*/false,
PRAttr.Linkage, PRAttr.Executable, PRAttr.BindingScope);
OS << '\n';
ED->setEmitted();
Sec.setEmitted();
EmitExternalName();
} else
OS << ED->getName() << " CATTR PART(" << Sec.getName() << ")\n";
break;
}
default:
llvm_unreachable("Wrong section type");
}
MCStreamer::changeSection(Section, Subsection);
EmitEOL();
}
void SystemZHLASMAsmStreamer::emitAlignmentDS(uint64_t ByteAlignment,
std::optional<int64_t> Value,
unsigned ValueSize,
unsigned MaxBytesToEmit) {
if (!isPowerOf2_64(ByteAlignment))
report_fatal_error("Only power-of-two alignments are supported ");
OS << " DS 0";
switch (ValueSize) {
default:
llvm_unreachable("Invalid size for machine code value!");
case 1:
OS << "B";
break;
case 2:
OS << "H";
break;
case 4:
OS << "F";
break;
case 8:
OS << "D";
break;
case 16:
OS << "Q";
break;
}
EmitEOL();
}
void SystemZHLASMAsmStreamer::emitRawComment(const Twine &T, bool TabPrefix) {
OS << MAI->getCommentString() << T;
EmitEOL();
}
void SystemZHLASMAsmStreamer::AddComment(const Twine &T, bool EOL) {
if (!IsVerboseAsm)
return;
T.toVector(CommentToEmit);
if (EOL)
CommentToEmit.push_back('\n'); // Place comment in a new line.
}
void SystemZHLASMAsmStreamer::EmitComment() {
if (CommentToEmit.empty() && CommentStream.GetNumBytesInBuffer() == 0)
return;
StringRef Comments = CommentToEmit;
assert(Comments.back() == '\n' && "Comment array not newline terminated");
do {
// Emit a line of comments, but not exceeding 80 characters.
size_t Position = std::min(InstLimit - 2, Comments.find('\n'));
FOS << MAI->getCommentString() << ' ' << Comments.substr(0, Position)
<< '\n';
if (Comments[Position] == '\n')
Position++;
Comments = Comments.substr(Position);
} while (!Comments.empty());
CommentToEmit.clear();
}
void SystemZHLASMAsmStreamer::emitValueToAlignment(Align Alignment,
int64_t Fill,
uint8_t FillLen,
unsigned MaxBytesToEmit) {
emitAlignmentDS(Alignment.value(), Fill, FillLen, MaxBytesToEmit);
}
void SystemZHLASMAsmStreamer::emitCodeAlignment(Align Alignment,
const MCSubtargetInfo &STI,
unsigned MaxBytesToEmit) {
// Emit with a text fill value.
if (MAI->getTextAlignFillValue())
emitAlignmentDS(Alignment.value(), MAI->getTextAlignFillValue(), 1,
MaxBytesToEmit);
else
emitAlignmentDS(Alignment.value(), std::nullopt, 1, MaxBytesToEmit);
}
void SystemZHLASMAsmStreamer::emitBytes(StringRef Data) {
assert(getCurrentSectionOnly() &&
"Cannot emit contents before setting section!");
if (Data.empty())
return;
OS << " DC ";
size_t Len = Data.size();
SmallVector<uint8_t> Chars;
Chars.resize(Len);
OS << "XL" << Len;
uint32_t Index = 0;
for (uint8_t C : Data) {
Chars[Index] = C;
Index++;
}
OS << '\'' << toHex(Chars) << '\'';
EmitEOL();
}
void SystemZHLASMAsmStreamer::emitFill(const MCExpr &NumBytes,
uint64_t FillValue, SMLoc Loc) {
int64_t Count;
if (!NumBytes.evaluateAsAbsolute(Count)) {
getContext().reportError(Loc, "fill length must be absolute in HLASM");
return;
}
if (Count < 0) {
getContext().reportError(Loc, "invalid number of bytes");
return;
}
if (Count > 0x7FFFFFFF) {
getContext().reportError(Loc, "fill length too large for HLASM");
return;
}
if (Count == 0)
return;
OS << " DC " << Count << "XL1'"
<< format_hex_no_prefix(FillValue & 0xff, 2, true) << '\'';
EmitEOL();
}
void SystemZHLASMAsmStreamer::addEncodingComment(const MCInst &Inst,
const MCSubtargetInfo &STI) {
raw_ostream &OS = getCommentOS();
SmallString<256> Code;
SmallVector<MCFixup, 4> Fixups;
// If we have no code emitter, don't emit code.
if (!getAssembler().getEmitterPtr())
return;
getAssembler().getEmitter().encodeInstruction(Inst, Code, Fixups, STI);
// If we are showing fixups, create symbolic markers in the encoded
// representation. We do this by making a per-bit map to the fixup item index,
// then trying to display it as nicely as possible.
SmallVector<uint8_t, 64> FixupMap;
FixupMap.resize(Code.size() * 8);
for (unsigned I = 0, E = Code.size() * 8; I != E; ++I)
FixupMap[I] = 0;
for (unsigned I = 0, E = Fixups.size(); I != E; ++I) {
MCFixup &F = Fixups[I];
MCFixupKindInfo Info =
getAssembler().getBackend().getFixupKindInfo(F.getKind());
for (unsigned J = 0; J != Info.TargetSize; ++J) {
unsigned Index = F.getOffset() * 8 + Info.TargetOffset + J;
assert(Index < Code.size() * 8 && "Invalid offset in fixup!");
FixupMap[Index] = 1 + I;
}
}
OS << "encoding: [";
for (unsigned I = 0, E = Code.size(); I != E; ++I) {
if (I)
OS << ',';
// See if all bits are the same map entry.
uint8_t MapEntry = FixupMap[I * 8 + 0];
for (unsigned J = 1; J != 8; ++J) {
if (FixupMap[I * 8 + J] == MapEntry)
continue;
MapEntry = uint8_t(~0U);
break;
}
if (MapEntry != uint8_t(~0U)) {
if (MapEntry == 0) {
OS << format("0x%02x", uint8_t(Code[I]));
} else {
if (Code[I]) {
// FIXME: Some of the 8 bits require fix up.
OS << format("0x%02x", uint8_t(Code[I])) << '\''
<< char('A' + MapEntry - 1) << '\'';
} else
OS << char('A' + MapEntry - 1);
}
} else {
// Otherwise, write out in binary.
OS << "0b";
for (unsigned J = 8; J--;) {
unsigned Bit = (Code[I] >> J) & 1;
unsigned FixupBit = I * 8 + (7 - J);
if (uint8_t MapEntry = FixupMap[FixupBit]) {
assert(Bit == 0 && "Encoder wrote into fixed up bit!");
OS << char('A' + MapEntry - 1);
} else
OS << Bit;
}
}
}
OS << "]\n";
for (unsigned I = 0, E = Fixups.size(); I != E; ++I) {
MCFixup &F = Fixups[I];
OS << " fixup " << char('A' + I) << " - "
<< "offset: " << F.getOffset() << ", value: ";
MAI->printExpr(OS, *F.getValue());
auto Kind = F.getKind();
if (mc::isRelocation(Kind))
OS << ", relocation type: " << Kind;
else {
OS << ", kind: ";
auto Info = getAssembler().getBackend().getFixupKindInfo(Kind);
if (F.isPCRel() && StringRef(Info.Name).starts_with("FK_Data_"))
OS << "FK_PCRel_" << (Info.TargetSize / 8);
else
OS << Info.Name;
}
OS << "\n";
}
}
void SystemZHLASMAsmStreamer::emitInstruction(const MCInst &Inst,
const MCSubtargetInfo &STI) {
// Show the encoding in a comment if we have a code emitter.
addEncodingComment(Inst, STI);
EmitEOL();
InstPrinter->printInst(&Inst, 0, "", STI, OS);
EmitEOL();
}
void SystemZHLASMAsmStreamer::emitLabel(MCSymbol *Symbol, SMLoc Loc) {
MCSymbolGOFF *Sym = static_cast<MCSymbolGOFF *>(Symbol);
MCStreamer::emitLabel(Sym, Loc);
// Emit label and ENTRY statement only if not implied by CSECT. Do not emit a
// label if the symbol is on a PR section.
bool EmitLabelAndEntry =
!static_cast<MCSectionGOFF *>(getCurrentSectionOnly())->isPR();
if (!Sym->isTemporary() && Sym->isInEDSection()) {
EmitLabelAndEntry =
Sym->getName() !=
static_cast<MCSectionGOFF &>(Sym->getSection()).getParent()->getName();
if (EmitLabelAndEntry) {
OS << " ENTRY " << Sym->getName();
EmitEOL();
}
emitXATTR(OS, Sym->getName(), Sym->getADA(), Sym->isIndirect(),
Sym->getLinkage(), Sym->getCodeData(), Sym->getBindingScope());
EmitEOL();
if (Sym->hasExternalName())
emitALIAS(OS, Sym->getName(), Sym->getExternalName());
}
if (EmitLabelAndEntry) {
OS << Sym->getName() << " DS 0H";
EmitEOL();
}
}
bool SystemZHLASMAsmStreamer::emitSymbolAttribute(MCSymbol *Sym,
MCSymbolAttr Attribute) {
return static_cast<MCSymbolGOFF *>(Sym)->setSymbolAttribute(Attribute);
}
void SystemZHLASMAsmStreamer::emitCommonSymbol(MCSymbol *S, uint64_t Size,
Align ByteAlignment) {
auto *Symbol = static_cast<MCSymbolGOFF *>(S);
MCSectionGOFF *Section =
Symbol->getSectionForCommonSymbol(getContext(), ByteAlignment);
pushSection();
switchSection(Section);
emitLabel(Symbol, SMLoc());
emitZeros(Size);
popSection();
}
void SystemZHLASMAsmStreamer::emitRawTextImpl(StringRef String) {
String.consume_back("\n");
OS << String;
EmitEOL();
}
// Slight duplicate of MCExpr::print due to HLASM only recognizing limited
// arithmetic operators (+-*/).
void SystemZHLASMAsmStreamer::emitHLASMValueImpl(const MCExpr *Value,
unsigned Size, bool Parens) {
switch (Value->getKind()) {
case MCExpr::Constant: {
int64_t Val = cast<MCConstantExpr>(Value)->getValue();
OS << "XL" << Size << '\''
<< format_hex_no_prefix(Val, Size * 2, /*Upper=*/true) << '\'';
return;
}
case MCExpr::Binary: {
const MCBinaryExpr &BE = cast<MCBinaryExpr>(*Value);
int64_t Const;
// Or is handled differently.
if (BE.getOpcode() == MCBinaryExpr::Or) {
emitHLASMValueImpl(BE.getLHS(), Size, true);
OS << ',';
emitHLASMValueImpl(BE.getRHS(), Size, true);
return;
}
if (Parens)
OS << "AD(";
emitHLASMValueImpl(BE.getLHS(), Size);
switch (BE.getOpcode()) {
case MCBinaryExpr::LShr: {
Const = cast<MCConstantExpr>(BE.getRHS())->getValue();
OS << '/' << (1 << Const);
if (Parens)
OS << ')';
return;
}
case MCBinaryExpr::Add:
OS << '+';
break;
case MCBinaryExpr::Div:
OS << '/';
break;
case MCBinaryExpr::Mul:
OS << '*';
break;
case MCBinaryExpr::Sub:
OS << '-';
break;
default:
getContext().reportError(SMLoc(),
"Unrecognized HLASM arithmetic expression!");
}
emitHLASMValueImpl(BE.getRHS(), Size);
if (Parens)
OS << ')';
return;
}
case MCExpr::Target:
MAI->printExpr(OS, *Value);
return;
default:
Parens &= isa<MCSymbolRefExpr>(Value);
if (Parens)
OS << "AD(";
MAI->printExpr(OS, *Value);
if (Parens)
OS << ')';
return;
}
}
void SystemZHLASMAsmStreamer::emitValueImpl(const MCExpr *Value, unsigned Size,
SMLoc Loc) {
assert(Size <= 8 && "Invalid size");
assert(getCurrentSectionOnly() &&
"Cannot emit contents before setting section!");
MCStreamer::emitValueImpl(Value, Size, Loc);
OS << " DC ";
emitHLASMValueImpl(Value, Size, true);
EmitEOL();
}
void SystemZHLASMAsmStreamer::finishImpl() {
for (auto &Symbol : getAssembler().symbols()) {
if (Symbol.isTemporary() || !Symbol.isRegistered() || Symbol.isDefined())
continue;
auto &Sym = static_cast<MCSymbolGOFF &>(const_cast<MCSymbol &>(Symbol));
if (Sym.getCodeData() == GOFF::ESD_EXE_DATA) {
OS << Sym.getADA()->getParent()->getExternalName() << " CATTR PART("
<< Sym.getName() << ")";
EmitEOL();
} else {
OS << " " << (Sym.isWeak() ? "WXTRN" : "EXTRN") << " " << Sym.getName();
EmitEOL();
}
emitXATTR(OS, Sym.getName(), Sym.getADA(), Sym.isIndirect(),
Sym.getLinkage(), Sym.getCodeData(), Sym.getBindingScope());
EmitEOL();
if (Sym.hasExternalName())
emitALIAS(OS, Sym.getName(), Sym.getExternalName());
}
// Finish the assembly output.
OS << " END";
EmitEOL();
}