add intrinsic function support for customized backend

Hi, All,

I want to add one intrinsic function for my particular backend. Let’s say the intrinsic function is named “foo” which takes two i32 inputs and has one i32 output.

First, I add this line “def int_foo : Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty], [IntrReadArgMem]>;” in /include/llvm/IR/Intrinsics.td.

Then, in my target/InstrInfo.td, I’m supposed to add one pattern to match this intrinsic function. However, I don’t understand how LLVM can tell the difference between this intrinsic function call and other normal function calls. From the IR, I can see both intrinsic functions and other normal functions use ‘call’.

So my question is how to add intrinsic function support for customized backend? Any hint/suggestion is appreciable.

Regards,

Xiangyang

Hi Xiangyang,

When your intrinsic is passed to the back-end, it will be converted into DAG nodes automatically (like for a function call). Then the back-end need to know how to convert it into real instructions or at least how to manage it through instruction selection pass. An intrinsic cannot exist inside the backend (it is IR code) but can be converted into pseudo-instruction instead (at least for the X86 backend). Then, there’s many ways to handle pseudo-instructions inside a backend.

I will take the X86 backend as an example.

First, your intrinsics:

def int_foo : Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty], [IntrReadArgMem]>;

It will be converted into DAG nodes. Then, you can handle it manually inside the code or using TableGen mechanism. For the latter, you should define a pseudo-instruction that match your intrinsic-translated-into-dag-node. The pseudo-instruction definition should look like this:

let isPseudo = 1 in {
def FOO : PseudoI<(outs i32mem:$dst), (ins i32mem:$src1, i32mem:$src2, ), [(set i32mem:$dst, (int_foo i32mem:$src1, i32mem:$src2))]>;
}

First, you should always set isPeudo to 1 if it is a pseudo-instruction. Then, if it has some side effect, you should define them. For example, set Defs = [EFLAGS] if it impacts EFLAGS value.
The tricky part is the pattern matching for dag nodes. I’m not sure it’s correct. I’m used to enable debug pass in Clang to have the resulted dag node representation of my intrinsics and then create my pseudo-instruction definition based on it.

When your intrinsic is correctly translated into pseudo-instruction, you can use it where you want. If you need to convert it into real instructions, there’s some common place to do it.

You have the ExpandISelPseudos pass which is called at the beginning of addMachinePasses. Its operation is relatively simple since it browses the MachineInstr by looking for pseudo-instructions and then calls TargetLowering::EmitInstrWithCustomeInserter for each of them. This last method being abstract, it is implemented by each backend that wants it like in X86TargetLowering for the x86 backend. Due to its location, this solution offers the advantage that no optimization has already taken place. Thus, the added machine code will be optimized in the same way than any other options of the program. Moreover, you still have the virtual register abstraction allowing you to be more flexibility in your implementation.

You also have ExpandPostRA pass. This one commes right after register allocation and the addition of the prolog-epilog. It calls TargetInstrInfo::expandPostRAPseudo() giving a chance to the target to extend the pseudo-instruction encountered. For the backend X86, the TargetInstrInfo concrete implementation is in X86InstrInfo. As register allocation and the majority of previous optimizations have already been done, this solution ensures that the added code will not be altered afterwards.

Finally, it the two previous passes are not suitable for a particular reason, other more generic ways exist. Simply create a new MachineFunctionPass and call it when you need it. For example from:

  • addPreRegAlloc
  • addPostRegAlloc
  • addPreSched2
  • addPreEmitPass

I don’t have a big LLVM background but thus are my findings when I was playing with the middle-end/back-end some time ago.

Regards,
Gaël

Hi, Gaël,

Thanks for your detailed reply. I tried the pattern matching first, however, the intrinsic function is matching with a normal function call.

For example, I have the following IR, which contains intrinsic function “foo”:

What's your definition of int_foo? It should be coming from
include/llvm/IR/IntrinsicsXYZ.td. If the expected name and types don't
match up with that .td file LLVM tends to just assume you meant a real
call.

Also, make sure you've included IntrinsicsXYZ.td in Intrinsics.td.

Tim.

I just put the definition of int_foo in file include/llvm/IR/Intrinsics.td like this “def int_foo : Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty], [IntrReadArgMem]>;”, is that a problem? Is a separate file IntrinsicsMyTarget.td necessary in this case?

Thanks

It depends on use (Intrinsics.td is for target-agnostic ones), but
would probably be advised if the intrinsic is target-specific. It's
obviously up to you for a local project though.

In any case, I think the problem is that the ".i32.i32" in
"@llvm.foo.i32.i32" is only needed if you use llvm_anyint_ty (or
similar) for some of the parameters. In this case the declaration
should be simply "declare i32 @llvm.foo(i32, i32)".

Cheers.

Tim.