From 74bae94efb361527a6ad5db59e127014dc0c65c3 Mon Sep 17 00:00:00 2001 From: Matt Arsenault Date: Mon, 15 Sep 2025 22:41:07 +0900 Subject: [PATCH] TableGen: Support target specialized pseudoinstructions Allow a target to steal the definition of a generic pseudoinstruction and remap the operands. This works by defining a new instruction, which will simply swap out the emitted entry in the InstrInfo table. This is intended to eliminate the C++ half of the implementation of PointerLikeRegClass. With RegClassByHwMode, the remaining usecase for PointerLikeRegClass are the common codegen pseudoinstructions. Every target maintains its own copy of the generic pseudo operand definitions anyway, so we can stub out the register operands with an appropriate class instead of waiting for runtime resolution. In the future we could probably take this a bit further. For example, there is a similar problem for ADJCALLSTACKUP/DOWN since they depend on target register definitions for the stack pointer register. --- llvm/include/llvm/Target/Target.td | 93 ++++++++++++++++ .../TableGen/target-specialized-pseudos.td | 101 ++++++++++++++++++ llvm/utils/TableGen/Common/CodeGenTarget.cpp | 12 ++- llvm/utils/TableGen/InstrInfoEmitter.cpp | 37 +++++++ 4 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 llvm/test/TableGen/target-specialized-pseudos.td diff --git a/llvm/include/llvm/Target/Target.td b/llvm/include/llvm/Target/Target.td index 13175177edd3e..4a759a99d1d25 100644 --- a/llvm/include/llvm/Target/Target.td +++ b/llvm/include/llvm/Target/Target.td @@ -1574,6 +1574,99 @@ def CONVERGENCECTRL_GLUE : StandardPseudoInstruction { } } +/// Allow a target to replace the instruction definition of a +/// StandardPseudoInstruction. A target should only define one +/// instance of this per instruction. +/// +/// This is intended to allow targets to specify the register class +/// used for pointers. It should not be used to change the fundamental +/// operand structure (e.g., this should not add or remove operands, +/// or change the operand types). +class TargetSpecializedStandardPseudoInstruction< + StandardPseudoInstruction base_inst> : Instruction { + + StandardPseudoInstruction Instruction = base_inst; + let OutOperandList = base_inst.OutOperandList; + let InOperandList = base_inst.InOperandList; + + // TODO: Copy everything + let usesCustomInserter = base_inst.usesCustomInserter; + let hasSideEffects = base_inst.hasSideEffects; + let mayLoad = base_inst.mayLoad; + let mayStore = base_inst.mayStore; + let isTerminator = base_inst.isTerminator; + let isBranch = base_inst.isBranch; + let isIndirectBranch = base_inst.isIndirectBranch; + let isEHScopeReturn = base_inst.isEHScopeReturn; + let isReturn = base_inst.isReturn; + let isCall = base_inst.isCall; + let hasCtrlDep = base_inst.hasCtrlDep; + let isReMaterializable = base_inst.isReMaterializable; + let isMeta = base_inst.isMeta; + let Size = base_inst.Size; + let isAsCheapAsAMove = base_inst.isAsCheapAsAMove; + let isPseudo = true; + let hasNoSchedulingInfo = true; + let isNotDuplicable = base_inst.isNotDuplicable; + let isConvergent = base_inst.isConvergent; + let hasExtraSrcRegAllocReq = base_inst.hasExtraSrcRegAllocReq; + let hasExtraDefRegAllocReq = base_inst.hasExtraDefRegAllocReq; +} + +// All pseudo instructions which need a pointer register class, which +// should be specialized by a target. +defvar PseudosWithPtrOps = [ + LOAD_STACK_GUARD, + PREALLOCATED_ARG, + PATCHABLE_EVENT_CALL, + PATCHABLE_TYPED_EVENT_CALL +]; + + +/// Replace PointerLikeRegClass operands in OperandList with new_rc. +class RemapPointerOperandList { + // Collect the set of names so we can query and rewrite them. + list op_names = !foreach(i, !range(!size(OperandList)), + !getdagname(OperandList, i)); + + // Beautiful language. This would be a lot easier if !getdagarg + // didn't require a specific type. We can't just collect a list of + // the operand values and reconstruct the dag, since there isn't a + // common base class for all the field kinds used in + // pseudoinstruction definitions; therefore everything must be + // maintained as a dag, so use a foldl. Additionally, ? doesn't + // evaluate as false so we get even more noise. + dag ret = + !foldl(OperandList, op_names, acc, name, + !cond( + !initialized(!getdagarg(OperandList, name)) + : !setdagarg(acc, name, new_rc), + !initialized(!getdagarg(OperandList, name)) : acc, + !initialized(!getdagarg(OperandList, name)) : acc + ) + ); +} + +/// Define an override for a pseudoinstruction which uses a pointer +/// register class, specialized to the target's pointer type. +class RemapPointerOperands : + TargetSpecializedStandardPseudoInstruction { + let OutOperandList = + RemapPointerOperandList.ret; + let InOperandList = + RemapPointerOperandList.ret; +} + +/// Helper to replace all pseudoinstructions using pointers to a +/// target register class. Most targets should use this +multiclass RemapAllTargetPseudoPointerOperands< + RegisterClassLike default_ptr_rc> { + foreach inst = PseudosWithPtrOps in { + def : RemapPointerOperands; + } +} + // Generic opcodes used in GlobalISel. include "llvm/Target/GenericOpcodes.td" diff --git a/llvm/test/TableGen/target-specialized-pseudos.td b/llvm/test/TableGen/target-specialized-pseudos.td new file mode 100644 index 0000000000000..99c63f3ec29d9 --- /dev/null +++ b/llvm/test/TableGen/target-specialized-pseudos.td @@ -0,0 +1,101 @@ +// RUN: llvm-tblgen -gen-instr-info -I %p/../../include %s -DONECASE -o - | FileCheck -check-prefixes=CHECK,ONECASE %s +// RUN: llvm-tblgen -gen-instr-info -I %p/../../include %s -DALLCASES -o - | FileCheck -check-prefixes=CHECK,ALLCASES %s +// RUN: not llvm-tblgen -gen-instr-info -I %p/../../include %s -DERROR -o /dev/null 2>&1 | FileCheck -check-prefix=ERROR %s + +// CHECK: namespace llvm::MyTarget { +// CHECK: enum { +// CHECK: LOAD_STACK_GUARD = [[LOAD_STACK_GUARD_OPCODE:[0-9]+]], +// CHECK: PREALLOCATED_ARG = [[PREALLOCATED_ARG_OPCODE:[0-9]+]], +// CHECK: PATCHABLE_EVENT_CALL = [[PATCHABLE_EVENT_CALL_OPCODE:[0-9]+]], +// CHECK: PATCHABLE_TYPED_EVENT_CALL = [[PATCHABLE_TYPED_EVENT_CALL_OPCODE:[0-9]+]], + +// Make sure no enum entry is emitted for MY_LOAD_STACK_GUARD +// CHECK: G_UBFX = [[G_UBFX_OPCODE:[0-9]+]], +// CHECK-NEXT: MY_MOV = [[MY_MOV_OPCODE:[0-9]+]], +// CHECK-NEXT: INSTRUCTION_LIST_END = [[INSTR_LIST_END_OPCODE:[0-9]+]] + + +// CHECK: extern const MyTargetInstrTable MyTargetDescs = { +// CHECK-NEXT: { +// CHECK-NEXT: { [[MY_MOV_OPCODE]], 2, 1, 2, 0, 0, 0, {{[0-9]+}}, MyTargetImpOpBase + 0, 0|(1ULL< + : Register { + let Namespace = "MyTarget"; +} + +class MyClass types, dag registers> + : RegisterClass<"MyTarget", types, size, registers> { + let Size = size; +} + +def X0 : MyReg<"x0">; +def X1 : MyReg<"x1">; +def XRegs : RegisterClass<"MyTarget", [i64], 64, (add X0, X1)>; + + +class TestInstruction : Instruction { + let Size = 2; + let Namespace = "MyTarget"; + let hasSideEffects = false; +} + +#ifdef ONECASE + +// Example setting the pointer register class manually +def MY_LOAD_STACK_GUARD : + TargetSpecializedStandardPseudoInstruction { + let Namespace = "MyTarget"; + let OutOperandList = (outs XRegs:$dst); +} + +#endif + +#ifdef ALLCASES + +defm my_remaps : RemapAllTargetPseudoPointerOperands; + +#endif + + +#ifdef ERROR + +def MY_LOAD_STACK_GUARD_0 : TargetSpecializedStandardPseudoInstruction; + +// ERROR: :[[@LINE+1]]:5: error: multiple overrides of 'LOAD_STACK_GUARD' defined +def MY_LOAD_STACK_GUARD_1 : TargetSpecializedStandardPseudoInstruction; + +#endif + +def MY_MOV : TestInstruction { + let OutOperandList = (outs XRegs:$dst); + let InOperandList = (ins XRegs:$src); + let AsmString = "my_mov $dst, $src"; +} + + +def MyTargetISA : InstrInfo; +def MyTarget : Target { let InstructionSet = MyTargetISA; } diff --git a/llvm/utils/TableGen/Common/CodeGenTarget.cpp b/llvm/utils/TableGen/Common/CodeGenTarget.cpp index 3db0d07eec88f..3493c21f0ab68 100644 --- a/llvm/utils/TableGen/Common/CodeGenTarget.cpp +++ b/llvm/utils/TableGen/Common/CodeGenTarget.cpp @@ -284,15 +284,25 @@ void CodeGenTarget::ComputeInstrsByEnum() const { assert(EndOfPredefines == getNumFixedInstructions() && "Missing generic opcode"); + unsigned SkippedInsts = 0; + for (const auto &[_, CGIUp] : InstMap) { const CodeGenInstruction *CGI = CGIUp.get(); if (CGI->Namespace != "TargetOpcode") { + + if (CGI->TheDef->isSubClassOf( + "TargetSpecializedStandardPseudoInstruction")) { + ++SkippedInsts; + continue; + } + InstrsByEnum.push_back(CGI); NumPseudoInstructions += CGI->TheDef->getValueAsBit("isPseudo"); } } - assert(InstrsByEnum.size() == InstMap.size() && "Missing predefined instr"); + assert(InstrsByEnum.size() + SkippedInsts == InstMap.size() && + "Missing predefined instr"); // All of the instructions are now in random order based on the map iteration. llvm::sort( diff --git a/llvm/utils/TableGen/InstrInfoEmitter.cpp b/llvm/utils/TableGen/InstrInfoEmitter.cpp index 176e4b250b82a..ed32f537d4b5d 100644 --- a/llvm/utils/TableGen/InstrInfoEmitter.cpp +++ b/llvm/utils/TableGen/InstrInfoEmitter.cpp @@ -71,6 +71,13 @@ class InstrInfoEmitter { typedef std::vector OperandInfoListTy; typedef std::map OperandInfoMapTy; + DenseMap + TargetSpecializedPseudoInsts; + + /// Compute mapping of opcodes which should have their definitions overridden + /// by a target version. + void buildTargetSpecializedPseudoInstsMap(); + /// Generate member functions in the target-specific GenInstrInfo class. /// /// This method is used to custom expand TIIPredicate definitions. @@ -215,6 +222,10 @@ InstrInfoEmitter::CollectOperandInfo(OperandInfoListTy &OperandInfoList, const CodeGenTarget &Target = CDP.getTargetInfo(); unsigned Offset = 0; for (const CodeGenInstruction *Inst : Target.getInstructions()) { + auto OverrideEntry = TargetSpecializedPseudoInsts.find(Inst); + if (OverrideEntry != TargetSpecializedPseudoInsts.end()) + Inst = OverrideEntry->second; + OperandInfoTy OperandInfo = GetOperandInfo(*Inst); if (OperandInfoMap.try_emplace(OperandInfo, Offset).second) { OperandInfoList.push_back(OperandInfo); @@ -882,6 +893,25 @@ void InstrInfoEmitter::emitTIIHelperMethods(raw_ostream &OS, } } +void InstrInfoEmitter::buildTargetSpecializedPseudoInstsMap() { + ArrayRef SpecializedInsts = Records.getAllDerivedDefinitions( + "TargetSpecializedStandardPseudoInstruction"); + const CodeGenTarget &Target = CDP.getTargetInfo(); + + for (const Record *SpecializedRec : SpecializedInsts) { + const CodeGenInstruction &SpecializedInst = + Target.getInstruction(SpecializedRec); + const Record *BaseInstRec = SpecializedRec->getValueAsDef("Instruction"); + + const CodeGenInstruction &BaseInst = Target.getInstruction(BaseInstRec); + + if (!TargetSpecializedPseudoInsts.insert({&BaseInst, &SpecializedInst}) + .second) + PrintFatalError(SpecializedRec, "multiple overrides of '" + + BaseInst.getName() + "' defined"); + } +} + //===----------------------------------------------------------------------===// // Main Output. //===----------------------------------------------------------------------===// @@ -904,6 +934,8 @@ void InstrInfoEmitter::run(raw_ostream &OS) { // Collect all of the operand info records. Timer.startTimer("Collect operand info"); + buildTargetSpecializedPseudoInstsMap(); + OperandInfoListTy OperandInfoList; OperandInfoMapTy OperandInfoMap; unsigned OperandInfoSize = @@ -970,6 +1002,11 @@ void InstrInfoEmitter::run(raw_ostream &OS) { for (const CodeGenInstruction *Inst : reverse(NumberedInstructions)) { // Keep a list of the instruction names. InstrNames.add(Inst->getName()); + + auto OverrideEntry = TargetSpecializedPseudoInsts.find(Inst); + if (OverrideEntry != TargetSpecializedPseudoInsts.end()) + Inst = OverrideEntry->second; + // Emit the record into the table. emitRecord(*Inst, --Num, InstrInfo, EmittedLists, OperandInfoMap, OS); }