Skip to content

Calling Convention Lowering: support function pointers #995

@gitoleg

Description

@gitoleg

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions