/* Manipulation of formal and actual parameters of functions and function calls. Copyright (C) 2017-2023 Free Software Foundation, Inc. This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, 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 . This file defines classes and other data structures that are used to manipulate the prototype of a function, especially to create, remove or split its formal parameters, but also to remove its return value, and also its call statements correspondingly. The most basic one is a vector of structures ipa_adjusted_param. It is simply a description how the new parameters should look like after the transformation in what way they relate to the previous ones (if in any). Such relation to an old parameter can be an outright copy or an IPA-SRA replacement. If an old parameter is not listed or otherwise mentioned, it is removed as unused or at least unnecessary. Note that this most basic structure does not work for modifying calls of functions with variable number of arguments. Class ipa_param_adjustments is only a little more than a thin encapsulation of a vector of ipa_param_adjustments. Along with this vector it contains an index of the first potential vararg argument and a boolean flag whether the return value should be removed or not. Moreover, the class contains method modify_call which can transform a call statement so that it correctly calls a modified function. These two data structures were designed to have a small memory footprint because they are allocated for each clone of a call graph node that has its prototype changed and live until the end of IPA clone materialization and call redirection phase. On the other hand, class ipa_param_body_adjustments can afford to allocate more data because its life span is much smaller, it is allocated and destroyed in the course of materialization of each single clone that needs it or only when a particular pass needs to change a function it is operating on. This class has various methods required to change function declaration and the body of the function according to instructions given either by class ipa_param_adjustments or only a vector of ipa_adjusted_params. When these classes are used in the context of call graph clone materialization and subsequent call statement redirection - which is the point at which we modify arguments in call statements - they need to cooperate with each other in order to handle what we refer to as pass-through (IPA-SRA) splits. These are situations when a formal parameter of one function is split into several smaller ones and some of them are then passed on in a call to another function because the formal parameter of this callee has also been split. Consider a simple example: struct S {int a, b, c;}; struct Z {int x; S s;}; foo (S s) { use (s.b); } bar (Z z) { use (z.s.a); foo (z.s); } baz () { bar (*global); } Both bar and foo would have their parameter split. Foo would receive one replacement representing s.b. Function bar would see its parameter split into one replacement representing z.s.a and another representing z.s.b which would be passed on to foo. It would be a so called pass-through split IPA-SRA replacement, one which is passed in a call as an actual argument to another IPA-SRA replacement in another function. Note that the call chain the example can be arbitrarily long and recursive and that any function in it can be cloned by another IPA pass and any number of adjacent functions in the call chain can be inlined into each other. Call redirection takes place only after bodies of the function have been modified by all of the above. Call redirection has to be able to find the right decl or SSA_NAME that corresponds to the transitive split in the caller. The SSA names are assigned right after clone materialization/ modification and cannot be "added" to call arguments at any later point. Moreover, if the caller has been inlined the SSA_NAMEs in question no longer belong to PARM_DECLs but to VAR_DECLs, indistinguishable from any others. Therefore, when clone materialization finds a call statement which it knows is a part of a transitive split, it will simply add as arguments all new "split" replacements (that have grater or equal offset than the original call argument): foo (repl_for_a, repl_for_b, ); It will also store into ipa_edge_modification_info (which is internal to ipa-param-modification.c) information about which replacement is which and where original arguments are. Call redirection will then invoke ipa_param_adjustments::modify_call which will access this information and eliminate all replacements which the callee does not expect (repl_for_a in our example above). In between these two steps, however, a call statement might have extraneous arguments. */ #ifndef IPA_PARAM_MANIPULATION_H #define IPA_PARAM_MANIPULATION_H /* Indices into ipa_param_prefixes to identify a human-readable prefix for newly synthesized parameters. Keep in sync with the array. */ enum ipa_param_name_prefix_indices { IPA_PARAM_PREFIX_SYNTH, IPA_PARAM_PREFIX_ISRA, IPA_PARAM_PREFIX_SIMD, IPA_PARAM_PREFIX_MASK, IPA_PARAM_PREFIX_COUNT }; /* We do not support manipulating functions with more than 1< *adj_params); /* Class used to record planned modifications to parameters of a function and also to perform necessary modifications at the caller side at the gimple level. Used to describe all cgraph node clones that have their parameters changed, therefore the class should only have a small memory footprint. */ class GTY(()) ipa_param_adjustments { public: /* Constructor from NEW_PARAMS showing how new parameters should look like plus copying any pre-existing actual arguments starting from argument with index ALWAYS_COPY_START (if non-negative, negative means do not copy anything beyond what is described in NEW_PARAMS), and SKIP_RETURN, which indicates that the function should return void after transformation. */ ipa_param_adjustments (vec *new_params, int always_copy_start, bool skip_return) : m_adj_params (new_params), m_always_copy_start (always_copy_start), m_skip_return (skip_return) {} /* Modify a call statement arguments (and possibly remove the return value) as described in the data fields of this class. */ gcall *modify_call (cgraph_edge *cs, bool update_references); /* Return if the first parameter is left intact. */ bool first_param_intact_p (); /* Build a function type corresponding to the modified call. */ tree build_new_function_type (tree old_type, bool type_is_original_p); /* Build a declaration corresponding to the target of the modified call. */ tree adjust_decl (tree orig_decl); /* Fill a vector marking which parameters are intact by the described modifications. */ void get_surviving_params (vec *surviving_params); /* Fill a vector with new indices of surviving original parameters. */ void get_updated_indices (vec *new_indices); /* Return the original index for the given new parameter index. Return a negative number if not available. */ int get_original_index (int newidx); void dump (FILE *f); void debug (); /* How the known part of arguments should look like. */ vec *m_adj_params; /* If non-negative, copy any arguments starting at this offset without any modifications so that functions with variable number of arguments can be modified. This number should be equal to the number of original forma parameters. */ int m_always_copy_start; /* If true, make the function not return any value. */ bool m_skip_return; static bool type_attribute_allowed_p (tree); private: ipa_param_adjustments () {} void init (vec *cur_params); int get_max_base_index (); bool method2func_p (tree orig_type); }; /* Structure used to map expressions accessing split or replaced parameters to new PARM_DECLs. */ struct ipa_param_body_replacement { /* The old decl of the original parameter. */ tree base; /* The new decl it should be replaced with. */ tree repl; /* Users of ipa_param_body_adjustments that modify standalone functions outside of IPA clone materialization can use the following field for their internal purposes. */ tree dummy; /* The offset within BASE that REPL represents. */ unsigned unit_offset; }; struct ipa_replace_map; /* Class used when actually performing adjustments to formal parameters of a function to map accesses that need to be replaced to replacements. The class attempts to work in two very different sets of circumstances: as a part of tree-inine.c's tree_function_versioning machinery to clone functions (when M_ID is not NULL) and in s standalone fashion, modifying an existing function in place (when M_ID is NULL). While a lot of stuff handled in a unified way in both modes, there are many aspects of the processs that requires distinct paths. */ class ipa_param_body_adjustments { public: /* Constructor to use from within tree-inline. */ ipa_param_body_adjustments (ipa_param_adjustments *adjustments, tree fndecl, tree old_fndecl, struct copy_body_data *id, tree *vars, vec *tree_map); /* Constructor to use for modifying a function outside of tree-inline from an instance of ipa_param_adjustments. */ ipa_param_body_adjustments (ipa_param_adjustments *adjustments, tree fndecl); /* Constructor to use for modifying a function outside of tree-inline from a simple vector of desired parameter modification. */ ipa_param_body_adjustments (vec *adj_params, tree fndecl); /* The do-it-all function for modifying a function outside of tree-inline. */ bool perform_cfun_body_modifications (); /* Change the PARM_DECLs. */ void modify_formal_parameters (); /* Register a REPLACEMENT for accesses to BASE at UNIT_OFFSET. */ void register_replacement (tree base, unsigned unit_offset, tree replacement); /* Register a replacement decl for the transformation done in APM. */ void register_replacement (ipa_adjusted_param *apm, tree replacement); /* Sort m_replacements and set m_sorted_replacements_p to true. Users that call register_replacement themselves must call the method before any lookup and thus also any statement or expression modification. */ void sort_replacements (); /* Lookup a replacement for a given offset within a given parameter. */ tree lookup_replacement (tree base, unsigned unit_offset); /* Lookup a replacement for an expression, if there is one. */ ipa_param_body_replacement *get_expr_replacement (tree expr, bool ignore_default_def); /* Lookup the new base for surviving names previously belonging to a parameter. */ tree get_replacement_ssa_base (tree old_decl); /* Modify a statement. */ bool modify_gimple_stmt (gimple **stmt, gimple_seq *extra_stmts, gimple *orig_stmt); /* Return the new chain of parameters. */ tree get_new_param_chain (); /* Replace all occurances of SSAs in m_dead_ssa_debug_equiv in t with what they are mapped to. */ void remap_with_debug_expressions (tree *t); /* If there are any initialization statements that need to be emitted into the basic block BB right at ther start of the new function, do so. */ void append_init_stmts (basic_block bb); /* Pointers to data structures defining how the function should be modified. */ vec *m_adj_params; ipa_param_adjustments *m_adjustments; /* Vector of old parameter declarations that must have their debug bind statements re-mapped and debug decls created. */ auto_vec m_reset_debug_decls; /* Sets of statements and SSA_NAMEs that only manipulate data from parameters removed because they are not necessary. */ hash_set m_dead_stmts; hash_set m_dead_ssas; /* Mapping from DCEd SSAs to what their potential debug_binds should be. */ hash_map m_dead_ssa_debug_equiv; /* Mapping from DCEd statements to debug expressions that will be placed on the RHS of debug statement that will replace this one. */ hash_map m_dead_stmt_debug_equiv; private: void common_initialization (tree old_fndecl, tree *vars, vec *tree_map); tree carry_over_param (tree t); unsigned get_base_index (ipa_adjusted_param *apm); ipa_param_body_replacement *lookup_replacement_1 (tree base, unsigned unit_offset); ipa_param_body_replacement *lookup_first_base_replacement (tree base); tree replace_removed_params_ssa_names (tree old_name, gimple *stmt); bool modify_expression (tree *expr_p, bool convert, gimple_seq * = nullptr); bool modify_assignment (gimple *stmt, gimple_seq *extra_stmts); bool modify_call_stmt (gcall **stmt_p, gimple *orig_stmt); bool modify_cfun_body (); void reset_debug_stmts (); tree get_ddef_if_exists_and_is_used (tree decl); void mark_dead_statements (tree dead_param, vec *debugstack); void mark_clobbers_dead (tree dead_param); bool prepare_debug_expressions (tree dead_ssa); /* Declaration of the function that is being transformed. */ tree m_fndecl; /* If non-NULL, the tree-inline master data structure guiding materialization of the current clone. */ struct copy_body_data *m_id; /* Vector of old parameter declarations (before changing them). */ auto_vec m_oparms; /* Vector of parameter declarations the function will have after transformation. */ auto_vec m_new_decls; /* If the function type has non-NULL TYPE_ARG_TYPES, this is the vector of these types after transformation, otherwise an empty one. */ auto_vec m_new_types; /* Vector of structures telling how to replace old parameters in the function body. TODO: Even though there usually be only few, but should we use a hash? */ auto_vec m_replacements; /* List of initialization assignments to be put at the beginning of the cloned function to deal with split aggregates which however have known constant value and so their PARM_DECL disappears. */ auto_vec m_split_agg_csts_inits; /* Vector for remapping SSA_BASES from old parameter declarations that are being removed as a part of the transformation. Before a new VAR_DECL is created, it holds the old PARM_DECL, once the variable is built it is stored here. */ auto_vec m_removed_decls; /* Hash to quickly lookup the item in m_removed_decls given the old decl. */ hash_map m_removed_map; /* True iff the transformed function is a class method that is about to loose its this pointer and must be converted to a normal function. */ bool m_method2func; /* True if m_replacements have ben sorted since the last insertion. */ bool m_sorted_replacements_p; }; void push_function_arg_decls (vec *args, tree fndecl); void push_function_arg_types (vec *types, tree fntype); void ipa_verify_edge_has_no_modifications (cgraph_edge *cs); void ipa_edge_modifications_finalize (); #endif /* IPA_PARAM_MANIPULATION_H */