Custom intrinsic with implicit register usage fails to find proper register class in tablegen

Hi,

I’m trying to add a new intrinsic to RISC-V which uses an implicit register as it’s done in AARch64. This worked for LLVM13, but for LLVM15 I experience some problems with tblgen and failing to find the proper register class.

The intrinsic itself is pretty similar to AArch64 one:
RISC-V:

let Predicates = [IsRV64], Uses = [ X5 ], Defs = [ X1, X6, X7, X28, X29, X30, X31 ] in {            
 def HWASAN_CHECK_MEMACCESS : Pseudo<                                                                
   (outs), (ins GPRNoX1X6X7X28X29X30X31:$ptr, i32imm:$accessinfo),                                   
   [(int_hwasan_check_memaccess X5, GPRNoX1X6X7X28X29X30X31:$ptr, (i32 timm:$accessinfo))]>;         
 def HWASAN_CHECK_MEMACCESS_SHORTGRANULES : Pseudo<                                                  
   (outs), (ins GPRNoX1X6X7X28X29X30X31:$ptr, i32imm:$accessinfo),                                   
   [(int_hwasan_check_memaccess_shortgranules X5, GPRNoX1X6X7X28X29X30X31:$ptr , (i32 timm:$accessinfo))]>;
}

AArch64:

let Uses = [ X9 ], Defs = [ X16, X17, LR, NZCV ] in {                                               
 def HWASAN_CHECK_MEMACCESS : Pseudo<                                                                
   (outs), (ins GPR64noip:$ptr, i32imm:$accessinfo),                                                 
   [(int_hwasan_check_memaccess X9, GPR64noip:$ptr, (i32 timm:$accessinfo))]>,                       
   Sched<[]>;                                                                                        
 }                                                                                                                                                                                                        
 let Uses = [ X20 ], Defs = [ X16, X17, LR, NZCV ] in {                                              
 def HWASAN_CHECK_MEMACCESS_SHORTGRANULES : Pseudo<                                                  
   (outs), (ins GPR64noip:$ptr, i32imm:$accessinfo),                                                 
   [(int_hwasan_check_memaccess_shortgranules X20, GPR64noip:$ptr, (i32 timm:$accessinfo))]>,        
   Sched<[]>;                                                                                        
}

The problem here is with this implicit X5 usage: while parsing patterns, the type of this X5 becomes iPTR, which leads to failure in global isel emitter while looking for the best register class. For AArch64’s X9 everything works fine.

Originally both X5 and X9 contain no register type and they’re filled in CodeGenTarget::getRegisterVTs.

  • RISC-V: HWASAN_CHECK_MEMACCESS: (intrinsic_void 153:{ *:[iPTR] }, X5:{ *:[i32 f16 f32 f64 Untyped] m1:[i64 f16 f32 f64 Untyped] }, GPRNoX1X6X7X28X29X30X31:{ *:[i32] m1:[i64] }:$ptr, (timm:{ *:[i32] }):$accessinfo)
  • AARch64 we get: HWASAN_CHECK_MEMACCESS: (intrinsic_void 153:{ *:[iPTR] }, X9:{ *:[i64] }, GPR64noip:{ *:[i64] }:$ptr, (timm:{ *:[i32] }):$accessinfo)

It looks quite legitimate. Please correct me if I’m wrong.

Then in TypeSetByHwMode::intersect these types are intersected with iPTR to get the most specific scalar type. That’s the comment from this function:

   // The intersection of iPTR with a set of integer scalar types that does not                      
   // include iPTR will result in the most specific scalar type:                                     
   // - iPTR is more specific than any set with two elements or more                                 
   // - iPTR is less specific than any single integer scalar type.                                   
   // For example                                                                                    
   // { iPTR } * { i32 }     -> { i32 }                                                              
   // { iPTR } * { i32 i64 } -> { iPTR }                                                             
   // and                                                                                            
   // { iPTR i32 } * { i32 }          -> { i32 }                                                     
   // { iPTR i32 } * { i32 i64 }      -> { i32 i64 }                                                 
   // { iPTR i32 } * { i32 i64 i128 } -> { iPTR i32 }                                                

So for RISC-V the type of X5 now becomes X5:{ *:[iPTR] m1:[iPTR] } while for AArch64 it’s still X9:{ *:[i64] }.

And then in CodeGenRegBank::getMinimalPhysRegClass proper register class is easilly matched, while for RISC-V it fails.

Unfortunately, I don’t know much about LLVM backend or RISC-V backend specifically, so my question here is: what’s the proper way to fix this?
Should we be able to find a register class for iPTR for RISC-V target?
Should we somehow/somewhere along the road convert this iPTR to i32/i64 based on rv32/64 architecture?

Any help would be greatly appreciated.

Thanks!

This looks like a bug. Do you have a patch that reproduces this in main?

Hi @kparzysz-quic,

Sure. Unfortunately I can’t attach the patch here since I’m a new member, but please check this
pastebin

For reproduction:

llvm-tblgen -gen-global-isel -I ${LLVM_SRC}/llvm/lib/Target/RISCV -I${LLVM_BUILD}/include -I${LLVM_SRC}/llvm/include -I ${LLVM_SRC}/llvm/lib/Target ${LLVM_SRC}/llvm/lib/Target/RISCV/RISCV.td --write-if-changed -o ${LLVM_BUILD}/lib/Target/RISCV/RISCVGenGlobalISel.inc

It took a few commits to sort this out, but it should work now in main.

Btw, you have X30 listed twice in your RegisterInfo.td.

It works now, thanks for such a fast fix!