-
Notifications
You must be signed in to change notification settings - Fork 159
Description
Hi all.
I faced with the necessity of function pointer support in the calling convention lowering pass. And here are several examples that demonstrate the complexity of the problem. I don't even touch calls to functions at all - it's not a problem so far.
The next types are used in the examples below:
typedef struct {
int a;
} S;
typedef int (*myfptr)(S);
typedef struct {
myfptr f;
} T;
Example 1
int foo(S s) { return s.a; }
void bar() {
S s;
myfptr f = foo;
f(s);
}
%1 = cir.alloca !cir.ptr<!cir.func<!s32i (!ty_S)>>, !cir.ptr<!cir.ptr<!cir.func<!s32i (!ty_S)>>>, ["a", init] {alignment = 8 : i64}
%2 = cir.get_global @foo : !cir.ptr<!cir.func<!s32i (!ty_S)>>
cir.store %2, %1 : !cir.ptr<!cir.func<!s32i (!ty_S)>>, !cir.ptr<!cir.ptr<!cir.func<!s32i (!ty_S)>>>
As one can see, the code above needed to be rewritten in order to comply with a calling convetion.
First of all, this code won't compile at all:
'cir.get_global' op result type pointee type ''!cir.func<!cir.int<s, 32> (!cir.struct<struct "S" {!cir.int<s, 32>} #cir.record.decl.ast>)>'' does not match type '!cir.func<!cir.int<s, 32> (!cir.int<s, 32>)>' of the global @foo
Since @foo
has the proper type already: '!cir.func<!cir.int<s, 32> (!cir.int<s, 32>)>'
Ok. we can fix this - and rewrite GetGlobalOp
.
But next we have to do something with AllocaOp
- we can either perform a cast of the get_global
result or rewrite the alloca operation. The former is easier but is not applicable for all the cases (see the next example). The latter means we need to replace AllocaOp
and update its users.
Also, we could relax the GetGlobalOp
verification - but it's not a good way at all and anyway we still depend on the corresponded operation verification from LLVM dialect, which may change eventually.
Example 2
void bar() {
myfptr a[2] = {foo, foo};
}
%1 = cir.alloca !cir.array<!cir.ptr<!cir.func<!s32i (!ty_S)>> x 2>, !cir.ptr<!cir.array<!cir.ptr<!cir.func<!s32i (!ty_S)>> x 2>>, ["a"] {alignment = 16 : i64}
%2 = cir.const #cir.const_array<[#cir.global_view<@foo> : !cir.ptr<!cir.func<!s32i (!ty_S)>>, #cir.global_view<@foo> : !cir.ptr<!cir.func<!s32i (!ty_S)>>]> : !cir.array<!cir.ptr<!cir.func<!s32i (!ty_S)>> x 2>
cir.store %2, %1 : !cir.array<!cir.ptr<!cir.func<!s32i (!ty_S)>> x 2>, !cir.ptr<!cir.array<!cir.ptr<!cir.func<!s32i (!ty_S)>> x 2>>
Here we have the const array of function pointers (not lowered ones, i.e. of a wrong type) and ConstantOp
which (I believe) we can not simple cast to another type, so StoreOp
is wrong. So we have to rewrite it and again - rewrite AllocaOp
and it's users.
Example 3
void bar() {
T t;
t.f = foo;
}
%2 = cir.get_global @foo : !cir.ptr<!cir.func<!s32i (!ty_S)>>
%3 = cir.get_member %1[0] {name = "f"} : !cir.ptr<!ty_T> -> !cir.ptr<!cir.ptr<!cir.func<!s32i (!ty_S)>>>
cir.store %2, %3 : !cir.ptr<!cir.func<!s32i (!ty_S)>>
Here the struct type is involved as a storage for the function pointer. Again either cast is needed or we need to change the struct type itself.
Solution ?
So what I eager to find a good approach for the problem. Right now I think that the existing pass should match against many operations:
AllocaOp
, LoadOp
, StoreOp
, GetMemberOp
, GetGlobalOP
... - looks like its not enough just to call replaceAllUsesWith
once the value type is changed. Also, the pass should perform type conversions (Example 3). I m not sure that it's a good way though - so may be you think of something else ? And the solution is simplier?
@bcardosolopes @sitio-couto