You’re exporting a spirv intrinsic, it should just be a spirv builtin usable from any language.
Doing it this way means we need to introduce arch specific macros into our header which was something we wanted to avoid. Moving that logic to the TargetBuiltin was our way of abstracting away that requirement.
is there a reason you can’t just treat this as an optimization pattern in the backend?
Are you asking if you can optimization pattern for the builtin or for IR we are generating for the generic case? I’m going to assume generic case.
The problem with pattern matching the ir below:
%sub.i = fsub <4 x float> %X, %Y
%mul.i = fmul <4 x float> %sub.i, %sub.i
%rdx.fadd.i = tail call float @llvm.vector.reduce.fadd.v4f32(float 0.000000e+00, <4 x float> %mul.i)
%0 = tail call noundef float @llvm.sqrt.f32(float %rdx.fadd.i)
is that since length and distance share the same code with the exception of the fsub
the pattern match is order dependent. which isn’t ideal.
Also the SPIRV backend doesn’t have a SPIRVGIsel.td
so there is no tablegen matcher to leverage the def : Pat<
for this case. I would have to write the pattern matcher in Instruction selector on a case by case basis. Below is an example of what that would look like. And it ends up being about 100 lines of unpleasant code to parse especially because you have to account for G_INTRINSIC_W_SIDE_EFFECTS
wrappings of input and outputs.
bool SPIRVInstructionSelector::spvDistancePatternMatch(Register ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {
if (I.getOpcode() != TargetOpcode::G_FSQRT)
return false;
MachineRegisterInfo &MRI = I.getMF()->getRegInfo();
Register AssignTypeReg = I.getOperand(1).getReg();
MachineInstr *AssignTypeInst = MRI.getVRegDef(AssignTypeReg);
if (!AssignTypeInst ||
AssignTypeInst->getDesc().getOpcode() != SPIRV::ASSIGN_TYPE)
return false;
Register DotReg = AssignTypeInst->getOperand(1).getReg();
MachineInstr *DotInstr = MRI.getVRegDef(DotReg);
if (!DotInstr || DotInstr->getOpcode() != TargetOpcode::G_INTRINSIC ||
cast<GIntrinsic>(DotInstr)->getIntrinsicID() != Intrinsic::spv_fdot)
return false;
// DotInstr first operand is `G_INTRINSIC` so start at operand 2.
Register Sub1AssignTypeReg = DotInstr->getOperand(2).getReg();
MachineInstr *Sub1AssignTypeInst = MRI.getVRegDef(Sub1AssignTypeReg);
if (!Sub1AssignTypeInst ||
Sub1AssignTypeInst->getDesc().getOpcode() != SPIRV::ASSIGN_TYPE)
return false;
Register Sub2AssignTypeReg = DotInstr->getOperand(3).getReg();
MachineInstr *Sub2AssignTypeInst = MRI.getVRegDef(Sub2AssignTypeReg);
if (!Sub2AssignTypeInst ||
Sub2AssignTypeInst->getDesc().getOpcode() != SPIRV::ASSIGN_TYPE)
return false;
Register SubReg1 = Sub1AssignTypeInst->getOperand(1).getReg();
Register SubReg2 = Sub2AssignTypeInst->getOperand(1).getReg();
MachineInstr *SubInstr1 = MRI.getVRegDef(SubReg1);
MachineInstr *SubInstr2 = MRI.getVRegDef(SubReg2);
if (!SubInstr1 || !SubInstr2 ||
SubInstr1->getOpcode() != TargetOpcode::G_FSUB ||
SubInstr2->getOpcode() != TargetOpcode::G_FSUB)
return false;
if (SubInstr1->getOperand(1).getReg() != SubInstr2->getOperand(1).getReg() ||
SubInstr1->getOperand(2).getReg() != SubInstr2->getOperand(2).getReg())
return false;
// selectExtInst(ResVReg, ResType, I, CL::distance, GL::Distance);
ExtInstList ExtInsts = {{SPIRV::InstructionSet::OpenCL_std, CL::distance},
{SPIRV::InstructionSet::GLSL_std_450, GL::Distance}};
MachineBasicBlock &BB = *I.getParent();
for (const auto &Ex : ExtInsts) {
SPIRV::InstructionSet::InstructionSet Set = Ex.first;
uint32_t Opcode = Ex.second;
if (STI.canUseExtInstSet(Set)) {
MachineInstrBuilder NewInstr =
BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpExtInst));
NewInstr.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addImm(static_cast<uint32_t>(Set))
.addImm(Opcode)
.addUse(SubInstr1->getOperand(1).getReg())
.addUse(SubInstr1->getOperand(2).getReg())
.constrainAllUses(TII, TRI, RBI);
}
}
if (I.getNumDefs() > 0) { // Make all vregs 64 bits (for SPIR-V IDs).
for (unsigned Index = 0; Index < I.getNumDefs(); ++Index)
MRI.setType(I.getOperand(Index).getReg(), LLT::scalar(64));
}
// G_INTRINSIC for `fdot` have `G_INTRINSIC_W_SIDE_EFFECTS`
// using its argument and return registers. Remove these uses.
auto RemoveAllUses = [&](Register Reg) {
for (auto &UseMI : MRI.use_instructions(Reg)) {
UseMI.eraseFromParent();
}
};
// Clean up: erase the original instructions
I.eraseFromParent(); // Remove FSQRT
AssignTypeInst->eraseFromParent(); // Remove DOT ASSIGN
RemoveAllUses(AssignTypeReg); // Remove intrinsic return side effect uses
DotInstr->eraseFromParent(); // Remove spv.fdot
Sub1AssignTypeInst->eraseFromParent();
RemoveAllUses(Sub1AssignTypeReg); // Remove intrinsic argument side effect
// uses
SubInstr1->eraseFromParent(); // Remove FSUB
if (SubInstr1 != SubInstr2) {
Sub2AssignTypeInst->eraseFromParent();
RemoveAllUses(
Sub2AssignTypeReg); // Remove intrinsic argument side effect uses
SubInstr2->eraseFromParent(); // Remove FSUB
}
return true;
}
bool SPIRVInstructionSelector::spvPatternMatch(Register ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {
if (spvDistancePatternMatch(ResVReg, ResType, I))
return true;
return false;
}