Create an Intrinsic that is not removed during SelectionDAG process

Is there a flag, attribute, or feature to assign to an intrinsic function so that it does not get removed if it does not affect the program? My Intrinsic is for simply informing the processor on the code executing.

Currently, I can keep my intrinsic from getting removed this way

  %1 = alloca i64, align 8
  %2 = call i64 @llvm.MyIntrinsic.i64.i64.i64(i64 2, i64 57005)
  store i64 %2, ptr %1, align 8

I have tried to change the source code but I am stuck on the last step SelectionDAG Scheduling and Formation Phase. I am wondering if there is a better to do this that is less invasive?

Mark it as having side-effects.

I tried IntrHasSideEffects and that does not do anything. I have tried other attributes like throw too

Here the intrinsic
def int_test : Intrinsic<[], [llvm_any_ty, llvm_any_ty], [IntrHasSideEffects]>;

IR

; Function Attrs: noinline nounwind uwtable
define dso_local void @foo() #0 {
  call void @llvm.test.i64.i64(i64 2, i64 57005)
  %1 = call i32 (ptr, ...) @printf(ptr noundef @.str)
  ret void
}

It does not get past the optimized lowered selection DAG stage

Initial selection DAG: %bb.0 ‘foo:’
SelectionDAG has 17 nodes:
t3: i64 = G_TEST Constant:i64<2>, Constant:i64<57005>
//some other outputs

Optimized lowered selection DAG: %bb.0 ‘foo:’
SelectionDAG has 12 nodes:
t0: ch,glue = EntryToken
t7: ch,glue = callseq_start t0, TargetConstant:i64<0>, TargetConstant:i64<0>
t9: ch,glue = CopyToReg t7, Register:i64 $x10, GlobalAddress:i64<ptr @.str> 0
t12: ch,glue = RISCVISD::CALL t9, TargetGlobalAddress:i64<ptr @printf> 0 [TF=2], Register:i64 $x10, RegisterMask:Untyped, t9:1
t13: ch,glue = callseq_end t12, TargetConstant:i64<0>, TargetConstant:i64<0>, t12:1
t14: i64,ch,glue = CopyFromReg t13, Register:i64 $x10, t13:1
t16: ch = RISCVISD::RET_GLUE t14:1

What is the G_TEST? Seems like the initial DAG doesn’t even have that intrinsic in it. If G_TEST is an instruction, it should also have side-effects.

G_TEST is my instrinsic. Its how I declared it in the TargetOpcodes.def file

HANDLE_TARGET_OPCODE(G_TEST)

So that when my intrinsic gets called in the SelectionDAGBuilder.cpp file
that will be the opcode

  case Intrinsic::test:
  {
      SDValue Ops[2];
      SDValue Root = getRoot();
      Ops[0] = getValue(I.getArgOperand(0));
      Ops[1] = getValue(I.getArgOperand(1));
      EVT voidType = MVT::isVoid;
      MachineSDNode* Op = DAG.getMachineNode(TargetOpcode::G_TEST, sdl,  Ops[0].getValueType(), Ops[0], Ops[1]);
      SDValue Op_new1 = SDValue(Op, 0);
      setValue(&I, Op_new1);
      DAG.setRoot(Root);
      return; 
  }

Would it be better to add one more step at the end of the DAG process to remove the nodes(instructions) that I don’t need? Like the store operation in my original question

The G_TEST instruction needs to have the side-effects property as well. Why don’t you just leave the intrinsic? Do you need to convert it to an instruction this early?

I am now changing the intrinsic to an MIR during the Instruction Selection phase of the DAG, and I have added side-effects to G_TEST and TEST (see code below) and its partially working.
when I have just a single test intrinsic; it will be added to my assembly output using llc, however If I do more than one test intrinsic the last one is chosen out of all them(See IR below). in my assembly the output will only be for IR %3. I think this has something to do with the scheduler in the last step. How can I make it so that any TEST instruction will not get optimized out?

define dso_local i32 @main() #0 {
  %1 = call i64 @llvm.test.i64.i64.i64(i64 1, i64 2)
  %2 = call i64 @llvm.test.i64.i64.i64(i64 3, i64 4)
  %3 = call i64 @llvm.test.i64.i64.i64(i64 6, i64 7)
  ret i32 0
}
llvm/include/llvm/Target/Target.td
def G_TEST : StandardPseudoInstruction {
  let OutOperandList = (outs unknown:$dst);
  let InOperandList = (ins unknown:$src1, unknown:$src2);
  let mayLoad = false;
  let mayStore = false;
  let hasSideEffects = true;
}
llvm/lib/Target/RISCV/RISCVInstrInfo.td
let hasSideEffects = 1, mayLoad = 0, mayStore = 0 in
class ALU_rrTEST<bits<7> funct7, bits<3> funct3, string opcodestr,
             bit Commutable = 0>
    : RVInstR<funct7, funct3, OPC_CUSTOM_1, (outs GPR:$rd), (ins GPR:$rs1, GPR:$rs2),
              opcodestr, "$rd, $rs1, $rs2">{
  let isCommutable = Commutable;
}

let hasSideEffects = 1, mayLoad = 0, mayStore = 0 in {
def TEST  : ALU_rrTEST<0b0000000, 0b000, "test", /*Commutable*/1>,
           Sched<[]>;
}

Obviously something goes wrong here, but it’s hard to tell what’s causing it. Do all the intrinsics remain in the code until the selection DAG is created? Do they show up in the initial SDAG? You can dump the SDAG with -debug-only=isel. If the intrinsics disappear somewhere during the SDAG processing, it could hint at what the problem may be. In that case, could you show the SDAG before and after the intrinsics disappear?

Here is the output of the last three steps of the debug. What I am trying to create is an instruction that is treated similar to fences during compilation

===== Instruction selection begins: %bb.0 ‘’

ISEL: Starting selection on root node: t15: ch = RISCVISD::RET_GLUE t14, Register:i64 $x10, t14:1
ISEL: Starting pattern match
  Morphed node: t15: ch = PseudoRET Register:i64 $x10, t14, t14:1
ISEL: Match complete!

ISEL: Starting selection on root node: t14: ch,glue = CopyToReg t10:1, Register:i64 $x10, Constant:i64<0>

ISEL: Starting selection on root node: t10: i64,ch = llvm.test t7:1, TargetConstant:i64<292>, Constant:i64<6>, Constant:i64<7>

ISEL: Starting selection on root node: t7: i64,ch = llvm.test t4:1, TargetConstant:i64<292>, Constant:i64<3>, Constant:i64<4>

ISEL: Starting selection on root node: t4: i64,ch = llvm.test t0, TargetConstant:i64<292>, Constant:i64<1>, Constant:i64<2>

ISEL: Starting selection on root node: t13: i64 = Register $x10

ISEL: Starting selection on root node: t12: i64 = Constant<0>

ISEL: Starting selection on root node: t9: i64 = Constant<7>

ISEL: Starting selection on root node: t8: i64 = Constant<6>

ISEL: Starting selection on root node: t6: i64 = Constant<4>

ISEL: Starting selection on root node: t5: i64 = Constant<3>

ISEL: Starting selection on root node: t3: i64 = Constant<2>

ISEL: Starting selection on root node: t2: i64 = Constant<1>

ISEL: Starting selection on root node: t1: i64 = TargetConstant<292>

ISEL: Starting selection on root node: t0: ch,glue = EntryToken

===== Instruction selection ends:

Selected selection DAG: %bb.0 'main:'
SelectionDAG has 25 nodes:
  t0: ch,glue = EntryToken
  t4: i64,ch = llvm.test t0, TargetConstant:i64<292>, t32, t30
    t18: i64,ch = G_TEST t32, t30
  t7: i64,ch = llvm.test t18:1, TargetConstant:i64<292>, t28, t26
    t17: i64,ch = G_TEST t28, t26
  t10: i64,ch = llvm.test t17:1, TargetConstant:i64<292>, t24, t22
    t16: i64,ch = G_TEST t24, t22
    t20: i64,ch = CopyFromReg t0, Register:i64 $x0
  t14: ch,glue = CopyToReg t16:1, Register:i64 $x10, t20
  t22: i64 = ADDI Register:i64 $x0, TargetConstant:i64<7>
  t24: i64 = ADDI Register:i64 $x0, TargetConstant:i64<6>
  t26: i64 = ADDI Register:i64 $x0, TargetConstant:i64<4>
  t28: i64 = ADDI Register:i64 $x0, TargetConstant:i64<3>
  t30: i64 = ADDI Register:i64 $x0, TargetConstant:i64<2>
  t32: i64 = ADDI Register:i64 $x0, TargetConstant:i64<1>
  t15: ch = PseudoRET Register:i64 $x10, t14, t14:1

Total amount of phi nodes to update: 0
*** MachineFunction at end of ISel ***

Machine code for function main: IsSSA, TracksLiveness

bb.0 (%ir-block.0):
  %0:gpr = ADDI $x0, 7
  %1:gpr = ADDI $x0, 6
  %2:gpr = G_TEST %1:gpr, %0:gpr
  %3:gpr = COPY $x0
  $x10 = COPY %3:gpr
  PseudoRET implicit $x10

End machine code for function main.