I am writing a compiler that emits LLVM ir and wishes to use the same calling conventions as C to be compatible with C programs.
Part of the problem is handled by the default C calling convention present in LLVM ir, but part of it is handled by clang.
if i run clang++ -S -emit-llvm -o
on the following source code on x86_64
struct X{
int x;
int y;
int j;
int k;
};
struct X f() {
struct X x;
return x;
}
the emitted ir is
%struct.X = type { i32, i32, i32, i32 }
; Function Attrs: noinline nounwind optnone uwtable
define dso_local { i64, i64 } @f() #0 {
%1 = alloca %struct.X, align 4
%2 = bitcast %struct.X* %1 to { i64, i64 }*
%3 = load { i64, i64 }, { i64, i64 }* %2, align 4
ret { i64, i64 } %3
}
while if i add a extra field
struct X{
int x;
int y;
int j;
int k;
int m;
};
struct X f() {
struct X x;
return x;
}
the resulting ir is
%struct.X = type { i32, i32, i32, i32, i32 }
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @f(%struct.X* noalias sret(%struct.X) align 4 %0) #0 {
ret void
}
The struct gained a extra field and because of the parameter passing rules it got promoted to be a pointer passed by the caller.
I understand that at least some part of this process is driven by this file https://github.com/llvm/llvm-project/blob/main/clang/include/clang/CodeGen/CodeGenABITypes.h that converts a type into a llvm::type that respects the calling conventions, but i have not found a comprehensive guide explaining all design decision regarding the IR calling conventions.
- How do i know which rules related to parameter passing and calling conventions in general are correctly handled by the LLVM ir and which are not?
- Why has it been implemented this way instead of letting the IR handle all this intricacies in the backend?
- Do i have a alternative that does not involve relying on the implementation of clang, nor re-implements the whole logic for each target, to figure out how my functions should be lowered to LLVM IR?