Question about function body and function specialization

Erick Ochoa erick.ochoa@theobroma-systems.com
Wed Jul 15 15:30:09 GMT 2020



On 15.07.20 05:03, Martin Jambor wrote:
> Hi,
> 
> On Tue, Jul 14 2020, Erick Ochoa wrote:
>> On 14/07/2020 12:37, Erick Ochoa wrote:
>>> Hello,
>>>
>>> I have a function foo defined on a source file. Sometimes, a function
>>> pointer pointing to foo is passed as a parameter to other functions
>>> where foo is called indirectly. This indirect call is specialized during
>>> link time. Still at link time, I analyze the function call the following
> 
> Does "at link time" mean that all of the below happens as part of the
> execute method of an IPA pass?  Statements and call graph do diverge for
> a while at that stage (perhaps watching
> https://youtu.be/LBn-0jYKLb8?t=2604 from time 26:04 until about 50:20
> might help a little).

Thanks, I'll give this a watch.
What I meant is that the indirect call is specialized during ipa-cp.

> 
>>> way:
>>>
>>>     // s is the gimple statement which corresponds to the indirect call
>>>     tree fn = gimple_call_fndecl(s);
>>>     // for this particular call the assertions are true
>>>     gcc_assert(fn)
> 
> That can't be.  If s was an indirect call, gimple_call_fndecl(s) would
> return NULL (because gimple_call_fn(gs) would be an SSA_NAME).  Perhaps
> early inlining already converted it to a direct one?

It is not early inlining which converted this indirect function call to 
a direct call. It happens during ipa-cp.

In another pass (scheduled just after materialization) is when I inspect 
gimple instructions of the caller to this indirect function which has 
been specialized.

> 
>>>     cgraph_node *node = cgraph_node::get(fn)
>>>     gcc_assert(node)
>>>
>>> I have analyzed the body of this function previously by using the
>>> FOR_EACH_FUNCTION_WITH_GIMPLE_BODY macro. However, I do not know if
>>> there's a way to link the cnode_graph (or function decl) analyzed in the
>>> macro with the one obtained at the call site. What would be the best way
>>> to say something like:
>>>
>>>     tree fn = gimple_call_fndecl(s);
>>>     // for this particular call the assertions are true
>>>     gcc_assert(fn)
>>>     cgraph_node *node = cgraph_node::get(fn)
>>>     gcc_assert(node)
>>>     bool i_want_this_to_be_true = saw_fn_in_loop_before(node, fn);
> 
> At IPA time, the best way is always to look at the call graph edges,
> something like:
> 
>     cgraph_edge *cs = caller_cgraph_node->get_edge (s);
>     examine (e->callee);
> 
> Note that if the call is truly an indirect one cs->callee will be NULL
> (and cs->indirect_unknown_callee will be set).  Things can also get
> quite a bit more complicated if cs->speculative is set, then there is
> both an indirect and guessed direct edge for a single call.

Thanks! I did read a bunch of source code and I was wondering why all 
the constant propagation and specialization happens by navigating the 
call graph edges and not the gimple code. I initially thought that 
gimple_call_set_fndecl was not called because I couldn't find anything 
except call graph edges. However, this was not the case.

Also, by the time I am inspecting the indirect edge, it has already been 
specialized and it is not speculative. So, it has been changed to a 
direct function.

> 
>>>
>>> I don't think that using storing the for loop and checking in
>>> saw_fn_in_loop_before is the way to go because I believe cnode->decl
>>> pointers can be different. Is this correct? In other words
>>>
>>>
>>> FOR_EACH_FUNCTION_WITH_GIMPLE_BODY(cnode)
>>> {
>>>     a_std_set.insert(cnode->decl)
>>> }
>>>
>>> // later
>>>
>>> bool
>>> saw_fn_in_loop_before(cnode_graph *cn, tree fn)
>>> {
>>>     return a_std_set.find(fn) != a_std_set.end();
>>> }
>>>
>>> Should not work. Something I found interesting is that for the fndecl
>>> obtained through the callsite gimple_has_body_p returns false. Even
>>> though I saw a fndecl which corresponds to the same function in the
>>> FOR_EACH_FUNCTION_WITH_GIMPLE_BODY.
> 
> I can only guess but it seems that something has created specialized
> clone for all contexts and you happen to be looking at the gimple bodies
> after clone materialization but before edge redirection... but I am only
> guessing.

I think this is close to the truth. So... the function I am interested 
in finding has indeed been specialized for "all contexts", but I think 
this "all contexts" did not take into account the function pointers.

During ipa-cp I looked at the code where the function call has been 
changed from an indirect call to a direct call and I was able to find 
that callee->has_gimple_body_p() returns true and that it was also found 
in FOR_EACH_FUNCTION_WITH_GIMPLE_BODY loop.

I also did this during ipa-sra and the same was true.

But for the pass just after materialization it was false.

As mentioned in a sibling post, I found out that during the inlining 
phase, the function is "reclaimed". I'm not 100% sure yet, but I think 
the indirect functions are not inlined. I used -fdump-ipa-inline and in 
my pass (after materialization) I used cnode->dump_name() to find its 
dump name... I then looked for its dump_name and only found the following:

IPA function summary for $dump_name inlinable

$dump_name ($name) @0x40002a1936d8
   Type: function
   Body removed by symtab_remove_unreachable_nodes
   Visibility: prevailing_def_ironly
   Address is taken.
   References:
   Referring:
   Read from file: $file
   Availability: not_available
   Profile id: 1677473182
   Unit id: 2
   Function flags: count:17343 (adjusted) first_run:3 hot
   Called by:
   Calls:

These are the only two mentions of the function by its dump_name

> 
>>
>> Actually, another interesting hint is that the original foo function
>> takes two parameters. The function I am seeing inside the
>> FOR_EACH_FUNCTION_WITH_GIMPLE_BODY is a specialized function of foo with
>> only 1 parameter. However, the indirect function call is a version of
>> foo which has not been specialized (i.e. it takes the original two
>> parameters).
>>
>> I guess my questions would be:
>>
>> * Does FOR_EACH_FUNCTION_WITH_GIMPLE_BODY only iterates across functions
>> which are reachable for main across the call graph?
> 
> No.  But functions which are known not to be reachable are of course
> removed from the call graph and from the entire compilation too.
> 
>> * Is the the underlying mechanism for FOR_EACH_FUNCTION_WITH_GIMPLE_BODY
>> not updated after ipa-prop discovers targets of indirect functions?
> 
> I am not sure I understand the question but I guess that no.  The
> mechanism is simple, just look it up.
> 
>> * Or is it just that the new callsite does not have a gimple body for
>> its function? (This seems implausible since the new direct callsite
>> should refer to the original function implementation.) How can I access
>> this function's body?
>>
> 
> In the IPA scheme of things, call-sites are updated last, even when the
> function where they are keeps its body from the start until the end.  It
> seems to me that you really want to be looking at the call graph edges
> instead.
> 
> Generally speaking, at IPA time you want to work only with the call
> graph and the data you collected in summary building stage and look at
> actual function bodies only as the last resort.

Thanks Martin, this is all very useful information. I'll see if I can 
refactor my code to use the call graph and not the gimple code. However, 
I still think that there might be an interesting behaviour on reclaiming 
a cgraph_node that is reachable through function pointers...

> 
> Martin
> 


More information about the Gcc mailing list