When compiling code using LLVM in normal mode without pure-capability, I was curious as to why the printf function used capability registers? I found this out when I was trying to use the printf function in EL2 which didn't work and fell over on an instruction using capability registers. I then realised I needed to switch on the 'enable morello instructions' bit for EL2 which is turned off by default in the _start code.
So I have a general question as to why capability registers are used when compiling for normal mode without pure-capability switched on? I'm using Development Studio, with target set to Morello. Under what condition by the compiler does it decide to use these registers?
There are several things of note here:
1. If you compile with -march=morello, the existence of capability registers is assumed. It is rare that they will be used on code that doesn't use __capability, but not out of the question; for example, if you write code that requires a tag-preserving memcpy (because the compiler can't prove there aren't capabilities, such as when you have a large `char` buffer) and the compiler can prove the pointer is aligned enough for capability loads/stores then it will inline the memcpy using capability registers: https://cheri-compiler-explorer.cl.cam.ac.uk/z/xoerWP
2. Various libc functions, being compiled for hybrid C, have to be tag-preserving, so memcpy/memmove/etc will use load/store capability instructions to copy the aligned subregions.
3. The current Morello ABI for varargs in hybrid C is extremely inefficient and a little fragile (tracked as https://git.morello-project.org/morello/llvm-project/-/issues/11), spilling all capability argument registers on function entry along with all the aliasing integer argument registers (for CHERI-RISC-V we only spill the latter and pass capabilities indirectly to varargs functions, avoiding that entirely). This means any hybrid ABI varargs function on Morello will unconditionally use capability registers when called. This is what you are seeing with printf.
In summary, if you are compiling code with -march=morello, you must have capability registers accessible at run time; you can get away with it to a certain extent, but sooner or later you will run into something like this. The only way to avoid capability registers is to build with -march=morello+noa64c (including all libraries), which will disable all the CHERI parts of the Morello architecture and just give you the Armv8.2+extensions baseline that Morello extends with CHERI.
Many thanks for the detailed explanation!