Register alignment in RISCV

In there are multiple definitions of FPR registers with differing alignment and corresponding value types (regTypes parameter): FPR16, FPR32, FPR64. In these definitions alignment argument of RegisterClass class is initialized to the data length.
ex. def FPR32 : RegisterClass<"RISCV", [f32], 32, ...
On the other hand, GPR registers are defined only with 32 or 64 bit lengths, depending on the used architecture (RV32, RV64), and with alignment argument always initialized to 32 bits.
I can also see that GPR class has RegInfos field initialized with SpillAlignment argument set to 32 or 64 bit depending on used architecture.
ex. def GPR : RegisterClass<"RISCV", [XLenVT], 32, ...> { let RegInfos = XLenRI;}

I compiled LLVM IR program (with llc targeting riscv64) using types of different lengths in both int and float format (i8, i16, i32, i64, float, double).
In machine code float and double types use respectively FPR32 and FPR64 classes and in assembly, values are loaded/stored with alignment of 4 and 8 bytes. This is a behavior I was expecting.
Integer types all use GPR register class in machine code, but in assembly are loaded/stored using (sb/lb, sh/lh, sw/lw) and aligned to 1, 2, 4 bytes respectively. This is consistent with LLVM IR instructions (ex. %8 = alloca i16, align 2), but not with alignment specified in RegisterClass for GPR class.

My questions are:
1 What is the purpose of the alignment parameter specified in RegisterClass, as it seems to have no effect on alignment applied in assembly?

If it has an impact on code assembling:
2. Why GPR class is defined with 32 bit alignment but assembly loads/store instructions are have lower alignment values?
3. Why are there no GPR register classes with a lower number of bits specified?
4. Why is GPR register class alignment always set to 32, for both 32 and 64 bit lengths of GPR registers?
5. What is the difference between the alignment parameter in RegistedClass arguments, and the SpillAlignment parameter in RegInfos field?

Code snippet of LLVM IR:
%1 = alloca i8, align 1
%2 = alloca i16, align 2
%3 = alloca i32, align 4
%4 = alloca i64, align 8
%5 = alloca float, align 4
%6 = alloca double, align 8
store i8 11, ptr %1, align 1
store i16 19, ptr %2, align 2
store i32 29, ptr %3, align 4
store i64 37, ptr %4, align 8
store float 1.000000e+00, ptr %5, align 4
store double 2.000000e+00, ptr %6, align 8

Code snippet of assembly:
li a0, 11
sb a0, -17(s0)
li a0, 19
sh a0, -20(s0)
li a0, 29
sw a0, -24(s0)
li a0, 37
sd a0, -32(s0)
lui a0, 260096
sw a0, -36(s0)
li a0, 1
slli a0, a0, 62
sd a0, -48(s0)

  1. The alignment parameter in the register class is used for load/stores created by register allocation when we run out of registers. We need to “spill” a register value to the stack to free up a register.
  2. The alignment is used for spills/reloads.
  3. Unlike FP, there aren’t different sizes of integer instructions. We can’t make much use of different sizes of GPR classes.
    4/5. I think the register class alignment field is ignored when RegInfos is used. So the 32 shouldn’t matter and there’s no way to set it to a value based on the mode.

Okay, thanks for the clarification :slight_smile: