[RISCV] Support .option {no}exact (#122483)

This implements [the `.option exact` and `.option noexact`
proposal](https://github.com/riscv-non-isa/riscv-asm-manual/pull/122)
for RISC-V.

`.option exact` turns off:
- Compression
- Branch Relaxation
- Linker Relaxation

`.option noexact` turns these back on, and is also the default, matching
the current behaviour.
This commit is contained in:
Sam Elliott 2025-03-26 11:14:16 -07:00 committed by GitHub
parent 27539c3f90
commit 6a371c7744
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 267 additions and 61 deletions

View File

@ -152,6 +152,9 @@ Changes to the RISC-V Backend
handlers.
* When the experimental extension `Xqcili` is enabled, `qc.e.li` and `qc.li` may
now be used to materialize immediates.
* Adds assembler support for ``.option exact``, which disables automatic compression,
and branch and linker relaxation. This can be disabled with ``.option noexact``,
which is also the default.
Changes to the WebAssembly Backend
----------------------------------

View File

@ -3205,6 +3205,26 @@ bool RISCVAsmParser::parseDirectiveOption() {
return false;
}
if (Option == "exact") {
if (Parser.parseEOL())
return true;
getTargetStreamer().emitDirectiveOptionExact();
setFeatureBits(RISCV::FeatureExactAssembly, "exact-asm");
clearFeatureBits(RISCV::FeatureRelax, "relax");
return false;
}
if (Option == "noexact") {
if (Parser.parseEOL())
return true;
getTargetStreamer().emitDirectiveOptionNoExact();
clearFeatureBits(RISCV::FeatureExactAssembly, "exact-asm");
setFeatureBits(RISCV::FeatureRelax, "relax");
return false;
}
if (Option == "rvc") {
if (Parser.parseEOL())
return true;
@ -3261,9 +3281,10 @@ bool RISCVAsmParser::parseDirectiveOption() {
}
// Unknown option.
Warning(Parser.getTok().getLoc(), "unknown option, expected 'push', 'pop', "
"'rvc', 'norvc', 'arch', 'relax' or "
"'norelax'");
Warning(Parser.getTok().getLoc(),
"unknown option, expected 'push', 'pop', "
"'rvc', 'norvc', 'arch', 'relax', 'norelax', "
"'exact', or 'noexact'");
Parser.eatToEndOfStatement();
return false;
}
@ -3473,10 +3494,13 @@ bool RISCVAsmParser::parseDirectiveVariantCC() {
void RISCVAsmParser::emitToStreamer(MCStreamer &S, const MCInst &Inst) {
MCInst CInst;
bool Res = RISCVRVC::compress(CInst, Inst, getSTI());
bool Res = false;
const MCSubtargetInfo &STI = getSTI();
if (!STI.hasFeature(RISCV::FeatureExactAssembly))
Res = RISCVRVC::compress(CInst, Inst, STI);
if (Res)
++RISCVNumInstrsCompressed;
S.emitInstruction((Res ? CInst : Inst), getSTI());
S.emitInstruction((Res ? CInst : Inst), STI);
}
void RISCVAsmParser::emitLoadImm(MCRegister DestReg, int64_t Value,

View File

@ -171,8 +171,39 @@ bool RISCVAsmBackend::fixupNeedsRelaxationAdvanced(
}
}
// Given a compressed control flow instruction this function returns
// the expanded instruction.
static unsigned getRelaxedOpcode(unsigned Op) {
switch (Op) {
default:
return Op;
case RISCV::C_BEQZ:
return RISCV::BEQ;
case RISCV::C_BNEZ:
return RISCV::BNE;
case RISCV::C_J:
case RISCV::C_JAL: // fall through.
return RISCV::JAL;
case RISCV::BEQ:
return RISCV::PseudoLongBEQ;
case RISCV::BNE:
return RISCV::PseudoLongBNE;
case RISCV::BLT:
return RISCV::PseudoLongBLT;
case RISCV::BGE:
return RISCV::PseudoLongBGE;
case RISCV::BLTU:
return RISCV::PseudoLongBLTU;
case RISCV::BGEU:
return RISCV::PseudoLongBGEU;
}
}
void RISCVAsmBackend::relaxInstruction(MCInst &Inst,
const MCSubtargetInfo &STI) const {
if (STI.hasFeature(RISCV::FeatureExactAssembly))
return;
MCInst Res;
switch (Inst.getOpcode()) {
default:
@ -341,36 +372,14 @@ std::pair<bool, bool> RISCVAsmBackend::relaxLEB128(const MCAssembler &Asm,
return std::make_pair(Expr.evaluateKnownAbsolute(Value, Asm), false);
}
// Given a compressed control flow instruction this function returns
// the expanded instruction.
unsigned RISCVAsmBackend::getRelaxedOpcode(unsigned Op) const {
switch (Op) {
default:
return Op;
case RISCV::C_BEQZ:
return RISCV::BEQ;
case RISCV::C_BNEZ:
return RISCV::BNE;
case RISCV::C_J:
case RISCV::C_JAL: // fall through.
return RISCV::JAL;
case RISCV::BEQ:
return RISCV::PseudoLongBEQ;
case RISCV::BNE:
return RISCV::PseudoLongBNE;
case RISCV::BLT:
return RISCV::PseudoLongBLT;
case RISCV::BGE:
return RISCV::PseudoLongBGE;
case RISCV::BLTU:
return RISCV::PseudoLongBLTU;
case RISCV::BGEU:
return RISCV::PseudoLongBGEU;
}
}
bool RISCVAsmBackend::mayNeedRelaxation(const MCInst &Inst,
const MCSubtargetInfo &STI) const {
// This function has access to two STIs, the member of the AsmBackend, and the
// one passed as an argument. The latter is more specific, so we query it for
// specific features.
if (STI.hasFeature(RISCV::FeatureExactAssembly))
return false;
return getRelaxedOpcode(Inst.getOpcode()) != Inst.getOpcode();
}

View File

@ -81,7 +81,6 @@ public:
bool mayNeedRelaxation(const MCInst &Inst,
const MCSubtargetInfo &STI) const override;
unsigned getRelaxedOpcode(unsigned Op) const;
void relaxInstruction(MCInst &Inst,
const MCSubtargetInfo &STI) const override;

View File

@ -47,14 +47,16 @@ RISCVELFStreamer &RISCVTargetELFStreamer::getStreamer() {
return static_cast<RISCVELFStreamer &>(Streamer);
}
void RISCVTargetELFStreamer::emitDirectiveOptionPush() {}
void RISCVTargetELFStreamer::emitDirectiveOptionPop() {}
void RISCVTargetELFStreamer::emitDirectiveOptionExact() {}
void RISCVTargetELFStreamer::emitDirectiveOptionNoExact() {}
void RISCVTargetELFStreamer::emitDirectiveOptionPIC() {}
void RISCVTargetELFStreamer::emitDirectiveOptionNoPIC() {}
void RISCVTargetELFStreamer::emitDirectiveOptionRVC() {}
void RISCVTargetELFStreamer::emitDirectiveOptionNoRVC() {}
void RISCVTargetELFStreamer::emitDirectiveOptionPop() {}
void RISCVTargetELFStreamer::emitDirectiveOptionPush() {}
void RISCVTargetELFStreamer::emitDirectiveOptionRelax() {}
void RISCVTargetELFStreamer::emitDirectiveOptionNoRelax() {}
void RISCVTargetELFStreamer::emitDirectiveOptionRVC() {}
void RISCVTargetELFStreamer::emitDirectiveOptionNoRVC() {}
void RISCVTargetELFStreamer::emitAttribute(unsigned Attribute, unsigned Value) {
getStreamer().setAttributeItem(Attribute, Value, /*OverwriteExisting=*/true);

View File

@ -56,14 +56,16 @@ public:
RISCVELFStreamer &getStreamer();
RISCVTargetELFStreamer(MCStreamer &S, const MCSubtargetInfo &STI);
void emitDirectiveOptionPush() override;
void emitDirectiveOptionPop() override;
void emitDirectiveOptionExact() override;
void emitDirectiveOptionNoExact() override;
void emitDirectiveOptionPIC() override;
void emitDirectiveOptionNoPIC() override;
void emitDirectiveOptionRVC() override;
void emitDirectiveOptionNoRVC() override;
void emitDirectiveOptionPop() override;
void emitDirectiveOptionPush() override;
void emitDirectiveOptionRelax() override;
void emitDirectiveOptionNoRelax() override;
void emitDirectiveOptionRVC() override;
void emitDirectiveOptionNoRVC() override;
void emitDirectiveVariantCC(MCSymbol &Symbol) override;
void finish() override;

View File

@ -33,16 +33,18 @@ RISCVTargetStreamer::RISCVTargetStreamer(MCStreamer &S) : MCTargetStreamer(S) {}
void RISCVTargetStreamer::finish() { finishAttributeSection(); }
void RISCVTargetStreamer::reset() {}
void RISCVTargetStreamer::emitDirectiveOptionPush() {}
void RISCVTargetStreamer::emitDirectiveOptionPop() {}
void RISCVTargetStreamer::emitDirectiveOptionPIC() {}
void RISCVTargetStreamer::emitDirectiveOptionNoPIC() {}
void RISCVTargetStreamer::emitDirectiveOptionRVC() {}
void RISCVTargetStreamer::emitDirectiveOptionNoRVC() {}
void RISCVTargetStreamer::emitDirectiveOptionRelax() {}
void RISCVTargetStreamer::emitDirectiveOptionNoRelax() {}
void RISCVTargetStreamer::emitDirectiveOptionArch(
ArrayRef<RISCVOptionArchArg> Args) {}
void RISCVTargetStreamer::emitDirectiveOptionExact() {}
void RISCVTargetStreamer::emitDirectiveOptionNoExact() {}
void RISCVTargetStreamer::emitDirectiveOptionPIC() {}
void RISCVTargetStreamer::emitDirectiveOptionNoPIC() {}
void RISCVTargetStreamer::emitDirectiveOptionPop() {}
void RISCVTargetStreamer::emitDirectiveOptionPush() {}
void RISCVTargetStreamer::emitDirectiveOptionRelax() {}
void RISCVTargetStreamer::emitDirectiveOptionNoRelax() {}
void RISCVTargetStreamer::emitDirectiveOptionRVC() {}
void RISCVTargetStreamer::emitDirectiveOptionNoRVC() {}
void RISCVTargetStreamer::emitDirectiveVariantCC(MCSymbol &Symbol) {}
void RISCVTargetStreamer::emitAttribute(unsigned Attribute, unsigned Value) {}
void RISCVTargetStreamer::finishAttributeSection() {}
@ -125,6 +127,14 @@ void RISCVTargetAsmStreamer::emitDirectiveOptionNoRVC() {
OS << "\t.option\tnorvc\n";
}
void RISCVTargetAsmStreamer::emitDirectiveOptionExact() {
OS << "\t.option\texact\n";
}
void RISCVTargetAsmStreamer::emitDirectiveOptionNoExact() {
OS << "\t.option\tnoexact\n";
}
void RISCVTargetAsmStreamer::emitDirectiveOptionRelax() {
OS << "\t.option\trelax\n";
}

View File

@ -41,15 +41,17 @@ public:
void finish() override;
virtual void reset();
virtual void emitDirectiveOptionPush();
virtual void emitDirectiveOptionPop();
virtual void emitDirectiveOptionArch(ArrayRef<RISCVOptionArchArg> Args);
virtual void emitDirectiveOptionExact();
virtual void emitDirectiveOptionNoExact();
virtual void emitDirectiveOptionPIC();
virtual void emitDirectiveOptionNoPIC();
virtual void emitDirectiveOptionRVC();
virtual void emitDirectiveOptionNoRVC();
virtual void emitDirectiveOptionPop();
virtual void emitDirectiveOptionPush();
virtual void emitDirectiveOptionRelax();
virtual void emitDirectiveOptionNoRelax();
virtual void emitDirectiveOptionArch(ArrayRef<RISCVOptionArchArg> Args);
virtual void emitDirectiveOptionRVC();
virtual void emitDirectiveOptionNoRVC();
virtual void emitDirectiveVariantCC(MCSymbol &Symbol);
virtual void emitAttribute(unsigned Attribute, unsigned Value);
virtual void finishAttributeSection();
@ -78,15 +80,17 @@ class RISCVTargetAsmStreamer : public RISCVTargetStreamer {
public:
RISCVTargetAsmStreamer(MCStreamer &S, formatted_raw_ostream &OS);
void emitDirectiveOptionPush() override;
void emitDirectiveOptionPop() override;
void emitDirectiveOptionArch(ArrayRef<RISCVOptionArchArg> Args) override;
void emitDirectiveOptionExact() override;
void emitDirectiveOptionNoExact() override;
void emitDirectiveOptionPIC() override;
void emitDirectiveOptionNoPIC() override;
void emitDirectiveOptionRVC() override;
void emitDirectiveOptionNoRVC() override;
void emitDirectiveOptionPop() override;
void emitDirectiveOptionPush() override;
void emitDirectiveOptionRelax() override;
void emitDirectiveOptionNoRelax() override;
void emitDirectiveOptionArch(ArrayRef<RISCVOptionArchArg> Args) override;
void emitDirectiveOptionRVC() override;
void emitDirectiveOptionNoRVC() override;
void emitDirectiveVariantCC(MCSymbol &Symbol) override;
};

View File

@ -1496,6 +1496,10 @@ def FeatureRelax
: SubtargetFeature<"relax", "EnableLinkerRelax", "true",
"Enable Linker relaxation.">;
def FeatureExactAssembly
: SubtargetFeature<"exact-asm", "EnableExactAssembly", "true",
"Enable Exact Assembly (Disables Compression and Relaxation)">;
foreach i = {1-31} in
def FeatureReserveX#i :
SubtargetFeature<"reserve-x"#i, "UserReservedRegister[RISCV::X"#i#"]",

View File

@ -14,6 +14,7 @@
; CHECK-NEXT: disable-latency-sched-heuristic - Disable latency scheduling heuristic.
; CHECK-NEXT: dlen-factor-2 - Vector unit DLEN(data path width) is half of VLEN.
; CHECK-NEXT: e - 'E' (Embedded Instruction Set with 16 GPRs).
; CHECK-NEXT: exact-asm - Enable Exact Assembly (Disables Compression and Relaxation).
; CHECK-NEXT: experimental - Experimental intrinsics.
; CHECK-NEXT: experimental-p - 'P' ('Base P' (Packed SIMD)).
; CHECK-NEXT: experimental-rvm23u32 - RISC-V experimental-rvm23u32 profile.

View File

@ -0,0 +1,20 @@
; RUN: llc -mtriple=riscv32 -mattr=+relax,+c %s --filetype=obj -o - \
; RUN: | llvm-objdump --triple=riscv32 --mattr=+c -M no-aliases -dr - \
; RUN: | FileCheck %s
define i32 @foo(ptr noundef %f) nounwind {
; CHECK-LABEL: <foo>:
; CHECK: auipc ra, 0x0
; CHECK-NEXT: R_RISCV_CALL_PLT undefined
; CHECK-NEXT: jalr ra, 0x0(ra)
; CHECK-NEXT: lw a0, 0x0(a0)
; CHECK-NEXT: c.jr ra
entry:
%0 = tail call i32 asm sideeffect "
.option exact
call undefined@plt
lw $0, ($1)
.option noexact", "=^cr,^cr"(ptr %f)
ret i32 %0
}

View File

@ -0,0 +1,128 @@
# RUN: llvm-mc -triple riscv32 -show-encoding -mattr=+c,+relax \
# RUN: -M no-aliases %s | FileCheck -check-prefixes=CHECK-ASM,CHECK-INST %s
# RUN: llvm-mc -triple riscv32 -filetype=obj -mattr=+c,+relax %s \
# RUN: | llvm-objdump --triple=riscv32 --mattr=+c -dr --no-print-imm-hex -M no-aliases - \
# RUN: | FileCheck -check-prefixes=CHECK-OBJDUMP,CHECK-INST %s
# RUN: llvm-mc -triple riscv64 -show-encoding \
# RUN: -M no-aliases -mattr=+c,+relax %s \
# RUN: | FileCheck -check-prefixes=CHECK-ASM %s
# RUN: llvm-mc -triple riscv64 -filetype=obj -mattr=+c,+relax %s \
# RUN: | llvm-objdump --triple=riscv64 --mattr=+c -dr --no-print-imm-hex -M no-aliases - \
# RUN: | FileCheck -check-prefixes=CHECK-OBJDUMP,CHECK-INST %s
## `.option exact` disables a variety of assembler behaviour:
## - automatic compression
## - branch relaxation (of short branches to longer equivalent sequences)
## - linker relaxation (emitting R_RISCV_RELAX)
## `.option noexact` enables these behaviours again. It is also the default.
# CHECK-OBJDUMP: 4108
# CHECK-INST: c.lw a0, 0(a0)
# CHECK-ASM: # encoding: [0x08,0x41]
lw a0, 0(a0)
# CHECK-OBJDUMP: 4108
# CHECK-INST: c.lw a0, 0(a0)
# CHECK-ASM: # encoding: [0x08,0x41]
c.lw a0, 0(a0)
# CHECK-ASM: call undefined
# CHECK-ASM-SAME: # encoding: [0x97'A',A,A,A,0xe7'A',0x80'A',A,A]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_call_plt
# CHECK-ASM-NEXT: fixup B - offset: 0, value: 0, kind: fixup_riscv_relax
# CHECK-OBJDUMP: auipc ra, 0
# CHECK-OBJDUMP-NEXT: R_RISCV_CALL_PLT undefined
# CHECK-OBJDUMP-NEXT: R_RISCV_RELAX *ABS*
# CHECK-OBJDUMP-NEXT: jalr ra
call undefined@plt
# CHECK-ASM: beq a0, a1, undefined
# CHECK-ASM-SAME: # encoding: [0x63'A',A,0xb5'A',A]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_branch
# CHECK-OBJDUMP: bne a0, a1, 0x14
# CHECK-OBJDUMP-NEXT: jal zero, 0x10
# CHECK-OBJDUMP-NEXT: R_RISCV_JAL undefined
beq a0, a1, undefined
# CHECK-ASM: c.j undefined
# CHECK-ASM-SAME: # encoding: [0bAAAAAA01,0b101AAAAA]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_rvc_jump
# CHECK-OBJDUMP: jal zero, 0x14
# CHECK-OBJDUMP-NEXT: R_RISCV_JAL undefined
c.j undefined
# CHECK-ASM: .option exact
.option exact
# CHECK-OBJDUMP: 00052503
# CHECK-INST: lw a0, 0(a0)
# CHECK-ASM: # encoding: [0x03,0x25,0x05,0x00]
lw a0, 0(a0)
# CHECK-OBJDUMP: 4108
# CHECK-INST: c.lw a0, 0(a0)
# CHECK-ASM: # encoding: [0x08,0x41]
c.lw a0, 0(a0)
# CHECK-ASM: call undefined
# CHECK-ASM-SAME: # encoding: [0x97'A',A,A,A,0xe7'A',0x80'A',A,A]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_call_plt
# CHECK-ASM-NOT: fixup_riscv_relax
# CHECK-OBJDUMP: auipc ra, 0
# CHECK-OBJDUMP-NEXT: R_RISCV_CALL_PLT undefined
# CHECK-OBJDUMP-NOT: R_RISCV_RELAX
# CHECK-OBJDUMP-NEXT: jalr ra, 0(ra)
call undefined@plt
# CHECK-ASM: beq a0, a1, undefined
# CHECK-ASM-SAME: # encoding: [0x63'A',A,0xb5'A',A]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_branch
# CHECK-OBJDUMP: beq a0, a1, 0x26
# CHECK-OBJDUMP-NEXT: R_RISCV_BRANCH undefined
beq a0, a1, undefined
# CHECK-ASM: c.j undefined
# CHECK-ASM-SAME: # encoding: [0bAAAAAA01,0b101AAAAA]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_rvc_jump
# CHECK-OBJDUMP: c.j 0x2a
# CHECK-OBJDUMP-NEXT: R_RISCV_RVC_JUMP undefined
c.j undefined
# CHECK-ASM: .option noexact
.option noexact
# CHECK-OBJDUMP: 4108
# CHECK-INST: c.lw a0, 0(a0)
# CHECK-ASM: # encoding: [0x08,0x41]
lw a0, 0(a0)
# CHECK-OBJDUMP: 4108
# CHECK-INST: c.lw a0, 0(a0)
# CHECK-ASM: # encoding: [0x08,0x41]
c.lw a0, 0(a0)
# CHECK-ASM: call undefined
# CHECK-ASM-SAME: # encoding: [0x97'A',A,A,A,0xe7'A',0x80'A',A,A]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_call_plt
# CHECK-ASM-NEXT: fixup B - offset: 0, value: 0, kind: fixup_riscv_relax
# CHECK-OBJDUMP: auipc ra, 0
# CHECK-OBJDUMP-NEXT: R_RISCV_CALL_PLT undefined
# CHECK-OBJDUMP-NEXT: R_RISCV_RELAX *ABS*
# CHECK-OBJDUMP-NEXT: jalr ra, 0(ra)
call undefined@plt
# CHECK-ASM: beq a0, a1, undefined
# CHECK-ASM-SAME: # encoding: [0x63'A',A,0xb5'A',A]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_branch
# CHECK-OBJDUMP: bne a0, a1, 0x40
# CHECK-OBJDUMP-NEXT: jal zero, 0x3c
# CHECK-OBJDUMP-NEXT: R_RISCV_JAL undefined
beq a0, a1, undefined
# CHECK-ASM: c.j undefined
# CHECK-ASM-SAME: # encoding: [0bAAAAAA01,0b101AAAAA]
# CHECK-ASM-NEXT: fixup A - offset: 0, value: undefined, kind: fixup_riscv_rvc_jump
# CHECK-OBJDUMP: jal zero, 0x40
# CHECK-OBJDUMP-NEXT: R_RISCV_JAL undefined
c.j undefined

View File

@ -53,7 +53,7 @@
# CHECK: :[[#@LINE+1]]:13: error: expected newline
.option rvc foo
# CHECK: :[[#@LINE+1]]:12: warning: unknown option, expected 'push', 'pop', 'rvc', 'norvc', 'arch', 'relax' or 'norelax'
# CHECK: :[[#@LINE+1]]:12: warning: unknown option, expected 'push', 'pop', 'rvc', 'norvc', 'arch', 'relax', 'norelax', 'exact', or 'noexact'
.option bar
# CHECK: :[[#@LINE+1]]:12: error: .option pop with no .option push