How to describe the attribute of, the 'def-use' chain between implicit registers, of an intrinsic?

I tried to define two intrinsics to read and write an implicit registers. But clang does not seem to recognize them correctly, so that the function is removed with ‘-O2’ option.

  • The definition of intrinsics is as follows:
// to prevent "IntrHasSideEffects" be overwritten, didn't use "IntrReadmem"
class ImplicitRegLoad: Intrinsic<[], [llvm_i32_ty, LLVMPointerType<llvm_i32_ty>], 
                  			  [IntrHasSideEffects]>;
class ImplicitRegRead: Intrinsic<[llvm_i32_ty], [llvm_i32_ty], [IntrHasSideEffects]>;
def int_riscv_loadToReg: ImplicitRegLoad;
def int_riscv_readFromReg: ImplicitRegRead;
  • The source code for test case is as follows:
#include <stdio.h>
#include <stdlib.h>

int data[4] = {0xffff5555, 0x12345678, 0x77777777, 0x00000001};

int main(int argc, char *argv[]) {
  int test_value;

  __builtin_riscv_loadToReg(28, &data[0]); /* load data to implicit reg */
  test_value = __builtin_riscv_readFromReg(28);/* read data from implicit reg */
  if (test_value != 0xffff5555) {/* check reg value */
      printf("fail1\n");
      exit(1);
  }
    
  __builtin_riscv_loadToReg(28, &data[2]);
  test_value = __builtin_riscv_readFromReg(28);
  if (test_value != 0x77777777) {
      printf("fail2\n");
      exit(2);
  }
  
  printf("pass\n");
  return 0;
}
  • The compilation command used is as follows:
clang --target=riscv32 -march=rv32imafcv -emit-llvm -S -O2 foo.cpp -o foo.ll

The main code in file “foo.ll” is as follows:

entry:(main)
  // first branch, load data to implicit register
  tail call void @llvm.riscv.loadToReg(i32 0, i32* getelementptr inbounds ([4 x i32], [4 x i32]* @data, i32 0, i32 0)) 
  // first branch, read data from implicit register
  %0 = tail call i32 @llvm.riscv.readFromReg(i32 0)
  %cmp.not.i = icmp eq i32 %0, -43691
  br i1 %cmp.not.i, label %if.then.i19, label %if.then.i

if.then.i:                                        ; preds = %entry
  %call.i = tail call i32 (i8*, ...) @printf(i8* noundef nonnull dereferenceable(1) getelementptr inbounds ([60 x i8], [60 x i8]* @.str.1, i32 0, i32 0), i32 noundef 1, i32 noundef %0, i32 noundef -43691)
  tail call void @exit(i32 noundef 1) #4
  unreachable

if.then.i19:                                      ; preds = %entry
  // second branch, load new data to implicit register
  tail call void @llvm.riscv.loadToReg(i32 0, i32* getelementptr inbounds ([4 x i32], [4 x i32]* @data, i32 0, i32 1))
  // llvm.riscv.readFromReg not exist!!! It was eliminated by optimization
  %call.i18 = tail call i32 (i8*, ...) @printf(i8* noundef nonnull dereferenceable(1) getelementptr inbounds ([60 x i8], [60 x i8]* @.str.1, i32 0, i32 0), i32 noundef 2, i32 noundef -43691, i32 noundef 305419896)
  tail call void @exit(i32 noundef 2) #4
  unreachable
}

As we can see, clang treat the second call of ‘readFromReg’ as redundant call, the property ‘IntrHasSideEffects’ does not prevent this optimization.

I don’t know what to do so that clang with ‘-O2’ option doesn’t over-optimize my intrinsic, and the clang version is 14.0.6. Any suggestions would be helpful, thanks!

I did a couple of quick tests and it didn’t immediately eliminate a readFromReg call for me, so maybe the original IR is a bit weird in some way. What’s the output with -Xclang -disable-llvm-passes?

1 Like

I’m sorry, I forgot to add the ‘sideEffect’ attribute to the readFromReg(In fact, I forgot to install the new version). It’s work fine. Thanks for your reply!