I am writing an LLVM pass which has to deal with structure fields and I came across this behaviour which I cannot explain to myself.
I have the following C++ file:
struct vec3 {
unsigned x, y, z;
};
vec3 foo();
vec3 *foo2();
void bar(vec3 d);
void bar2(vec3 *d);
void user() {
foo();
foo2();
vec3 tmp;
bar(tmp);
bar2(&tmp);
}
When compiled to LLVM IR using clang++ -cc1 -emit-llvm vec3test.cpp -O3, we get this output (I am pasting only the relevant bits):
%struct.vec3 = type { i32, i32, i32 }
; Function Attrs: mustprogress nounwind
define dso_local void @_Z4userv() local_unnamed_addr #0 {
entry:
%tmp = alloca %struct.vec3, align 8
%call = tail call { i64, i32 } @_Z3foov() #3
%call1 = tail call %struct.vec3* @_Z4foo2v() #3
%0 = bitcast %struct.vec3* %tmp to i8*
call void @llvm.lifetime.start.p0i8(i64 12, i8* nonnull %0) #3
tail call void @_Z3bar4vec3(i64 undef, i32 undef) #3
call void @_Z4bar2P4vec3(%struct.vec3* nonnull %tmp) #3
call void @llvm.lifetime.end.p0i8(i64 12, i8* nonnull %0) #3
ret void
}
declare { i64, i32 } @_Z3foov() local_unnamed_addr #1
declare %struct.vec3* @_Z4foo2v() local_unnamed_addr #1
declare void @_Z3bar4vec3(i64, i32) local_unnamed_addr #1
declare void @_Z4bar2P4vec3(%struct.vec3*) local_unnamed_addr #1
So it seems that when a pointer is being passed around (as an argument or return value), it has a type { i64, i32 }. It looks like the x and y fields were combined in one i64.
However, when a pointer is involved, the type becomes { i32, i32, i32 }, which is to be expected.
I thought this might be because of some optimisation, however even when compiled using -O0 the result is the same.
Why does this happen? This makes it necessary to do type conversions when you mix functions that use pointers and ones that use values so it does not seems like a good idea to me.