From 554fd8c5195424bdbcabf5de30fdc183aba391bd Mon Sep 17 00:00:00 2001 From: upstream source tree Date: Sun, 15 Mar 2015 20:14:05 -0400 Subject: obtained gcc-4.6.4.tar.bz2 from upstream website; verified gcc-4.6.4.tar.bz2.sig; imported gcc-4.6.4 source tree from verified upstream tarball. downloading a git-generated archive based on the 'upstream' tag should provide you with a source tree that is binary identical to the one extracted from the above tarball. if you have obtained the source via the command 'git clone', however, do note that line-endings of files in your working directory might differ from line-endings of the respective files in the upstream repository. --- gcc/ipa-pure-const.c | 1695 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1695 insertions(+) create mode 100644 gcc/ipa-pure-const.c (limited to 'gcc/ipa-pure-const.c') diff --git a/gcc/ipa-pure-const.c b/gcc/ipa-pure-const.c new file mode 100644 index 000000000..b9476e643 --- /dev/null +++ b/gcc/ipa-pure-const.c @@ -0,0 +1,1695 @@ +/* Callgraph based analysis of static variables. + Copyright (C) 2004, 2005, 2007, 2008, 2009, 2010 + Free Software Foundation, Inc. + Contributed by Kenneth Zadeck + +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 marks functions as being either const (TREE_READONLY) or + pure (DECL_PURE_P). It can also set a variant of these that + are allowed to loop indefinitely (DECL_LOOPING_CONST_PURE_P). + + This must be run after inlining decisions have been made since + otherwise, the local sets will not contain information that is + consistent with post inlined state. The global sets are not prone + to this problem since they are by definition transitive. */ + +/* The code in this module is called by the ipa pass manager. It + should be one of the later passes since it's information is used by + the rest of the compilation. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "tree.h" +#include "tree-flow.h" +#include "tree-inline.h" +#include "tree-pass.h" +#include "langhooks.h" +#include "pointer-set.h" +#include "ggc.h" +#include "ipa-utils.h" +#include "gimple.h" +#include "cgraph.h" +#include "output.h" +#include "flags.h" +#include "timevar.h" +#include "diagnostic.h" +#include "gimple-pretty-print.h" +#include "langhooks.h" +#include "target.h" +#include "lto-streamer.h" +#include "cfgloop.h" +#include "tree-scalar-evolution.h" +#include "intl.h" +#include "opts.h" + +static struct pointer_set_t *visited_nodes; + +/* Lattice values for const and pure functions. Everything starts out + being const, then may drop to pure and then neither depending on + what is found. */ +enum pure_const_state_e +{ + IPA_CONST, + IPA_PURE, + IPA_NEITHER +}; + +const char *pure_const_names[3] = {"const", "pure", "neither"}; + +/* Holder for the const_state. There is one of these per function + decl. */ +struct funct_state_d +{ + /* See above. */ + enum pure_const_state_e pure_const_state; + /* What user set here; we can be always sure about this. */ + enum pure_const_state_e state_previously_known; + bool looping_previously_known; + + /* True if the function could possibly infinite loop. There are a + lot of ways that this could be determined. We are pretty + conservative here. While it is possible to cse pure and const + calls, it is not legal to have dce get rid of the call if there + is a possibility that the call could infinite loop since this is + a behavioral change. */ + bool looping; + + bool can_throw; +}; + +/* State used when we know nothing about function. */ +static struct funct_state_d varying_state + = { IPA_NEITHER, IPA_NEITHER, true, true, true }; + + +typedef struct funct_state_d * funct_state; + +/* The storage of the funct_state is abstracted because there is the + possibility that it may be desirable to move this to the cgraph + local info. */ + +/* Array, indexed by cgraph node uid, of function states. */ + +DEF_VEC_P (funct_state); +DEF_VEC_ALLOC_P (funct_state, heap); +static VEC (funct_state, heap) *funct_state_vec; + +/* Holders of ipa cgraph hooks: */ +static struct cgraph_node_hook_list *function_insertion_hook_holder; +static struct cgraph_2node_hook_list *node_duplication_hook_holder; +static struct cgraph_node_hook_list *node_removal_hook_holder; + +/* Try to guess if function body will always be visible to compiler + when compiling the call and whether compiler will be able + to propagate the information by itself. */ + +static bool +function_always_visible_to_compiler_p (tree decl) +{ + return (!TREE_PUBLIC (decl) || DECL_DECLARED_INLINE_P (decl)); +} + +/* Emit suggestion about attribute ATTRIB_NAME for DECL. KNOWN_FINITE + is true if the function is known to be finite. The diagnostic is + controlled by OPTION. WARNED_ABOUT is a pointer_set unique for + OPTION, this function may initialize it and it is always returned + by the function. */ + +static struct pointer_set_t * +suggest_attribute (int option, tree decl, bool known_finite, + struct pointer_set_t *warned_about, + const char * attrib_name) +{ + if (!option_enabled (option, &global_options)) + return warned_about; + if (TREE_THIS_VOLATILE (decl) + || (known_finite && function_always_visible_to_compiler_p (decl))) + return warned_about; + + if (!warned_about) + warned_about = pointer_set_create (); + if (pointer_set_contains (warned_about, decl)) + return warned_about; + pointer_set_insert (warned_about, decl); + warning_at (DECL_SOURCE_LOCATION (decl), + option, + known_finite + ? _("function might be candidate for attribute %<%s%>") + : _("function might be candidate for attribute %<%s%>" + " if it is known to return normally"), attrib_name); + return warned_about; +} + +/* Emit suggestion about __attribute_((pure)) for DECL. KNOWN_FINITE + is true if the function is known to be finite. */ + +static void +warn_function_pure (tree decl, bool known_finite) +{ + static struct pointer_set_t *warned_about; + + warned_about + = suggest_attribute (OPT_Wsuggest_attribute_pure, decl, + known_finite, warned_about, "pure"); +} + +/* Emit suggestion about __attribute_((const)) for DECL. KNOWN_FINITE + is true if the function is known to be finite. */ + +static void +warn_function_const (tree decl, bool known_finite) +{ + static struct pointer_set_t *warned_about; + warned_about + = suggest_attribute (OPT_Wsuggest_attribute_const, decl, + known_finite, warned_about, "const"); +} + +void +warn_function_noreturn (tree decl) +{ + static struct pointer_set_t *warned_about; + if (!lang_hooks.missing_noreturn_ok_p (decl)) + warned_about + = suggest_attribute (OPT_Wsuggest_attribute_noreturn, decl, + true, warned_about, "noreturn"); +} +/* Init the function state. */ + +static void +finish_state (void) +{ + free (funct_state_vec); +} + + +/* Return true if we have a function state for NODE. */ + +static inline bool +has_function_state (struct cgraph_node *node) +{ + if (!funct_state_vec + || VEC_length (funct_state, funct_state_vec) <= (unsigned int)node->uid) + return false; + return VEC_index (funct_state, funct_state_vec, node->uid) != NULL; +} + +/* Return the function state from NODE. */ + +static inline funct_state +get_function_state (struct cgraph_node *node) +{ + if (!funct_state_vec + || VEC_length (funct_state, funct_state_vec) <= (unsigned int)node->uid + || !VEC_index (funct_state, funct_state_vec, node->uid)) + /* We might want to put correct previously_known state into varying. */ + return &varying_state; + return VEC_index (funct_state, funct_state_vec, node->uid); +} + +/* Set the function state S for NODE. */ + +static inline void +set_function_state (struct cgraph_node *node, funct_state s) +{ + if (!funct_state_vec + || VEC_length (funct_state, funct_state_vec) <= (unsigned int)node->uid) + VEC_safe_grow_cleared (funct_state, heap, funct_state_vec, node->uid + 1); + VEC_replace (funct_state, funct_state_vec, node->uid, s); +} + +/* Check to see if the use (or definition when CHECKING_WRITE is true) + variable T is legal in a function that is either pure or const. */ + +static inline void +check_decl (funct_state local, + tree t, bool checking_write, bool ipa) +{ + /* Do not want to do anything with volatile except mark any + function that uses one to be not const or pure. */ + if (TREE_THIS_VOLATILE (t)) + { + local->pure_const_state = IPA_NEITHER; + if (dump_file) + fprintf (dump_file, " Volatile operand is not const/pure"); + return; + } + + /* Do not care about a local automatic that is not static. */ + if (!TREE_STATIC (t) && !DECL_EXTERNAL (t)) + return; + + /* If the variable has the "used" attribute, treat it as if it had a + been touched by the devil. */ + if (DECL_PRESERVE_P (t)) + { + local->pure_const_state = IPA_NEITHER; + if (dump_file) + fprintf (dump_file, " Used static/global variable is not const/pure\n"); + return; + } + + /* In IPA mode we are not interested in checking actual loads and stores; + they will be processed at propagation time using ipa_ref. */ + if (ipa) + return; + + /* Since we have dealt with the locals and params cases above, if we + are CHECKING_WRITE, this cannot be a pure or constant + function. */ + if (checking_write) + { + local->pure_const_state = IPA_NEITHER; + if (dump_file) + fprintf (dump_file, " static/global memory write is not const/pure\n"); + return; + } + + if (DECL_EXTERNAL (t) || TREE_PUBLIC (t)) + { + /* Readonly reads are safe. */ + if (TREE_READONLY (t) && !TYPE_NEEDS_CONSTRUCTING (TREE_TYPE (t))) + return; /* Read of a constant, do not change the function state. */ + else + { + if (dump_file) + fprintf (dump_file, " global memory read is not const\n"); + /* Just a regular read. */ + if (local->pure_const_state == IPA_CONST) + local->pure_const_state = IPA_PURE; + } + } + else + { + /* Compilation level statics can be read if they are readonly + variables. */ + if (TREE_READONLY (t)) + return; + + if (dump_file) + fprintf (dump_file, " static memory read is not const\n"); + /* Just a regular read. */ + if (local->pure_const_state == IPA_CONST) + local->pure_const_state = IPA_PURE; + } +} + + +/* Check to see if the use (or definition when CHECKING_WRITE is true) + variable T is legal in a function that is either pure or const. */ + +static inline void +check_op (funct_state local, tree t, bool checking_write) +{ + t = get_base_address (t); + if (t && TREE_THIS_VOLATILE (t)) + { + local->pure_const_state = IPA_NEITHER; + if (dump_file) + fprintf (dump_file, " Volatile indirect ref is not const/pure\n"); + return; + } + else if (t + && (INDIRECT_REF_P (t) || TREE_CODE (t) == MEM_REF) + && TREE_CODE (TREE_OPERAND (t, 0)) == SSA_NAME + && !ptr_deref_may_alias_global_p (TREE_OPERAND (t, 0))) + { + if (dump_file) + fprintf (dump_file, " Indirect ref to local memory is OK\n"); + return; + } + else if (checking_write) + { + local->pure_const_state = IPA_NEITHER; + if (dump_file) + fprintf (dump_file, " Indirect ref write is not const/pure\n"); + return; + } + else + { + if (dump_file) + fprintf (dump_file, " Indirect ref read is not const\n"); + if (local->pure_const_state == IPA_CONST) + local->pure_const_state = IPA_PURE; + } +} + +/* compute state based on ECF FLAGS and store to STATE and LOOPING. */ + +static void +state_from_flags (enum pure_const_state_e *state, bool *looping, + int flags, bool cannot_lead_to_return) +{ + *looping = false; + if (flags & ECF_LOOPING_CONST_OR_PURE) + { + *looping = true; + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " looping"); + } + if (flags & ECF_CONST) + { + *state = IPA_CONST; + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " const\n"); + } + else if (flags & ECF_PURE) + { + *state = IPA_PURE; + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " pure\n"); + } + else if (cannot_lead_to_return) + { + *state = IPA_PURE; + *looping = true; + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " ignoring side effects->pure looping\n"); + } + else + { + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " neihter\n"); + *state = IPA_NEITHER; + *looping = true; + } +} + +/* Merge STATE and STATE2 and LOOPING and LOOPING2 and store + into STATE and LOOPING better of the two variants. + Be sure to merge looping correctly. IPA_NEITHER functions + have looping 0 even if they don't have to return. */ + +static inline void +better_state (enum pure_const_state_e *state, bool *looping, + enum pure_const_state_e state2, bool looping2) +{ + if (state2 < *state) + { + if (*state == IPA_NEITHER) + *looping = looping2; + else + *looping = MIN (*looping, looping2); + } + else if (state2 != IPA_NEITHER) + *looping = MIN (*looping, looping2); +} + +/* Merge STATE and STATE2 and LOOPING and LOOPING2 and store + into STATE and LOOPING worse of the two variants. */ + +static inline void +worse_state (enum pure_const_state_e *state, bool *looping, + enum pure_const_state_e state2, bool looping2) +{ + *state = MAX (*state, state2); + *looping = MAX (*looping, looping2); +} + +/* Recognize special cases of builtins that are by themselves not pure or const + but function using them is. */ +static bool +special_builtin_state (enum pure_const_state_e *state, bool *looping, + tree callee) +{ + if (DECL_BUILT_IN_CLASS (callee) == BUILT_IN_NORMAL) + switch (DECL_FUNCTION_CODE (callee)) + { + case BUILT_IN_RETURN: + case BUILT_IN_UNREACHABLE: + case BUILT_IN_ALLOCA: + case BUILT_IN_STACK_SAVE: + case BUILT_IN_STACK_RESTORE: + case BUILT_IN_EH_POINTER: + case BUILT_IN_EH_FILTER: + case BUILT_IN_UNWIND_RESUME: + case BUILT_IN_CXA_END_CLEANUP: + case BUILT_IN_EH_COPY_VALUES: + case BUILT_IN_FRAME_ADDRESS: + case BUILT_IN_APPLY: + case BUILT_IN_APPLY_ARGS: + *looping = false; + *state = IPA_CONST; + return true; + case BUILT_IN_PREFETCH: + *looping = true; + *state = IPA_CONST; + return true; + } + return false; +} + +/* Check the parameters of a function call to CALL_EXPR to see if + there are any references in the parameters that are not allowed for + pure or const functions. Also check to see if this is either an + indirect call, a call outside the compilation unit, or has special + attributes that may also effect the purity. The CALL_EXPR node for + the entire call expression. */ + +static void +check_call (funct_state local, gimple call, bool ipa) +{ + int flags = gimple_call_flags (call); + tree callee_t = gimple_call_fndecl (call); + bool possibly_throws = stmt_could_throw_p (call); + bool possibly_throws_externally = (possibly_throws + && stmt_can_throw_external (call)); + + if (possibly_throws) + { + unsigned int i; + for (i = 0; i < gimple_num_ops (call); i++) + if (gimple_op (call, i) + && tree_could_throw_p (gimple_op (call, i))) + { + if (possibly_throws && cfun->can_throw_non_call_exceptions) + { + if (dump_file) + fprintf (dump_file, " operand can throw; looping\n"); + local->looping = true; + } + if (possibly_throws_externally) + { + if (dump_file) + fprintf (dump_file, " operand can throw externally\n"); + local->can_throw = true; + } + } + } + + /* The const and pure flags are set by a variety of places in the + compiler (including here). If someone has already set the flags + for the callee, (such as for some of the builtins) we will use + them, otherwise we will compute our own information. + + Const and pure functions have less clobber effects than other + functions so we process these first. Otherwise if it is a call + outside the compilation unit or an indirect call we punt. This + leaves local calls which will be processed by following the call + graph. */ + if (callee_t) + { + enum pure_const_state_e call_state; + bool call_looping; + + if (special_builtin_state (&call_state, &call_looping, callee_t)) + { + worse_state (&local->pure_const_state, &local->looping, + call_state, call_looping); + return; + } + /* When bad things happen to bad functions, they cannot be const + or pure. */ + if (setjmp_call_p (callee_t)) + { + if (dump_file) + fprintf (dump_file, " setjmp is not const/pure\n"); + local->looping = true; + local->pure_const_state = IPA_NEITHER; + } + + if (DECL_BUILT_IN_CLASS (callee_t) == BUILT_IN_NORMAL) + switch (DECL_FUNCTION_CODE (callee_t)) + { + case BUILT_IN_LONGJMP: + case BUILT_IN_NONLOCAL_GOTO: + if (dump_file) + fprintf (dump_file, " longjmp and nonlocal goto is not const/pure\n"); + local->pure_const_state = IPA_NEITHER; + local->looping = true; + break; + default: + break; + } + } + + /* When not in IPA mode, we can still handle self recursion. */ + if (!ipa && callee_t == current_function_decl) + { + if (dump_file) + fprintf (dump_file, " Recursive call can loop.\n"); + local->looping = true; + } + /* Either callee is unknown or we are doing local analysis. + Look to see if there are any bits available for the callee (such as by + declaration or because it is builtin) and process solely on the basis of + those bits. */ + else if (!ipa) + { + enum pure_const_state_e call_state; + bool call_looping; + if (possibly_throws && cfun->can_throw_non_call_exceptions) + { + if (dump_file) + fprintf (dump_file, " can throw; looping\n"); + local->looping = true; + } + if (possibly_throws_externally) + { + if (dump_file) + { + fprintf (dump_file, " can throw externally to lp %i\n", + lookup_stmt_eh_lp (call)); + if (callee_t) + fprintf (dump_file, " callee:%s\n", + IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (callee_t))); + } + local->can_throw = true; + } + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " checking flags for call:"); + state_from_flags (&call_state, &call_looping, flags, + ((flags & (ECF_NORETURN | ECF_NOTHROW)) + == (ECF_NORETURN | ECF_NOTHROW)) + || (!flag_exceptions && (flags & ECF_NORETURN))); + worse_state (&local->pure_const_state, &local->looping, + call_state, call_looping); + } + /* Direct functions calls are handled by IPA propagation. */ +} + +/* Wrapper around check_decl for loads in local more. */ + +static bool +check_load (gimple stmt ATTRIBUTE_UNUSED, tree op, void *data) +{ + if (DECL_P (op)) + check_decl ((funct_state)data, op, false, false); + else + check_op ((funct_state)data, op, false); + return false; +} + +/* Wrapper around check_decl for stores in local more. */ + +static bool +check_store (gimple stmt ATTRIBUTE_UNUSED, tree op, void *data) +{ + if (DECL_P (op)) + check_decl ((funct_state)data, op, true, false); + else + check_op ((funct_state)data, op, true); + return false; +} + +/* Wrapper around check_decl for loads in ipa mode. */ + +static bool +check_ipa_load (gimple stmt ATTRIBUTE_UNUSED, tree op, void *data) +{ + if (DECL_P (op)) + check_decl ((funct_state)data, op, false, true); + else + check_op ((funct_state)data, op, false); + return false; +} + +/* Wrapper around check_decl for stores in ipa mode. */ + +static bool +check_ipa_store (gimple stmt ATTRIBUTE_UNUSED, tree op, void *data) +{ + if (DECL_P (op)) + check_decl ((funct_state)data, op, true, true); + else + check_op ((funct_state)data, op, true); + return false; +} + +/* Look into pointer pointed to by GSIP and figure out what interesting side + effects it has. */ +static void +check_stmt (gimple_stmt_iterator *gsip, funct_state local, bool ipa) +{ + gimple stmt = gsi_stmt (*gsip); + unsigned int i = 0; + + if (is_gimple_debug (stmt)) + return; + + if (dump_file) + { + fprintf (dump_file, " scanning: "); + print_gimple_stmt (dump_file, stmt, 0, 0); + } + + if (gimple_has_volatile_ops (stmt)) + { + local->pure_const_state = IPA_NEITHER; + if (dump_file) + fprintf (dump_file, " Volatile stmt is not const/pure\n"); + } + + /* Look for loads and stores. */ + walk_stmt_load_store_ops (stmt, local, + ipa ? check_ipa_load : check_load, + ipa ? check_ipa_store : check_store); + + if (gimple_code (stmt) != GIMPLE_CALL + && stmt_could_throw_p (stmt)) + { + if (cfun->can_throw_non_call_exceptions) + { + if (dump_file) + fprintf (dump_file, " can throw; looping"); + local->looping = true; + } + if (stmt_can_throw_external (stmt)) + { + if (dump_file) + fprintf (dump_file, " can throw externally"); + local->can_throw = true; + } + } + switch (gimple_code (stmt)) + { + case GIMPLE_CALL: + check_call (local, stmt, ipa); + break; + case GIMPLE_LABEL: + if (DECL_NONLOCAL (gimple_label_label (stmt))) + /* Target of long jump. */ + { + if (dump_file) + fprintf (dump_file, " nonlocal label is not const/pure"); + local->pure_const_state = IPA_NEITHER; + } + break; + case GIMPLE_ASM: + for (i = 0; i < gimple_asm_nclobbers (stmt); i++) + { + tree op = gimple_asm_clobber_op (stmt, i); + if (strcmp (TREE_STRING_POINTER (TREE_VALUE (op)), "memory") == 0) + { + if (dump_file) + fprintf (dump_file, " memory asm clobber is not const/pure"); + /* Abandon all hope, ye who enter here. */ + local->pure_const_state = IPA_NEITHER; + } + } + if (gimple_asm_volatile_p (stmt)) + { + if (dump_file) + fprintf (dump_file, " volatile is not const/pure"); + /* Abandon all hope, ye who enter here. */ + local->pure_const_state = IPA_NEITHER; + local->looping = true; + } + return; + default: + break; + } +} + + +/* This is the main routine for finding the reference patterns for + global variables within a function FN. */ + +static funct_state +analyze_function (struct cgraph_node *fn, bool ipa) +{ + tree decl = fn->decl; + tree old_decl = current_function_decl; + funct_state l; + basic_block this_block; + + l = XCNEW (struct funct_state_d); + l->pure_const_state = IPA_CONST; + l->state_previously_known = IPA_NEITHER; + l->looping_previously_known = true; + l->looping = false; + l->can_throw = false; + + if (dump_file) + { + fprintf (dump_file, "\n\n local analysis of %s\n ", + cgraph_node_name (fn)); + } + + push_cfun (DECL_STRUCT_FUNCTION (decl)); + current_function_decl = decl; + + FOR_EACH_BB (this_block) + { + gimple_stmt_iterator gsi; + struct walk_stmt_info wi; + + memset (&wi, 0, sizeof(wi)); + for (gsi = gsi_start_bb (this_block); + !gsi_end_p (gsi); + gsi_next (&gsi)) + { + check_stmt (&gsi, l, ipa); + if (l->pure_const_state == IPA_NEITHER && l->looping && l->can_throw) + goto end; + } + } + +end: + if (l->pure_const_state != IPA_NEITHER) + { + /* Const functions cannot have back edges (an + indication of possible infinite loop side + effect. */ + if (mark_dfs_back_edges ()) + { + /* Preheaders are needed for SCEV to work. + Simple latches and recorded exits improve chances that loop will + proved to be finite in testcases such as in loop-15.c and loop-24.c */ + loop_optimizer_init (LOOPS_NORMAL + | LOOPS_HAVE_RECORDED_EXITS); + if (dump_file && (dump_flags & TDF_DETAILS)) + flow_loops_dump (dump_file, NULL, 0); + if (mark_irreducible_loops ()) + { + if (dump_file) + fprintf (dump_file, " has irreducible loops\n"); + l->looping = true; + } + else + { + loop_iterator li; + struct loop *loop; + scev_initialize (); + FOR_EACH_LOOP (li, loop, 0) + if (!finite_loop_p (loop)) + { + if (dump_file) + fprintf (dump_file, " can not prove finiteness of loop %i\n", loop->num); + l->looping =true; + break; + } + scev_finalize (); + } + loop_optimizer_finalize (); + } + } + + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " checking previously known:"); + state_from_flags (&l->state_previously_known, &l->looping_previously_known, + flags_from_decl_or_type (fn->decl), + cgraph_node_cannot_return (fn)); + + better_state (&l->pure_const_state, &l->looping, + l->state_previously_known, + l->looping_previously_known); + if (TREE_NOTHROW (decl)) + l->can_throw = false; + + pop_cfun (); + current_function_decl = old_decl; + if (dump_file) + { + if (l->looping) + fprintf (dump_file, "Function is locally looping.\n"); + if (l->can_throw) + fprintf (dump_file, "Function is locally throwing.\n"); + if (l->pure_const_state == IPA_CONST) + fprintf (dump_file, "Function is locally const.\n"); + if (l->pure_const_state == IPA_PURE) + fprintf (dump_file, "Function is locally pure.\n"); + } + return l; +} + +/* Called when new function is inserted to callgraph late. */ +static void +add_new_function (struct cgraph_node *node, void *data ATTRIBUTE_UNUSED) +{ + if (cgraph_function_body_availability (node) < AVAIL_OVERWRITABLE) + return; + /* There are some shared nodes, in particular the initializers on + static declarations. We do not need to scan them more than once + since all we would be interested in are the addressof + operations. */ + visited_nodes = pointer_set_create (); + if (cgraph_function_body_availability (node) > AVAIL_OVERWRITABLE) + set_function_state (node, analyze_function (node, true)); + pointer_set_destroy (visited_nodes); + visited_nodes = NULL; +} + +/* Called when new clone is inserted to callgraph late. */ + +static void +duplicate_node_data (struct cgraph_node *src, struct cgraph_node *dst, + void *data ATTRIBUTE_UNUSED) +{ + if (has_function_state (src)) + { + funct_state l = XNEW (struct funct_state_d); + gcc_assert (!has_function_state (dst)); + memcpy (l, get_function_state (src), sizeof (*l)); + set_function_state (dst, l); + } +} + +/* Called when new clone is inserted to callgraph late. */ + +static void +remove_node_data (struct cgraph_node *node, void *data ATTRIBUTE_UNUSED) +{ + if (has_function_state (node)) + { + funct_state l = get_function_state (node); + if (l != &varying_state) + free (l); + set_function_state (node, NULL); + } +} + + +static void +register_hooks (void) +{ + static bool init_p = false; + + if (init_p) + return; + + init_p = true; + + node_removal_hook_holder = + cgraph_add_node_removal_hook (&remove_node_data, NULL); + node_duplication_hook_holder = + cgraph_add_node_duplication_hook (&duplicate_node_data, NULL); + function_insertion_hook_holder = + cgraph_add_function_insertion_hook (&add_new_function, NULL); +} + + +/* Analyze each function in the cgraph to see if it is locally PURE or + CONST. */ + +static void +generate_summary (void) +{ + struct cgraph_node *node; + + register_hooks (); + + /* There are some shared nodes, in particular the initializers on + static declarations. We do not need to scan them more than once + since all we would be interested in are the addressof + operations. */ + visited_nodes = pointer_set_create (); + + /* Process all of the functions. + + We process AVAIL_OVERWRITABLE functions. We can not use the results + by default, but the info can be used at LTO with -fwhole-program or + when function got cloned and the clone is AVAILABLE. */ + + for (node = cgraph_nodes; node; node = node->next) + if (cgraph_function_body_availability (node) >= AVAIL_OVERWRITABLE) + set_function_state (node, analyze_function (node, true)); + + pointer_set_destroy (visited_nodes); + visited_nodes = NULL; +} + + +/* Serialize the ipa info for lto. */ + +static void +pure_const_write_summary (cgraph_node_set set, + varpool_node_set vset ATTRIBUTE_UNUSED) +{ + struct cgraph_node *node; + struct lto_simple_output_block *ob + = lto_create_simple_output_block (LTO_section_ipa_pure_const); + unsigned int count = 0; + cgraph_node_set_iterator csi; + + for (csi = csi_start (set); !csi_end_p (csi); csi_next (&csi)) + { + node = csi_node (csi); + if (node->analyzed && has_function_state (node)) + count++; + } + + lto_output_uleb128_stream (ob->main_stream, count); + + /* Process all of the functions. */ + for (csi = csi_start (set); !csi_end_p (csi); csi_next (&csi)) + { + node = csi_node (csi); + if (node->analyzed && has_function_state (node)) + { + struct bitpack_d bp; + funct_state fs; + int node_ref; + lto_cgraph_encoder_t encoder; + + fs = get_function_state (node); + + encoder = ob->decl_state->cgraph_node_encoder; + node_ref = lto_cgraph_encoder_encode (encoder, node); + lto_output_uleb128_stream (ob->main_stream, node_ref); + + /* Note that flags will need to be read in the opposite + order as we are pushing the bitflags into FLAGS. */ + bp = bitpack_create (ob->main_stream); + bp_pack_value (&bp, fs->pure_const_state, 2); + bp_pack_value (&bp, fs->state_previously_known, 2); + bp_pack_value (&bp, fs->looping_previously_known, 1); + bp_pack_value (&bp, fs->looping, 1); + bp_pack_value (&bp, fs->can_throw, 1); + lto_output_bitpack (&bp); + } + } + + lto_destroy_simple_output_block (ob); +} + + +/* Deserialize the ipa info for lto. */ + +static void +pure_const_read_summary (void) +{ + struct lto_file_decl_data **file_data_vec = lto_get_file_decl_data (); + struct lto_file_decl_data *file_data; + unsigned int j = 0; + + register_hooks (); + while ((file_data = file_data_vec[j++])) + { + const char *data; + size_t len; + struct lto_input_block *ib + = lto_create_simple_input_block (file_data, + LTO_section_ipa_pure_const, + &data, &len); + if (ib) + { + unsigned int i; + unsigned int count = lto_input_uleb128 (ib); + + for (i = 0; i < count; i++) + { + unsigned int index; + struct cgraph_node *node; + struct bitpack_d bp; + funct_state fs; + lto_cgraph_encoder_t encoder; + + fs = XCNEW (struct funct_state_d); + index = lto_input_uleb128 (ib); + encoder = file_data->cgraph_node_encoder; + node = lto_cgraph_encoder_deref (encoder, index); + set_function_state (node, fs); + + /* Note that the flags must be read in the opposite + order in which they were written (the bitflags were + pushed into FLAGS). */ + bp = lto_input_bitpack (ib); + fs->pure_const_state + = (enum pure_const_state_e) bp_unpack_value (&bp, 2); + fs->state_previously_known + = (enum pure_const_state_e) bp_unpack_value (&bp, 2); + fs->looping_previously_known = bp_unpack_value (&bp, 1); + fs->looping = bp_unpack_value (&bp, 1); + fs->can_throw = bp_unpack_value (&bp, 1); + if (dump_file) + { + int flags = flags_from_decl_or_type (node->decl); + fprintf (dump_file, "Read info for %s/%i ", + cgraph_node_name (node), + node->uid); + if (flags & ECF_CONST) + fprintf (dump_file, " const"); + if (flags & ECF_PURE) + fprintf (dump_file, " pure"); + if (flags & ECF_NOTHROW) + fprintf (dump_file, " nothrow"); + fprintf (dump_file, "\n pure const state: %s\n", + pure_const_names[fs->pure_const_state]); + fprintf (dump_file, " previously known state: %s\n", + pure_const_names[fs->looping_previously_known]); + if (fs->looping) + fprintf (dump_file," function is locally looping\n"); + if (fs->looping_previously_known) + fprintf (dump_file," function is previously known looping\n"); + if (fs->can_throw) + fprintf (dump_file," function is locally throwing\n"); + } + } + + lto_destroy_simple_input_block (file_data, + LTO_section_ipa_pure_const, + ib, data, len); + } + } +} + + +static bool +ignore_edge (struct cgraph_edge *e) +{ + return (!e->can_throw_external); +} + +/* Return true if NODE is self recursive function. */ + +static bool +self_recursive_p (struct cgraph_node *node) +{ + struct cgraph_edge *e; + for (e = node->callees; e; e = e->next_callee) + if (e->callee == node) + return true; + return false; +} + +/* Produce transitive closure over the callgraph and compute pure/const + attributes. */ + +static void +propagate_pure_const (void) +{ + struct cgraph_node *node; + struct cgraph_node *w; + struct cgraph_node **order = + XCNEWVEC (struct cgraph_node *, cgraph_n_nodes); + int order_pos; + int i; + struct ipa_dfs_info * w_info; + + order_pos = ipa_utils_reduced_inorder (order, true, false, NULL); + if (dump_file) + { + dump_cgraph (dump_file); + ipa_utils_print_order(dump_file, "reduced", order, order_pos); + } + + /* Propagate the local information thru the call graph to produce + the global information. All the nodes within a cycle will have + the same info so we collapse cycles first. Then we can do the + propagation in one pass from the leaves to the roots. */ + for (i = 0; i < order_pos; i++ ) + { + enum pure_const_state_e pure_const_state = IPA_CONST; + bool looping = false; + int count = 0; + node = order[i]; + + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, "Starting cycle\n"); + + /* Find the worst state for any node in the cycle. */ + w = node; + while (w && pure_const_state != IPA_NEITHER) + { + struct cgraph_edge *e; + struct cgraph_edge *ie; + int i; + struct ipa_ref *ref; + + funct_state w_l = get_function_state (w); + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " Visiting %s/%i state:%s looping %i\n", + cgraph_node_name (w), + w->uid, + pure_const_names[w_l->pure_const_state], + w_l->looping); + + /* First merge in function body properties. */ + worse_state (&pure_const_state, &looping, + w_l->pure_const_state, w_l->looping); + if (pure_const_state == IPA_NEITHER) + break; + + /* For overwritable nodes we can not assume anything. */ + if (cgraph_function_body_availability (w) == AVAIL_OVERWRITABLE) + { + worse_state (&pure_const_state, &looping, + w_l->state_previously_known, + w_l->looping_previously_known); + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, + " Overwritable. state %s looping %i\n", + pure_const_names[w_l->state_previously_known], + w_l->looping_previously_known); + } + break; + } + + count++; + + /* We consider recursive cycles as possibly infinite. + This might be relaxed since infinite recursion leads to stack + overflow. */ + if (count > 1) + looping = true; + + /* Now walk the edges and merge in callee properties. */ + for (e = w->callees; e; e = e->next_callee) + { + struct cgraph_node *y = e->callee; + enum pure_const_state_e edge_state = IPA_CONST; + bool edge_looping = false; + + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, + " Call to %s/%i", + cgraph_node_name (e->callee), + e->callee->uid); + } + if (cgraph_function_body_availability (y) > AVAIL_OVERWRITABLE) + { + funct_state y_l = get_function_state (y); + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, + " state:%s looping:%i\n", + pure_const_names[y_l->pure_const_state], + y_l->looping); + } + if (y_l->pure_const_state > IPA_PURE + && cgraph_edge_cannot_lead_to_return (e)) + { + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, + " Ignoring side effects" + " -> pure, looping\n"); + edge_state = IPA_PURE; + edge_looping = true; + } + else + { + edge_state = y_l->pure_const_state; + edge_looping = y_l->looping; + } + } + else if (special_builtin_state (&edge_state, &edge_looping, + y->decl)) + ; + else + state_from_flags (&edge_state, &edge_looping, + flags_from_decl_or_type (y->decl), + cgraph_edge_cannot_lead_to_return (e)); + + /* Merge the results with what we already know. */ + better_state (&edge_state, &edge_looping, + w_l->state_previously_known, + w_l->looping_previously_known); + worse_state (&pure_const_state, &looping, + edge_state, edge_looping); + if (pure_const_state == IPA_NEITHER) + break; + } + if (pure_const_state == IPA_NEITHER) + break; + + /* Now process the indirect call. */ + for (ie = w->indirect_calls; ie; ie = ie->next_callee) + { + enum pure_const_state_e edge_state = IPA_CONST; + bool edge_looping = false; + + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " Indirect call"); + state_from_flags (&edge_state, &edge_looping, + ie->indirect_info->ecf_flags, + cgraph_edge_cannot_lead_to_return (ie)); + /* Merge the results with what we already know. */ + better_state (&edge_state, &edge_looping, + w_l->state_previously_known, + w_l->looping_previously_known); + worse_state (&pure_const_state, &looping, + edge_state, edge_looping); + if (pure_const_state == IPA_NEITHER) + break; + } + if (pure_const_state == IPA_NEITHER) + break; + + /* And finally all loads and stores. */ + for (i = 0; ipa_ref_list_reference_iterate (&w->ref_list, i, ref); i++) + { + enum pure_const_state_e ref_state = IPA_CONST; + bool ref_looping = false; + switch (ref->use) + { + case IPA_REF_LOAD: + /* readonly reads are safe. */ + if (TREE_READONLY (ipa_ref_varpool_node (ref)->decl)) + break; + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " nonreadonly global var read\n"); + ref_state = IPA_PURE; + break; + case IPA_REF_STORE: + if (ipa_ref_cannot_lead_to_return (ref)) + break; + ref_state = IPA_NEITHER; + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, " global var write\n"); + break; + case IPA_REF_ADDR: + break; + } + better_state (&ref_state, &ref_looping, + w_l->state_previously_known, + w_l->looping_previously_known); + worse_state (&pure_const_state, &looping, + ref_state, ref_looping); + if (pure_const_state == IPA_NEITHER) + break; + } + w_info = (struct ipa_dfs_info *) w->aux; + w = w_info->next_cycle; + } + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, "Result %s looping %i\n", + pure_const_names [pure_const_state], + looping); + + /* Copy back the region's pure_const_state which is shared by + all nodes in the region. */ + w = node; + while (w) + { + funct_state w_l = get_function_state (w); + enum pure_const_state_e this_state = pure_const_state; + bool this_looping = looping; + + if (w_l->state_previously_known != IPA_NEITHER + && this_state > w_l->state_previously_known) + { + this_state = w_l->state_previously_known; + this_looping |= w_l->looping_previously_known; + } + if (!this_looping && self_recursive_p (w)) + this_looping = true; + if (!w_l->looping_previously_known) + this_looping = false; + + /* All nodes within a cycle share the same info. */ + w_l->pure_const_state = this_state; + w_l->looping = this_looping; + + switch (this_state) + { + case IPA_CONST: + if (!TREE_READONLY (w->decl)) + { + warn_function_const (w->decl, !this_looping); + if (dump_file) + fprintf (dump_file, "Function found to be %sconst: %s\n", + this_looping ? "looping " : "", + cgraph_node_name (w)); + } + cgraph_set_const_flag (w, true, this_looping); + break; + + case IPA_PURE: + if (!DECL_PURE_P (w->decl)) + { + warn_function_pure (w->decl, !this_looping); + if (dump_file) + fprintf (dump_file, "Function found to be %spure: %s\n", + this_looping ? "looping " : "", + cgraph_node_name (w)); + } + cgraph_set_pure_flag (w, true, this_looping); + break; + + default: + break; + } + w_info = (struct ipa_dfs_info *) w->aux; + w = w_info->next_cycle; + } + } + + /* Cleanup. */ + for (node = cgraph_nodes; node; node = node->next) + { + /* Get rid of the aux information. */ + if (node->aux) + { + w_info = (struct ipa_dfs_info *) node->aux; + free (node->aux); + node->aux = NULL; + } + } + + free (order); +} + +/* Produce transitive closure over the callgraph and compute nothrow + attributes. */ + +static void +propagate_nothrow (void) +{ + struct cgraph_node *node; + struct cgraph_node *w; + struct cgraph_node **order = + XCNEWVEC (struct cgraph_node *, cgraph_n_nodes); + int order_pos; + int i; + struct ipa_dfs_info * w_info; + + order_pos = ipa_utils_reduced_inorder (order, true, false, ignore_edge); + if (dump_file) + { + dump_cgraph (dump_file); + ipa_utils_print_order(dump_file, "reduced for nothrow", order, order_pos); + } + + /* Propagate the local information thru the call graph to produce + the global information. All the nodes within a cycle will have + the same info so we collapse cycles first. Then we can do the + propagation in one pass from the leaves to the roots. */ + for (i = 0; i < order_pos; i++ ) + { + bool can_throw = false; + node = order[i]; + + /* Find the worst state for any node in the cycle. */ + w = node; + while (w) + { + struct cgraph_edge *e, *ie; + funct_state w_l = get_function_state (w); + + if (w_l->can_throw + || cgraph_function_body_availability (w) == AVAIL_OVERWRITABLE) + can_throw = true; + + if (can_throw) + break; + + for (e = w->callees; e; e = e->next_callee) + { + struct cgraph_node *y = e->callee; + + if (cgraph_function_body_availability (y) > AVAIL_OVERWRITABLE) + { + funct_state y_l = get_function_state (y); + + if (can_throw) + break; + if (y_l->can_throw && !TREE_NOTHROW (w->decl) + && e->can_throw_external) + can_throw = true; + } + else if (e->can_throw_external && !TREE_NOTHROW (y->decl)) + can_throw = true; + } + for (ie = node->indirect_calls; ie; ie = ie->next_callee) + if (ie->can_throw_external) + can_throw = true; + w_info = (struct ipa_dfs_info *) w->aux; + w = w_info->next_cycle; + } + + /* Copy back the region's pure_const_state which is shared by + all nodes in the region. */ + w = node; + while (w) + { + funct_state w_l = get_function_state (w); + if (!can_throw && !TREE_NOTHROW (w->decl)) + { + struct cgraph_edge *e; + cgraph_set_nothrow_flag (w, true); + for (e = w->callers; e; e = e->next_caller) + e->can_throw_external = false; + if (dump_file) + fprintf (dump_file, "Function found to be nothrow: %s\n", + cgraph_node_name (w)); + } + else if (can_throw && !TREE_NOTHROW (w->decl)) + w_l->can_throw = true; + w_info = (struct ipa_dfs_info *) w->aux; + w = w_info->next_cycle; + } + } + + /* Cleanup. */ + for (node = cgraph_nodes; node; node = node->next) + { + /* Get rid of the aux information. */ + if (node->aux) + { + w_info = (struct ipa_dfs_info *) node->aux; + free (node->aux); + node->aux = NULL; + } + } + + free (order); +} + + +/* Produce the global information by preforming a transitive closure + on the local information that was produced by generate_summary. */ + +static unsigned int +propagate (void) +{ + struct cgraph_node *node; + + cgraph_remove_function_insertion_hook (function_insertion_hook_holder); + cgraph_remove_node_duplication_hook (node_duplication_hook_holder); + cgraph_remove_node_removal_hook (node_removal_hook_holder); + + /* Nothrow makes more function to not lead to return and improve + later analysis. */ + propagate_nothrow (); + propagate_pure_const (); + + /* Cleanup. */ + for (node = cgraph_nodes; node; node = node->next) + if (has_function_state (node)) + free (get_function_state (node)); + VEC_free (funct_state, heap, funct_state_vec); + finish_state (); + return 0; +} + +static bool +gate_pure_const (void) +{ + return (flag_ipa_pure_const + /* Don't bother doing anything if the program has errors. */ + && !seen_error ()); +} + +struct ipa_opt_pass_d pass_ipa_pure_const = +{ + { + IPA_PASS, + "pure-const", /* name */ + gate_pure_const, /* gate */ + propagate, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_IPA_PURE_CONST, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0 /* todo_flags_finish */ + }, + generate_summary, /* generate_summary */ + pure_const_write_summary, /* write_summary */ + pure_const_read_summary, /* read_summary */ + NULL, /* write_optimization_summary */ + NULL, /* read_optimization_summary */ + NULL, /* stmt_fixup */ + 0, /* TODOs */ + NULL, /* function_transform */ + NULL /* variable_transform */ +}; + +/* Return true if function should be skipped for local pure const analysis. */ + +static bool +skip_function_for_local_pure_const (struct cgraph_node *node) +{ + /* Because we do not schedule pass_fixup_cfg over whole program after early optimizations + we must not promote functions that are called by already processed functions. */ + + if (function_called_by_processed_nodes_p ()) + { + if (dump_file) + fprintf (dump_file, "Function called in recursive cycle; ignoring\n"); + return true; + } + if (cgraph_function_body_availability (node) <= AVAIL_OVERWRITABLE) + { + if (dump_file) + fprintf (dump_file, "Function is not available or overwritable; not analyzing.\n"); + return true; + } + return false; +} + +/* Simple local pass for pure const discovery reusing the analysis from + ipa_pure_const. This pass is effective when executed together with + other optimization passes in early optimization pass queue. */ + +static unsigned int +local_pure_const (void) +{ + bool changed = false; + funct_state l; + bool skip; + struct cgraph_node *node; + + node = cgraph_node (current_function_decl); + skip = skip_function_for_local_pure_const (node); + if (!warn_suggest_attribute_const + && !warn_suggest_attribute_pure + && skip) + return 0; + + l = analyze_function (node, false); + + /* Do NORETURN discovery. */ + if (!skip && !TREE_THIS_VOLATILE (current_function_decl) + && EDGE_COUNT (EXIT_BLOCK_PTR->preds) == 0) + { + warn_function_noreturn (cfun->decl); + if (dump_file) + fprintf (dump_file, "Function found to be noreturn: %s\n", + lang_hooks.decl_printable_name (current_function_decl, 2)); + + /* Update declaration and reduce profile to executed once. */ + TREE_THIS_VOLATILE (current_function_decl) = 1; + if (node->frequency > NODE_FREQUENCY_EXECUTED_ONCE) + node->frequency = NODE_FREQUENCY_EXECUTED_ONCE; + + changed = true; + } + + switch (l->pure_const_state) + { + case IPA_CONST: + if (!TREE_READONLY (current_function_decl)) + { + warn_function_const (current_function_decl, !l->looping); + if (!skip) + { + cgraph_set_const_flag (node, true, l->looping); + changed = true; + } + if (dump_file) + fprintf (dump_file, "Function found to be %sconst: %s\n", + l->looping ? "looping " : "", + lang_hooks.decl_printable_name (current_function_decl, + 2)); + } + else if (DECL_LOOPING_CONST_OR_PURE_P (current_function_decl) + && !l->looping) + { + if (!skip) + { + cgraph_set_const_flag (node, true, false); + changed = true; + } + if (dump_file) + fprintf (dump_file, "Function found to be non-looping: %s\n", + lang_hooks.decl_printable_name (current_function_decl, + 2)); + } + break; + + case IPA_PURE: + if (!DECL_PURE_P (current_function_decl)) + { + if (!skip) + { + cgraph_set_pure_flag (node, true, l->looping); + changed = true; + } + warn_function_pure (current_function_decl, !l->looping); + if (dump_file) + fprintf (dump_file, "Function found to be %spure: %s\n", + l->looping ? "looping " : "", + lang_hooks.decl_printable_name (current_function_decl, + 2)); + } + else if (DECL_LOOPING_CONST_OR_PURE_P (current_function_decl) + && !l->looping) + { + if (!skip) + { + cgraph_set_pure_flag (node, true, false); + changed = true; + } + if (dump_file) + fprintf (dump_file, "Function found to be non-looping: %s\n", + lang_hooks.decl_printable_name (current_function_decl, + 2)); + } + break; + + default: + break; + } + if (!l->can_throw && !TREE_NOTHROW (current_function_decl)) + { + struct cgraph_edge *e; + + cgraph_set_nothrow_flag (node, true); + for (e = node->callers; e; e = e->next_caller) + e->can_throw_external = false; + changed = true; + if (dump_file) + fprintf (dump_file, "Function found to be nothrow: %s\n", + lang_hooks.decl_printable_name (current_function_decl, + 2)); + } + if (l) + free (l); + if (changed) + return execute_fixup_cfg (); + else + return 0; +} + +struct gimple_opt_pass pass_local_pure_const = +{ + { + GIMPLE_PASS, + "local-pure-const", /* name */ + gate_pure_const, /* gate */ + local_pure_const, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_IPA_PURE_CONST, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0 /* todo_flags_finish */ + } +}; -- cgit v1.2.3