blob: f72339bf83ef645dc2a610e4933a66e2a94b26fa [file]
/* Callback attribute summary
Copyright (C) 2026 Free Software Foundation, Inc.
Contributed by Josef Melcr <jmelcr@gcc.gnu.org>
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "tree.h"
#include "gimple.h"
#include "alloc-pool.h"
#include "cgraph.h"
#include "diagnostic.h"
#include "builtins.h"
#include "options.h"
#include "gimple-range.h"
#include "attribs.h"
#include "symbol-summary.h"
#include "data-streamer.h"
#include "attr-callback.h"
#include "callback-info.h"
callback_info_sum_t *callback_info_sum = NULL;
/* Calculates the CI->INVERTED_MAPPING field from CI->ARG_MAPPING. */
static void
invert_arg_mapping (cgraph_edge *e, callback_info *ci)
{
auto &mapping = ci->arg_mapping;
auto &inverted_mapping = ci->inverted_mapping;
unsigned num = gimple_call_num_args (e->call_stmt);
inverted_mapping.reserve (num);
for (unsigned i = 0; i < num; i++)
inverted_mapping.quick_push (ARG_MAPPING_UNKNOWN_IDX);
for (unsigned i = 0; i < mapping.length (); i++)
if (mapping[i] != ARG_MAPPING_UNKNOWN_IDX)
inverted_mapping[mapping[i]] = i;
}
/* Streams a vector into an output block, in format N ELEM_1 ... ELEM_N*/
static void
stream_vector_out (lto_simple_output_block *ob, const auto_vec<int> &v)
{
streamer_write_uhwi_stream (ob->main_stream, v.length ());
for (int elem : v)
streamer_write_hwi_stream (ob->main_stream, elem);
}
/* Reads a vector from an LTO input block. The format is supposed to be the
same as stream_vector_out. */
static void
stream_vector_in (lto_input_block *ib, auto_vec<int> &v)
{
unsigned length = streamer_read_uhwi (ib);
v.reserve (length);
for (unsigned i = 0; i < length; i++)
{
int elem = streamer_read_hwi (ib);
v.safe_push (elem);
}
}
/* Stream out callback_info. */
void
callback_info::stream_out (lto_simple_output_block *ob) const
{
streamer_write_uhwi_stream (ob->main_stream, fn_idx);
stream_vector_out (ob, arg_mapping);
stream_vector_out (ob, inverted_mapping);
bitpack_d bp = bitpack_create (ob->main_stream);
bp_pack_value (&bp, redirected, 1);
streamer_write_bitpack (&bp);
}
/* Stream in callback_info. */
void
callback_info::stream_in (lto_input_block *ib)
{
fn_idx = streamer_read_uhwi (ib);
stream_vector_in (ib, arg_mapping);
stream_vector_in (ib, inverted_mapping);
bitpack_d bp = streamer_read_bitpack (ib);
redirected = bp_unpack_value (&bp, 1);
}
/* Returns the id of the associated callback attribute. See the comment of the
fn_idx field. */
unsigned
callback_info::get_id () const
{
return fn_idx;
}
void
callback_info::init (cgraph_edge *e, unsigned fn_idx, tree attr)
{
this->fn_idx = fn_idx;
arg_mapping = callback_get_arg_mapping_from_attr (attr);
invert_arg_mapping (e, this);
redirected = false;
}
/* Initialize the summary for a callback call CBE of internal function IFN. */
callback_info *
callback_info::create_internal_summary (cgraph_edge *cbe, internal_fn ifn)
{
callback_info_sum_t::check_create_info_sum ();
callback_info *ci = callback_info_sum->get_create (cbe);
switch (ifn)
{
case IFN_ASSUME:
{
ci->fn_idx = 0;
unsigned n = gimple_call_num_args (cbe->call_stmt);
for (unsigned i = 0; i < n - 1; i++)
ci->arg_mapping.safe_push (i + 1);
invert_arg_mapping (cbe, ci);
break;
}
default:
gcc_unreachable ();
}
ci->redirected = false;
return ci;
}
/* Populates the callback_info_sum if it's NULL. */
void
callback_info_sum_t::check_create_info_sum (void)
{
if (!callback_info_sum)
callback_info_sum = new callback_info_sum_t (symtab, false);
}
/* Frees the callback_info_sum pointer. */
void
callback_info_sum_t::free_info_sum (void)
{
if (callback_info_sum)
delete callback_info_sum;
callback_info_sum = NULL;
}
/* Duplication function for the cgraph_edge duplication hook. */
void
callback_info_sum_t::duplicate (cgraph_edge *, cgraph_edge *,
callback_info *src_s, callback_info *dst_s)
{
dst_s->fn_idx = src_s->fn_idx;
/* Might need to be adjusted in the future, if argument modifications are
implemented. */
dst_s->arg_mapping = src_s->arg_mapping.copy ();
dst_s->inverted_mapping = src_s->inverted_mapping.copy ();
dst_s->redirected = src_s->redirected;
}
/* Return the argument at IDX if CI is null, otherwise use the mapping in CI and
return the remapped argument. Presumes that the remapped index is not
ARG_MAPPING_UNKNOWN_IDX. */
tree
callback_call_arg_summary (const gimple *stmt, int idx, callback_info *ci)
{
if (ci)
idx = ci->arg_mapping[idx];
gcc_checking_assert (idx != ARG_MAPPING_UNKNOWN_IDX);
return gimple_call_arg (stmt, idx);
}
/* Return the number of arguments of STMT if CI is null,
ci->arg_mapping.length() otherwise. */
unsigned
callback_call_num_args_summary (const gcall *stmt, callback_info *ci)
{
if (ci)
return ci->arg_mapping.length ();
return gimple_call_num_args (stmt);
}
/* Returns TRUE iff STMT is an internal call statement to IFN_ASSUME. */
bool
callback_ifn_assume_call_p (const gimple *stmt)
{
return is_gimple_call (stmt) && gimple_call_internal_p (stmt)
&& (gimple_call_internal_fn (stmt) == IFN_ASSUME);
}
/* Returns TRUE iff STMT is an internal call statement to IFN_ASSUME with a
callback edge pointing to the outlined body. */
bool
callback_ifn_assume_call_with_edge_p (cgraph_node *caller, gimple *stmt)
{
/* Fast path for most cases. */
if (!callback_ifn_assume_call_p (stmt))
return false;
cgraph_edge *cbei = caller->get_edge (stmt);
return cbei != nullptr;
}