Closed blackgeorge-boom closed 1 year ago
This seems like an unhandled case of the current StackMaps infrastructure.
Minimal reproducible example:
#include <stdio.h>
struct S {
int x, y;
int* z, w;
};
void f(struct S s) {
printf("hey!\n");
s.x++;
return;
}
int main() {
struct S s = {1, 2};
f(s);
}
When passing a struct by value and through memory, LLVM produces an IR of the following form for the callee:
define dso_local void @f(%struct.S* byval(%struct.S) align 8 %s) #0 { <---
entry:
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @main__str_hey___, i64 0, i64 0)) #3
call void (i64, i32, ...) @llvm.experimental.pcn.stackmap(i64 0, i32 0, %struct.S* %s) <---
%x = getelementptr inbounds %struct.S, %struct.S* %s, i32 0, i32 0
...
ret void
}
The struct parameter %s
has the attribute byval
. This implies that the value is passed through memory, but also that the callee cannot modify the value of the caller (even though the argument is a pointer, because there is an implicit copy at the time of the call): https://discourse.llvm.org/t/how-does-byval-work/62397.
The problem with the Popcorn StackMaps is that they record the live value of struct %s
as simply %struct.S* %s
, without including the byval
attribute as in the function definition. However, they can only handle pointers that are either temporary values or allocas, while %s
is technically neither of the two (it doesn't have an alloca like a usual parameter, because of the byval
attribute).
As a quick fix, we can modify the struct parameter and pass it through a pointer instead, without affecting the semantics of the program since it won't modify the contents of the pointer. The result is the following:
#include <stdio.h>
struct S {
int x, y;
int* z, w;
};
void f(struct S *s) {
printf("hey!\n");
s->x++;
return;
}
int main() {
struct S s = {1, 2};
f(&s);
}
and
define dso_local void @f(%struct.S* %s) #0 { <---
entry:
%s.addr = alloca %struct.S*, align 8 <---
store %struct.S* %s, %struct.S** %s.addr, align 8
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @main__str_hey___, i64 0, i64 0)) #3
call void (i64, i32, ...) @llvm.experimental.pcn.stackmap(i64 0, i32 0, %struct.S** %s.addr) <---
...
ret void
}
Now, the StackMaps are recorded properly.
As a side note, if the struct that is passed by value, could fit in the argument registers after coercion of its fields, instead of being passed through memory, there would not be an issue:
#include <stdio.h>
struct S {
int x, y;
};
void f(struct S s) {
printf("hey!\n");
s.x++;
return;
}
int main() {
struct S s = {1, 2};
f(s);
}
and:
define dso_local void @f(i64 %s.coerce) #0 { <---
entry:
%s = alloca %struct.S, align 4 <---
%0 = bitcast %struct.S* %s to i64*
store i64 %s.coerce, i64* %0, align 4
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @main__str_hey___, i64 0, i64 0)) #3
call void (i64, i32, ...) @llvm.experimental.pcn.stackmap(i64 0, i32 0, %struct.S* %s) <---
...
ret void
}
Notice that the struct that has only two integers is passed through an i64
register and is properly recorded in the stackmaps.
Closing this for now.