From f3f167c3bca14c5111f5bc04675a7eb1864c6c4b Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Sat, 30 May 2026 16:30:17 +0800 Subject: [PATCH] Expand LinxISA toolchain coverage for FP block headers and hidden operands Assembler, blockify, MC backend, and tests are updated together so the current ISA surface round-trips the forms that the documentation and runtime work now depend on. The packaged changes also cover large SSR IDs, HL immediate spellings, prefetch round-trips, and FP-aware BSTART header selection. Constraint: Package the existing model-generated delta without rebasing the detached submodule checkout first Rejected: Split parser, backend, and tests into separate commits | user requested direct packaging of the current worktree state Confidence: medium Scope-risk: moderate Directive: Rebuild clang as well as llvm-mc when touching assembler or MC paths because AVS .S flows use the integrated assembler Tested: git diff --check Not-tested: llvm build, MC test suite Co-authored-by: OmX --- lld/ELF/InputSection.cpp | 43 ++-- .../LinxISA/AsmParser/LinxISAAsmParser.cpp | 9 +- llvm/lib/Target/LinxISA/LinxISAAsmPrinter.cpp | 7 +- llvm/lib/Target/LinxISA/LinxISABlockify.cpp | 183 +++++++++++++----- llvm/lib/Target/LinxISA/LinxISAInstrInfo.td | 5 + .../lib/Target/LinxISA/LinxISAMCInstLower.cpp | 21 ++ .../LinxISA/LinxISASIMTAutoVectorize.cpp | 30 ++- .../MCTargetDesc/LinxISAAsmBackend.cpp | 17 ++ .../MCTargetDesc/LinxISAInstPrinter.cpp | 2 + .../MC/LinxISA/hl-alu-immediates-roundtrip.s | 82 ++++++++ llvm/test/MC/LinxISA/hl-ssrget-set-large-id.s | 4 + llvm/test/MC/LinxISA/prefetch-roundtrip.s | 18 ++ 12 files changed, 330 insertions(+), 91 deletions(-) create mode 100644 llvm/test/MC/LinxISA/hl-alu-immediates-roundtrip.s create mode 100644 llvm/test/MC/LinxISA/prefetch-roundtrip.s diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp index 8448beb0841c4..d0605e793d5ea 100644 --- a/lld/ELF/InputSection.cpp +++ b/lld/ELF/InputSection.cpp @@ -803,24 +803,28 @@ static const Relocation *getLinxPCRelHi20(Ctx &ctx, hiSec = eh->getParent(); } if (!hiSec) { - Err(ctx) << loSec->getLocation(loReloc.offset) - << ": R_LINX_LO12 relocation " - "points to unsupported anchor section '" - << d->section->name << "' for symbol '" << sym->getName() - << "'"; - return nullptr; + /* + * Final kernel/static links can point the LO12 relocation at a symbol + * that already lives in an output/synthetic section (for example + * .init.data, __param, or linker-defined text/data boundaries). Those + * symbols still commonly have a matching HI20 relocation by symbol in + * the same input section, so do not reject them here just because they + * are no longer modeled as an InputSection. + */ + hiSec = nullptr; + } + if (hiSec) { + anchor = sym->getName().empty() ? "offset 0x" + utohexstr(d->value) + : toStr(ctx, *sym) + "+0x" + + utohexstr(d->value); + if (addend != 0) + Warn(ctx) << loSec->getLocation(loReloc.offset) + << ": non-zero addend in " + "R_LINX_LO12 relocation to " + << anchor << " is ignored"; + if (const Relocation *hiRel = findLinxPCRelHiByOffset(hiSec, d->value)) + return hiRel; } - - anchor = sym->getName().empty() ? "offset 0x" + utohexstr(d->value) - : toStr(ctx, *sym) + "+0x" + - utohexstr(d->value); - if (addend != 0) - Warn(ctx) << loSec->getLocation(loReloc.offset) - << ": non-zero addend in " - "R_LINX_LO12 relocation to " - << anchor << " is ignored"; - if (const Relocation *hiRel = findLinxPCRelHiByOffset(hiSec, d->value)) - return hiRel; } } @@ -1015,7 +1019,10 @@ uint64_t InputSectionBase::getRelocTargetVA(Ctx &ctx, const Relocation &r, } case RE_LINX_PC_INDIRECT: { if (const Relocation *hiRel = getLinxPCRelHi20(ctx, this, r)) - return getRelocTargetVA(ctx, *hiRel, getVA(hiRel->offset)); + // For Linx ADDTPC+ADDI/ADDIW materialization, the HI20 relocation only + // establishes the page-relative anchor. The paired LO12 relocation must + // receive the final symbol VA's low 12 bits, not the HI20 page delta. + return r.sym->getVA(ctx, a); return 0; } case RE_LOONGARCH_PC_INDIRECT: { diff --git a/llvm/lib/Target/LinxISA/AsmParser/LinxISAAsmParser.cpp b/llvm/lib/Target/LinxISA/AsmParser/LinxISAAsmParser.cpp index 126e15f46bafe..98221d71f0f41 100644 --- a/llvm/lib/Target/LinxISA/AsmParser/LinxISAAsmParser.cpp +++ b/llvm/lib/Target/LinxISA/AsmParser/LinxISAAsmParser.cpp @@ -2403,8 +2403,13 @@ bool LinxISAAsmParser::buildMCInstForForm(unsigned FormIndex, const ParsedInst & if (!require(HasArrow || AllowsExplicitHiddenMemDst, "expected '->' destination for RegDst")) return false; - if (!require(!PI.ArrowDests.empty(), "expected destination after '->'")) - return false; + if (PI.ArrowDests.empty()) { + if (!require(AllowsExplicitHiddenMemDst, + "expected destination after '->'")) + return false; + emitFieldImm(0); + continue; + } if (!require(PI.ArrowDests[0].Code < (1u << Field.bit_width), "destination register does not fit field width")) return false; diff --git a/llvm/lib/Target/LinxISA/LinxISAAsmPrinter.cpp b/llvm/lib/Target/LinxISA/LinxISAAsmPrinter.cpp index ebc5c7499bb6d..1c66265ac8057 100644 --- a/llvm/lib/Target/LinxISA/LinxISAAsmPrinter.cpp +++ b/llvm/lib/Target/LinxISA/LinxISAAsmPrinter.cpp @@ -78,7 +78,8 @@ void LinxISAAsmPrinter::emitInstruction(const MachineInstr *MI) { } bool Emitted = false; - if (MI->getOpcode() == LinxISA::BSTART_STD_CALL && + if ((MI->getOpcode() == LinxISA::BSTART_STD_CALL || + MI->getOpcode() == LinxISA::BSTART_FP_CALL) && OutStreamer->hasRawTextSupport()) { MachineBasicBlock *MBB = const_cast(MI->getParent()); if (MBB) { @@ -95,7 +96,9 @@ void LinxISAAsmPrinter::emitInstruction(const MachineInstr *MI) { SmallString<128> Line; raw_svector_ostream OS(Line); - OS << "BSTART\tCALL, "; + OS << (MI->getOpcode() == LinxISA::BSTART_FP_CALL + ? "BSTART.FP\tCALL, " + : "BSTART\tCALL, "); if (BStartInst.getNumOperands() >= 1) { const MCOperand &TargetOp = BStartInst.getOperand(0); diff --git a/llvm/lib/Target/LinxISA/LinxISABlockify.cpp b/llvm/lib/Target/LinxISA/LinxISABlockify.cpp index d4593c1549536..4cba5679b2297 100644 --- a/llvm/lib/Target/LinxISA/LinxISABlockify.cpp +++ b/llvm/lib/Target/LinxISA/LinxISABlockify.cpp @@ -369,6 +369,7 @@ static unsigned tileRegIdFromReg(const TargetRegisterInfo &TRI, Register Reg) { static bool isMarkerInstr(const MachineInstr &MI) { switch (MI.getOpcode()) { case LinxISA::CBSTART_STD: + case LinxISA::CBSTART_FP: case LinxISA::BSTART_STD_FALL: case LinxISA::BSTART_STD_DIRECT: case LinxISA::BSTART_STD_COND: @@ -376,6 +377,9 @@ static bool isMarkerInstr(const MachineInstr &MI) { case LinxISA::BSTART_STD_IND: case LinxISA::BSTART_STD_ICALL: case LinxISA::BSTART_STD_RET: + case LinxISA::BSTART_FP_DIRECT: + case LinxISA::BSTART_FP_COND: + case LinxISA::BSTART_FP_CALL: case LinxISA::BSTART_TMA: case LinxISA::BSTART_CUBE: case LinxISA::BSTART_TEPL: @@ -390,6 +394,58 @@ static bool isMarkerInstr(const MachineInstr &MI) { } } +static bool isScalarBStartOpcode(unsigned Opc) { + switch (Opc) { + case LinxISA::CBSTART_STD: + case LinxISA::CBSTART_FP: + case LinxISA::BSTART_STD_FALL: + case LinxISA::BSTART_STD_DIRECT: + case LinxISA::BSTART_STD_COND: + case LinxISA::BSTART_STD_CALL: + case LinxISA::BSTART_STD_IND: + case LinxISA::BSTART_STD_ICALL: + case LinxISA::BSTART_STD_RET: + case LinxISA::BSTART_FP_DIRECT: + case LinxISA::BSTART_FP_COND: + case LinxISA::BSTART_FP_CALL: + return true; + default: + return false; + } +} + +static bool isScalarFPOpcode(unsigned Opc) { + switch (Opc) { + case LinxISA::FADDrr: + case LinxISA::FSUBrr: + case LinxISA::FMULrr: + case LinxISA::FDIVrr: + case LinxISA::FABSrr: + case LinxISA::FEQrr: + case LinxISA::FLTrr: + case LinxISA::FGErr: + case LinxISA::FCVT: + case LinxISA::FCVTZ: + case LinxISA::SCVTF: + case LinxISA::UCVTF: + return true; + default: + return false; + } +} + +static bool blockNeedsFPHeader(const MachineBasicBlock &MBB) { + for (const MachineInstr &MI : MBB) { + if (MI.isDebugInstr() || MI.isCFIInstruction() || MI.isPHI()) + continue; + if (isMarkerInstr(MI)) + continue; + if (isScalarFPOpcode(MI.getOpcode())) + return true; + } + return false; +} + static bool isSimtBodyHeaderOpcode(unsigned Opc) { switch (Opc) { case LinxISA::BSTART_MPAR: @@ -950,25 +1006,72 @@ class LinxISABlockify : public MachineFunctionPass { auto assignVecPipeDstCode = [&](StringRef DstPart, unsigned ParsedCode, VecPipeCursorState &PipeState) -> unsigned { + auto applyDstWidthSuffix = [&](StringRef Tok, unsigned Code) -> unsigned { + StringRef Trimmed = Tok.trim(); + size_t Dot = Trimmed.rfind('.'); + if (Dot == StringRef::npos) + return Code; + StringRef Suffix = Trimmed.drop_front(Dot + 1).trim(); + unsigned Width = 0; + if (Suffix.equals_insensitive("d")) { + Width = 0u; + } else if (Suffix.equals_insensitive("w")) { + Width = 1u; + } else if (Suffix.equals_insensitive("h")) { + Width = 2u; + } else if (Suffix.equals_insensitive("b")) { + Width = 3u; + } else { + return Code; + } + Code &= ~(0x3u << 7); + Code |= (Width & 0x3u) << 7; + return Code; + }; + auto nextCode = [&](unsigned Class, unsigned &NextIndex) { - return (Class << 5) | (NextIndex++ & 0x1fu); + unsigned Code = 0; + switch (Class) { + case 4: + Code = 0u; // DST_FVEC_VT + break; + case 5: + Code = 1u; // DST_FVEC_VU + break; + case 6: + Code = 2u; // DST_FVEC_VM + break; + case 7: + Code = 3u; // DST_FVEC_VN + break; + default: + llvm_unreachable("unexpected vector head queue class"); + } + ++NextIndex; + return Code; }; if (auto HeadClass = getHeadQueueClass(DstPart)) { + unsigned Code = 0; switch (*HeadClass) { case 4: - return nextCode(*HeadClass, PipeState.NextVt); + Code = nextCode(*HeadClass, PipeState.NextVt); + break; case 5: - return nextCode(*HeadClass, PipeState.NextVu); + Code = nextCode(*HeadClass, PipeState.NextVu); + break; case 6: - return nextCode(*HeadClass, PipeState.NextVm); + Code = nextCode(*HeadClass, PipeState.NextVm); + break; case 7: - return nextCode(*HeadClass, PipeState.NextVn); + Code = nextCode(*HeadClass, PipeState.NextVn); + break; default: llvm_unreachable("unexpected vector head queue class"); } + return applyDstWidthSuffix(DstPart, Code); } noteExplicitVecPipeDest(DstPart, PipeState); - return ParsedCode; + return applyDstWidthSuffix(DstPart, ParsedCode); }; auto emitVectorBodyLine = @@ -1963,14 +2066,7 @@ class LinxISABlockify : public MachineFunctionPass { ++It; continue; } - if (It->getOpcode() == LinxISA::CBSTART_STD || - It->getOpcode() == LinxISA::BSTART_STD_FALL || - It->getOpcode() == LinxISA::BSTART_STD_DIRECT || - It->getOpcode() == LinxISA::BSTART_STD_COND || - It->getOpcode() == LinxISA::BSTART_STD_CALL || - It->getOpcode() == LinxISA::BSTART_STD_IND || - It->getOpcode() == LinxISA::BSTART_STD_ICALL || - It->getOpcode() == LinxISA::BSTART_STD_RET) { + if (isScalarBStartOpcode(It->getOpcode())) { It = MBB.erase(It); Changed = true; continue; @@ -3136,33 +3232,18 @@ class LinxISABlockify : public MachineFunctionPass { // Tile blocks (TAU) have their own BSTART.TMA/BSTART.CUBE headers and // must not be wrapped by standard BSTART.STD or have T/U-hand queue // remapping applied. - auto isStdBStartOpcode = [&](unsigned Opc) -> bool { - switch (Opc) { - case LinxISA::CBSTART_STD: - case LinxISA::BSTART_STD_FALL: - case LinxISA::BSTART_STD_DIRECT: - case LinxISA::BSTART_STD_COND: - case LinxISA::BSTART_STD_CALL: - case LinxISA::BSTART_STD_IND: - case LinxISA::BSTART_STD_ICALL: - case LinxISA::BSTART_STD_RET: - return true; - default: - return false; - } - }; - bool IsTileBlock = false; for (MachineInstr &MI : MBB) { if (MI.isDebugInstr() || MI.isCFIInstruction() || MI.isPHI()) continue; // Be robust if the pass runs twice: skip any stale standard markers // and detect the tile header that follows. - if (isStdBStartOpcode(MI.getOpcode())) + if (isScalarBStartOpcode(MI.getOpcode())) continue; IsTileBlock = isTileBlockStartInstr(MI); break; } + const bool UseFPHeader = !IsTileBlock && blockNeedsFPHeader(MBB); auto isPhysRegLiveOutOfBlock = [&](Register Reg) -> bool { // Physical register live-in sets only track "use before def" within a @@ -5368,7 +5449,7 @@ class LinxISABlockify : public MachineFunctionPass { auto It = MBB.begin(); while (It != MBB.end() && It->isPHI()) ++It; - while (It != MBB.end() && isStdBStartOpcode(It->getOpcode())) { + while (It != MBB.end() && isScalarBStartOpcode(It->getOpcode())) { It = MBB.erase(It); Changed = true; } @@ -5380,14 +5461,7 @@ class LinxISABlockify : public MachineFunctionPass { // Remove any existing start marker (in case the pass runs twice). if (InsertBStart != MBB.end() && - (InsertBStart->getOpcode() == LinxISA::CBSTART_STD || - InsertBStart->getOpcode() == LinxISA::BSTART_STD_FALL || - InsertBStart->getOpcode() == LinxISA::BSTART_STD_DIRECT || - InsertBStart->getOpcode() == LinxISA::BSTART_STD_COND || - InsertBStart->getOpcode() == LinxISA::BSTART_STD_CALL || - InsertBStart->getOpcode() == LinxISA::BSTART_STD_IND || - InsertBStart->getOpcode() == LinxISA::BSTART_STD_ICALL || - InsertBStart->getOpcode() == LinxISA::BSTART_STD_RET)) { + isScalarBStartOpcode(InsertBStart->getOpcode())) { InsertBStart = MBB.erase(InsertBStart); Changed = true; } @@ -5398,14 +5472,16 @@ class LinxISABlockify : public MachineFunctionPass { case ExitKind::Fall: // Prefer the compressed BrType marker: C.BSTART (FALL). BStartMI = BuildMI(MBB, InsertBStart, DebugLoc(), - TII.get(LinxISA::CBSTART_STD)) + TII.get(UseFPHeader ? LinxISA::CBSTART_FP + : LinxISA::CBSTART_STD)) .addImm(1) // BrType = FALL .getInstr(); break; case ExitKind::Direct: if (CallTargetOp) { BStartMI = BuildMI(MBB, InsertBStart, DebugLoc(), - TII.get(LinxISA::BSTART_STD_DIRECT)) + TII.get(UseFPHeader ? LinxISA::BSTART_FP_DIRECT + : LinxISA::BSTART_STD_DIRECT)) .add(*CallTargetOp) .getInstr(); } else { @@ -5413,7 +5489,8 @@ class LinxISABlockify : public MachineFunctionPass { report_fatal_error("Linx: missing direct branch target"); TargetBB->setLabelMustBeEmitted(); BStartMI = BuildMI(MBB, InsertBStart, DebugLoc(), - TII.get(LinxISA::BSTART_STD_DIRECT)) + TII.get(UseFPHeader ? LinxISA::BSTART_FP_DIRECT + : LinxISA::BSTART_STD_DIRECT)) .addMBB(TargetBB) .getInstr(); } @@ -5422,7 +5499,8 @@ class LinxISABlockify : public MachineFunctionPass { if (TargetBB) TargetBB->setLabelMustBeEmitted(); BStartMI = BuildMI(MBB, InsertBStart, DebugLoc(), - TII.get(LinxISA::BSTART_STD_COND)) + TII.get(UseFPHeader ? LinxISA::BSTART_FP_COND + : LinxISA::BSTART_STD_COND)) .addMBB(TargetBB) .getInstr(); break; @@ -5430,7 +5508,8 @@ class LinxISABlockify : public MachineFunctionPass { if (!CallTargetOp) report_fatal_error("Linx: missing call target operand"); BStartMI = BuildMI(MBB, InsertBStart, DebugLoc(), - TII.get(LinxISA::BSTART_STD_CALL)) + TII.get(UseFPHeader ? LinxISA::BSTART_FP_CALL + : LinxISA::BSTART_STD_CALL)) .add(*CallTargetOp) .getInstr(); // Set return target for the call (ra = PC + imm20<<1). The ISA @@ -5448,21 +5527,24 @@ class LinxISABlockify : public MachineFunctionPass { case ExitKind::Ret: // Prefer the compressed BrType marker: C.BSTART (RET). BStartMI = BuildMI(MBB, InsertBStart, DebugLoc(), - TII.get(LinxISA::CBSTART_STD)) + TII.get(UseFPHeader ? LinxISA::CBSTART_FP + : LinxISA::CBSTART_STD)) .addImm(7) // BrType = RET .getInstr(); break; case ExitKind::Ind: // Prefer the compressed BrType marker: C.BSTART (IND). BStartMI = BuildMI(MBB, InsertBStart, DebugLoc(), - TII.get(LinxISA::CBSTART_STD)) + TII.get(UseFPHeader ? LinxISA::CBSTART_FP + : LinxISA::CBSTART_STD)) .addImm(5) // BrType = IND .getInstr(); break; case ExitKind::ICall: // Prefer the compressed BrType marker: C.BSTART (ICALL). BStartMI = BuildMI(MBB, InsertBStart, DebugLoc(), - TII.get(LinxISA::CBSTART_STD)) + TII.get(UseFPHeader ? LinxISA::CBSTART_FP + : LinxISA::CBSTART_STD)) .addImm(6) // BrType = ICALL .getInstr(); // Indirect calls behave like CALL blocks but select the callee via @@ -5593,10 +5675,11 @@ class LinxISABlockify : public MachineFunctionPass { auto markerHasImplicitAbiUses = [&](const MachineInstr &MI) -> bool { const unsigned Opc = MI.getOpcode(); if (Opc == LinxISA::BSTART_STD_CALL || Opc == LinxISA::BSTART_STD_ICALL || - Opc == LinxISA::BSTART_STD_RET) { + Opc == LinxISA::BSTART_STD_RET || Opc == LinxISA::BSTART_FP_CALL) { return true; } - if (Opc == LinxISA::CBSTART_STD && MI.getNumOperands() >= 1 && + if ((Opc == LinxISA::CBSTART_STD || Opc == LinxISA::CBSTART_FP) && + MI.getNumOperands() >= 1 && MI.getOperand(0).isImm()) { const int64_t BrType = MI.getOperand(0).getImm() & 0x7; return BrType == 4 /*CALL*/ || BrType == 6 /*ICALL*/ || diff --git a/llvm/lib/Target/LinxISA/LinxISAInstrInfo.td b/llvm/lib/Target/LinxISA/LinxISAInstrInfo.td index 3df67b49de148..4931671cd1398 100644 --- a/llvm/lib/Target/LinxISA/LinxISAInstrInfo.td +++ b/llvm/lib/Target/LinxISA/LinxISAInstrInfo.td @@ -403,6 +403,8 @@ def : Pat<(sra i32:$a, i32:$b), (SRAWrr $a, $b)>; let hasSideEffects = 1, isBarrier = 1, Size = 2 in def CBSTART_STD : LinxISAInst<(outs), (ins i64imm:$brtype), "", []>; +let hasSideEffects = 1, isBarrier = 1, Size = 2 in +def CBSTART_FP : LinxISAInst<(outs), (ins i64imm:$brtype), "", []>; // 32-bit block start forms used by the BlockISA control-flow model. // @@ -415,6 +417,9 @@ let hasSideEffects = 1, isBarrier = 1, Size = 4 in { def BSTART_STD_IND : LinxISAInst<(outs), (ins), "", []>; def BSTART_STD_ICALL : LinxISAInst<(outs), (ins), "", []>; def BSTART_STD_RET : LinxISAInst<(outs), (ins), "", []>; + def BSTART_FP_DIRECT : LinxISAInst<(outs), (ins BrTarget:$dst), "", []>; + def BSTART_FP_COND : LinxISAInst<(outs), (ins BrTarget:$dst), "", []>; + def BSTART_FP_CALL : LinxISAInst<(outs), (ins CallTarget:$dst), "", []>; } let hasSideEffects = 1, isBarrier = 1, isTerminator = 1, Size = 4 in diff --git a/llvm/lib/Target/LinxISA/LinxISAMCInstLower.cpp b/llvm/lib/Target/LinxISA/LinxISAMCInstLower.cpp index 813159f310639..170cf06064562 100644 --- a/llvm/lib/Target/LinxISA/LinxISAMCInstLower.cpp +++ b/llvm/lib/Target/LinxISA/LinxISAMCInstLower.cpp @@ -308,6 +308,12 @@ void LinxISAMCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI) const { OutMI.addOperand(MCOperand::createImm(I(0))); // BrType return; } + case LinxISA::CBSTART_FP: { + // Compressed block start marker: `C.BSTART.FP BrType`. + OutMI.setOpcode(getSpecOpcode("C.BSTART.FP", /*LengthBits=*/16, /*Fields=*/1)); + OutMI.addOperand(MCOperand::createImm(I(0))); // BrType + return; + } case LinxISA::BSTART_STD_FALL: { OutMI.setOpcode(getSpecOpcodeByAsmFmt("BSTART.STD FALL<, fixup_label>", /*LengthBits=*/32)); @@ -327,11 +333,26 @@ void LinxISAMCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI) const { OutMI.addOperand(lowerBranchTarget(0)); return; } + case LinxISA::BSTART_FP_DIRECT: { + OutMI.setOpcode(getSpecOpcodeByAsmFmt("BSTART.FP DIRECT,