Skip to content
Merged
196 changes: 191 additions & 5 deletions llvm/lib/MC/MCSFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,33 @@ using namespace sframe;

namespace {

// High-level structure to track info needed to emit a
// sframe_frame_row_entry_addrX. On disk these have both a fixed portion of type
// sframe_frame_row_entry_addrX and trailing data of X * S bytes, where X is the
// datum size, and S is 1, 2, or 3 depending on which of CFA, SP, and FP are
// being tracked.
struct SFrameFRE {
// An FRE describes how to find the registers when the PC is at this
// Label from function start.
const MCSymbol *Label = nullptr;
size_t CFAOffset = 0;
size_t FPOffset = 0;
size_t RAOffset = 0;
bool FromFP = false;
bool CFARegSet = false;

SFrameFRE(const MCSymbol *Start) : Label(Start) {}
};

// High-level structure to track info needed to emit a sframe_func_desc_entry
// and its associated FREs.
struct SFrameFDE {
// Reference to the original dwarf frame to avoid copying.
const MCDwarfFrameInfo &DFrame;
// Label where this FDE's FREs start.
MCSymbol *FREStart;
// Unwinding fres
SmallVector<SFrameFRE> FREs;

SFrameFDE(const MCDwarfFrameInfo &DF, MCSymbol *FRES)
: DFrame(DF), FREStart(FRES) {}
Expand All @@ -53,7 +73,8 @@ struct SFrameFDE {
MCFixup::getDataKindForSize(4)));
S.emitInt32(0);

// sfde_func_start_num_fres
// sfde_func_num_fres
// TODO: When we actually emit fres, replace 0 with FREs.size()
S.emitInt32(0);

// sfde_func_info word
Expand All @@ -76,10 +97,90 @@ class SFrameEmitterImpl {
MCObjectStreamer &Streamer;
SmallVector<SFrameFDE> FDEs;
ABI SFrameABI;
// Target-specific convenience variables to detect when a CFI instruction
// references these registers. Unlike in dwarf frame descriptions, they never
// escape into the sframe section itself.
unsigned SPReg;
unsigned FPReg;
unsigned RAReg;
MCSymbol *FDESubSectionStart;
MCSymbol *FRESubSectionStart;
MCSymbol *FRESubSectionEnd;

bool setCFARegister(SFrameFRE &FRE, const MCCFIInstruction &I) {
if (I.getRegister() == SPReg) {
FRE.CFARegSet = true;
FRE.FromFP = false;
return true;
}
if (I.getRegister() == FPReg) {
FRE.CFARegSet = true;
FRE.FromFP = true;
return true;
}
Streamer.getContext().reportWarning(
I.getLoc(), "canonical Frame Address not in stack- or frame-pointer. "
"Omitting SFrame unwind info for this function");
return false;
}

bool setCFAOffset(SFrameFRE &FRE, const SMLoc &Loc, size_t Offset) {
if (!FRE.CFARegSet) {
Streamer.getContext().reportWarning(
Loc, "adjusting CFA offset without a base register. "
"Omitting SFrame unwind info for this function");
return false;
}
FRE.CFAOffset = Offset;
return true;
}

// Add the effects of CFI to the current FDE, creating a new FRE when
// necessary.
bool handleCFI(SFrameFDE &FDE, SFrameFRE &FRE, const MCCFIInstruction &CFI) {
switch (CFI.getOperation()) {
case MCCFIInstruction::OpDefCfaRegister:
return setCFARegister(FRE, CFI);
case MCCFIInstruction::OpDefCfa:
case MCCFIInstruction::OpLLVMDefAspaceCfa:
if (!setCFARegister(FRE, CFI))
return false;
return setCFAOffset(FRE, CFI.getLoc(), CFI.getOffset());
case MCCFIInstruction::OpOffset:
if (CFI.getRegister() == FPReg)
FRE.FPOffset = CFI.getOffset();
else if (CFI.getRegister() == RAReg)
FRE.RAOffset = CFI.getOffset();
return true;
case MCCFIInstruction::OpRelOffset:
if (CFI.getRegister() == FPReg)
FRE.FPOffset += CFI.getOffset();
else if (CFI.getRegister() == RAReg)
FRE.RAOffset += CFI.getOffset();
return true;
case MCCFIInstruction::OpDefCfaOffset:
return setCFAOffset(FRE, CFI.getLoc(), CFI.getOffset());
case MCCFIInstruction::OpAdjustCfaOffset:
return setCFAOffset(FRE, CFI.getLoc(), FRE.CFAOffset + CFI.getOffset());
case MCCFIInstruction::OpRememberState:
// TODO: Implement. Will use FDE.
return true;
case MCCFIInstruction::OpRestore:
// TODO: Implement. Will use FDE.
return true;
case MCCFIInstruction::OpRestoreState:
// TODO: Implement. Will use FDE.
return true;
case MCCFIInstruction::OpEscape:
// TODO: Implement. Will use FDE.
return true;
default:
// Instructions that don't affect the CFA, RA, and SP can be safely
// ignored.
return true;
}
}

public:
SFrameEmitterImpl(MCObjectStreamer &Streamer) : Streamer(Streamer) {
assert(Streamer.getContext()
Expand All @@ -88,13 +189,96 @@ class SFrameEmitterImpl {
.has_value());
FDEs.reserve(Streamer.getDwarfFrameInfos().size());
SFrameABI = *Streamer.getContext().getObjectFileInfo()->getSFrameABIArch();
switch (SFrameABI) {
case ABI::AArch64EndianBig:
case ABI::AArch64EndianLittle:
SPReg = 31;
RAReg = 29;
FPReg = 30;
break;
case ABI::AMD64EndianLittle:
SPReg = 7;
// RARegister untracked in this abi. Value chosen to match
// MCDwarfFrameInfo constructor.
RAReg = static_cast<unsigned>(INT_MAX);
FPReg = 6;
break;
}

FDESubSectionStart = Streamer.getContext().createTempSymbol();
FRESubSectionStart = Streamer.getContext().createTempSymbol();
FRESubSectionEnd = Streamer.getContext().createTempSymbol();
}

void BuildSFDE(const MCDwarfFrameInfo &DF) {
FDEs.emplace_back(DF, Streamer.getContext().createTempSymbol());
bool atSameLocation(const MCSymbol *Left, const MCSymbol *Right) {
return Left != nullptr && Right != nullptr &&
Left->getFragment() == Right->getFragment() &&
Left->getOffset() == Right->getOffset();
}

bool equalIgnoringLocation(const SFrameFRE &Left, const SFrameFRE &Right) {
return Left.CFAOffset == Right.CFAOffset &&
Left.FPOffset == Right.FPOffset && Left.RAOffset == Right.RAOffset &&
Left.FromFP == Right.FromFP && Left.CFARegSet == Right.CFARegSet;
}

void buildSFDE(const MCDwarfFrameInfo &DF) {
bool Valid = true;
SFrameFDE FDE(DF, Streamer.getContext().createTempSymbol());
// This would have been set via ".cfi_return_column", but
// MCObjectStreamer doesn't emit an MCCFIInstruction for that. It just
// sets the DF.RAReg.
// FIXME: This also prevents providing a proper location for the error.
// LLVM doesn't change the return column itself, so this was
// hand-written assembly.
if (DF.RAReg != RAReg) {
Streamer.getContext().reportWarning(
SMLoc(), "non-default RA register in .cfi_return_column " +
Twine(DF.RAReg) +
". Omitting SFrame unwind info for this function");
Valid = false;
}
MCSymbol *LastLabel = DF.Begin;
SFrameFRE BaseFRE(LastLabel);
if (!DF.IsSimple) {
for (const auto &CFI :
Streamer.getContext().getAsmInfo()->getInitialFrameState())
if (!handleCFI(FDE, BaseFRE, CFI))
Valid = false;
}
FDE.FREs.push_back(BaseFRE);

for (const auto &CFI : DF.Instructions) {
// Instructions from InitialFrameState may not have a label, but if these
// instructions don't, then they are in dead code or otherwise unused.
// TODO: This check follows MCDwarf.cpp
// FrameEmitterImplementation::emitCFIInstructions, but nothing in the
// testsuite triggers it. We should see if it can be removed in both
// places, or alternately, add a test to exercise it.
auto *L = CFI.getLabel();
if (L && !L->isDefined())
continue;

SFrameFRE FRE = FDE.FREs.back();
if (!handleCFI(FDE, FRE, CFI))
Valid = false;

// If nothing relevant but the location changed, don't add the FRE.
if (equalIgnoringLocation(FRE, FDE.FREs.back()))
continue;

// If the location stayed the same, then update the current
// row. Otherwise, add a new one.
if (atSameLocation(LastLabel, L))
FDE.FREs.back() = FRE;
else {
FDE.FREs.push_back(FRE);
FDE.FREs.back().Label = L;
LastLabel = L;
}
}
if (Valid)
FDEs.push_back(FDE);
}

void emitPreamble() {
Expand All @@ -116,7 +300,9 @@ class SFrameEmitterImpl {
// shf_num_fdes
Streamer.emitInt32(FDEs.size());
// shf_num_fres
Streamer.emitInt32(0);
uint32_t TotalFREs = 0;
Streamer.emitInt32(TotalFREs);

// shf_fre_len
Streamer.emitAbsoluteSymbolDiff(FRESubSectionEnd, FRESubSectionStart,
sizeof(int32_t));
Expand Down Expand Up @@ -161,7 +347,7 @@ void MCSFrameEmitter::emit(MCObjectStreamer &Streamer) {
// Both the header itself and the FDEs include various offsets and counts.
// Therefore, all of this must be precomputed.
for (const auto &DFrame : FrameArray)
Emitter.BuildSFDE(DFrame);
Emitter.buildSFDE(DFrame);

MCSection *Section = Context.getObjectFileInfo()->getSFrameSection();
// Not strictly necessary, but gas always aligns to 8, so match that.
Expand Down
31 changes: 31 additions & 0 deletions llvm/test/MC/ELF/cfi-sframe-errors.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// TODO: Add other architectures as they gain sframe support
// REQUIRES: x86-registered-target
// RUN: llvm-mc --assemble --filetype=obj -triple x86_64 %s -o %t.o 2>&1 | FileCheck %s
// RUN: llvm-readelf --sframe %t.o | FileCheck --check-prefix=CHECK-NOFDES %s


.cfi_sections .sframe
f1:
.cfi_startproc simple
// CHECK: non-default RA register {{.*}}
.cfi_return_column 0
nop
// CHECK: {{.*}} adjusting CFA offset without a base register.{{.*}}
.cfi_def_cfa_offset 16 // no line number reported here.
nop
// CHECK: [[@LINE+1]]:{{.*}} adjusting CFA offset without a base register.{{.*}}
.cfi_adjust_cfa_offset 16
nop
.cfi_endproc
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something wrong with the indentation here. Tabs?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's still something wrong with the indentation on this line (.cfi_endproc), at least when I view it in github UI. (I would expect it to start at the same column as the other .cfi directives.


f2:
.cfi_startproc
nop
// CHECK: canonical Frame Address not in stack- or frame-pointer. {{.*}}
.cfi_def_cfa 0, 4
nop

.cfi_endproc

// CHECK-NOFDES: Num FDEs: 0
// CHECK-NOFDES: Num FREs: 0
20 changes: 15 additions & 5 deletions llvm/test/MC/ELF/cfi-sframe.s
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@

.cfi_sections .sframe
f1:
.cfi_startproc
.cfi_startproc // FRE 0
nop
.cfi_def_cfa_offset 16 // FRE 1
.cfi_def_cfa_offset 8 // location didn't change. No new FRE, but new offset.
nop
.cfi_def_cfa_offset 8 // offset didn't change. No new FRE.
nop
.cfi_def_cfa_offset 16 // FRE 2. new location, new offset.
nop
.cfi_register 0, 1 // Uninteresting register. No new FRE.
nop

.cfi_endproc

f2:
Expand All @@ -32,11 +42,11 @@ f2:
// CHECK: Function Index [
// CHECK-NEXT: FuncDescEntry [0] {
// CHECK-NEXT: PC {
// CHECK-NEXT: Relocation: {{.*}}32{{.*}}
// CHECK-NEXT: Relocation: {{.*}}PC32{{.*}}
// CHECK-NEXT: Symbol Name: .text
// CHECK-NEXT: Start Address: 0x0
// CHECK-NEXT: Start Address: {{.*}}
// CHECK-NEXT: }
// CHECK-NEXT: Size: 0x1
// CHECK-NEXT: Size: 0x5
// CHECK-NEXT: Start FRE Offset: 0x0
// CHECK-NEXT: Num FREs: 0
// CHECK-NEXT: Info {
Expand All @@ -51,7 +61,7 @@ f2:
// CHECK-NEXT: }
// CHECK-NEXT: FuncDescEntry [1] {
// CHECK-NEXT: PC {
// CHECK-NEXT: Relocation: R_X86_64_PC32
// CHECK-NEXT: Relocation: {{.*}}PC32{{.*}}
// CHECK-NEXT: Symbol Name: .text
// CHECK-NEXT: Start Address: {{.*}}
// CHECK-NEXT: }
Expand Down
Loading