From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 17412 invoked by alias); 27 Jun 2008 18:51:21 -0000 Received: (qmail 17346 invoked by uid 22791); 27 Jun 2008 18:51:12 -0000 X-Spam-Check-By: sourceware.org Received: from cantor2.suse.de (HELO mx2.suse.de) (195.135.220.15) by sourceware.org (qpsmtpd/0.31) with ESMTP; Fri, 27 Jun 2008 18:50:51 +0000 Received: from Relay2.suse.de (relay-ext.suse.de [195.135.221.8]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mx2.suse.de (Postfix) with ESMTP id CCD6545A24; Fri, 27 Jun 2008 20:50:46 +0200 (CEST) Date: Fri, 27 Jun 2008 19:05:00 -0000 From: Richard Guenther To: gcc-patches@gcc.gnu.org Cc: Daniel Berlin , Diego Novillo Subject: Re: [PATCH] Re-do call-clobber computation, track non-local pt sets, fix PRs 36400, 36373 and 36344 In-Reply-To: Message-ID: References: User-Agent: Alpine 1.10 (LNX 962 2008-03-14) MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org X-SW-Source: 2008-06/txt/msg01775.txt.bz2 On Mon, 23 Jun 2008, Richard Guenther wrote: > > This fixes a bunch of wrong-code bugs with respect to not accounting for > escapes of aggregates for call-clobber computation. It ends up > re-writing call clobber computation in terms of PTA. For this not to > loose too much precision I re-added the ability of PTA to track non-local > points-to sets. > > The patch is already quite big, so I left the following for followups: > > - remove most of the old call clobber computation code, get rid of > SMTs during this. > - track call-used variables separately (this will fix the XFAIL of > pr24287.c) > - some code movement and proper fixing of the PTA alias-warning code > > The code idea of this patch should be applicable to the 4.3 branch as > well, I'll try to prepare a version for the branch at some point. > > I tested compile-time performance of this patch on the usual PTA > time-hogs (especially those from 4.2, where we had a similar > mechanism), and compile-time is basically unchanged. > > Bootstrapped and tested on x86_64-unknown-linux-gnu - ok for trunk? Danny approved this on IRC. The following is what I committed after re-bootstrapping and testing on x86_64 - it contains one fix and the optimization Danny suggested earlier (tread ESCAPED as placeholder during propagation). Richard. 2008-06-23 Richard Guenther PR tree-optimization/36400 PR tree-optimization/36373 PR tree-optimization/36344 * tree-ssa-structalias.c (var_escaped, escaped_tree, escaped_id, var_nonlocal, nonlocal_tree, nonlocal_id): New globals (update_alias_info): Remove call clobbering code. (make_constraint_to): New helper function. (make_escape_constraint): Likewise. (handle_rhs_call): Use it on all pointer containing arguments. Also mark the static chain escaped. (handle_lhs_call): Make constraints from NONLOCAL and ESCAPED instead of ANYTHING. (make_constraint_from): New helper split out from ... (make_constraint_from_anything): ... here. (find_func_aliases): Add constraints for escape sites. (intra_create_variable_infos): Make constraints from NONLOCAL for parameters. (find_what_p_points_to): Interpret NONLOCAL and ESCAPED the same as ANYTHING. (clobber_what_p_points_to): Remove. (clobber_what_escaped): New function. (init_base_vars): Init NONLOCAL and ESCAPED. (do_sd_constraint): Do not propagate the solution from ESCAPED but use ESCAPED as a placeholder. (solve_graph): Likewise. * tree-flow.h (clobber_what_p_points_to): Remove. (clobber_what_escaped): Declare. * tree-ssa-alias.c (set_initial_properties): Call it. Remove code clobbering escaped pointers. * gcc.dg/torture/pr36373-1.c: New testcase. * gcc.dg/torture/pr36373-2.c: Likewise. * gcc.dg/torture/pr36373-3.c: Likewise. * gcc.dg/torture/pr36373-4.c: Likewise. * gcc.dg/torture/pr36373-5.c: Likewise. * gcc.dg/torture/pr36373-6.c: Likewise. * gcc.dg/torture/pr36373-7.c: Likewise. * gcc.dg/torture/pr36373-8.c: Likewise. * gcc.dg/torture/pr36373-9.c: Likewise. * gcc.dg/torture/pr36373-10.c: Likewise. * gcc.dg/torture/pr36400.c: Likewise. * gcc.c-torture/execute/pta-field-1.c: Likewise. * gcc.c-torture/execute/pta-field-2.c: Likewise. * gcc.dg/tree-ssa/loadpre8.c: Remove XFAIL. * gcc.dg/tree-ssa/pr24287.c: XFAIL. Index: trunk/gcc/tree-ssa-structalias.c =================================================================== *** trunk.orig/gcc/tree-ssa-structalias.c 2008-06-25 13:12:30.000000000 +0200 --- trunk/gcc/tree-ssa-structalias.c 2008-06-27 16:58:22.000000000 +0200 *************** get_varinfo_fc (unsigned int n) *** 294,319 **** return v; } /* Variable that represents the unknown pointer. */ static varinfo_t var_anything; static tree anything_tree; - static unsigned int anything_id; /* Variable that represents the NULL pointer. */ static varinfo_t var_nothing; static tree nothing_tree; - static unsigned int nothing_id; /* Variable that represents read only memory. */ static varinfo_t var_readonly; static tree readonly_tree; ! static unsigned int readonly_id; /* Variable that represents integers. This is used for when people do things like &0->a.b. */ static varinfo_t var_integer; static tree integer_tree; - static unsigned int integer_id; /* Lookup a heap var for FROM, and return it if we find one. */ --- 294,327 ---- return v; } + /* Static IDs for the special variables. */ + enum { nothing_id = 0, anything_id = 1, readonly_id = 2, + escaped_id = 3, nonlocal_id = 4, integer_id = 5 }; + /* Variable that represents the unknown pointer. */ static varinfo_t var_anything; static tree anything_tree; /* Variable that represents the NULL pointer. */ static varinfo_t var_nothing; static tree nothing_tree; /* Variable that represents read only memory. */ static varinfo_t var_readonly; static tree readonly_tree; ! ! /* Variable that represents escaped memory. */ ! static varinfo_t var_escaped; ! static tree escaped_tree; ! ! /* Variable that represents nonlocal memory. */ ! static varinfo_t var_nonlocal; ! static tree nonlocal_tree; /* Variable that represents integers. This is used for when people do things like &0->a.b. */ static varinfo_t var_integer; static tree integer_tree; /* Lookup a heap var for FROM, and return it if we find one. */ *************** do_sd_constraint (constraint_graph_t gra *** 1399,1404 **** --- 1407,1413 ---- bitmap_set_bit (sol, anything_id); goto done; } + /* For each variable j in delta (Sol(y)), add an edge in the graph from j to x, and union Sol(j) into Sol(x). */ EXECUTE_IF_SET_IN_BITMAP (delta, 0, j, bi) *************** do_sd_constraint (constraint_graph_t gra *** 1417,1424 **** /* Adding edges from the special vars is pointless. They don't have sets that can change. */ ! if (get_varinfo (t) ->is_special_var) flag |= bitmap_ior_into (sol, get_varinfo (t)->solution); else if (add_graph_edge (graph, lhs, t)) flag |= bitmap_ior_into (sol, get_varinfo (t)->solution); } --- 1426,1441 ---- /* Adding edges from the special vars is pointless. They don't have sets that can change. */ ! if (get_varinfo (t)->is_special_var) flag |= bitmap_ior_into (sol, get_varinfo (t)->solution); + /* Merging the solution from ESCAPED needlessly increases + the set. Use ESCAPED as representative instead. */ + else if (get_varinfo (t)->id == escaped_id + && !bitmap_bit_p (sol, get_varinfo (t)->id)) + { + bitmap_set_bit (sol, escaped_id); + flag = true; + } else if (add_graph_edge (graph, lhs, t)) flag |= bitmap_ior_into (sol, get_varinfo (t)->solution); } *************** solve_graph (constraint_graph_t graph) *** 2351,2357 **** solution_empty = bitmap_empty_p (solution); ! if (!solution_empty) { bitmap_iterator bi; --- 2368,2376 ---- solution_empty = bitmap_empty_p (solution); ! if (!solution_empty ! /* Do not propagate the ESCAPED solution. */ ! && i != escaped_id) { bitmap_iterator bi; *************** update_alias_info (tree stmt, struct ali *** 3271,3294 **** /* Mark all the variables whose address are taken by the statement. */ addr_taken = addresses_taken (stmt); if (addr_taken) ! { ! bitmap_ior_into (gimple_addressable_vars (cfun), addr_taken); ! ! /* If STMT is an escape point, all the addresses taken by it are ! call-clobbered. */ ! if (stmt_escape_type != NO_ESCAPE) ! { ! bitmap_iterator bi; ! unsigned i; ! ! EXECUTE_IF_SET_IN_BITMAP (addr_taken, 0, i, bi) ! { ! tree rvar = referenced_var (i); ! if (!unmodifiable_var_p (rvar)) ! mark_call_clobbered (rvar, stmt_escape_type); ! } ! } ! } /* Process each operand use. For pointers, determine whether they are dereferenced by the statement, or whether their value --- 3290,3296 ---- /* Mark all the variables whose address are taken by the statement. */ addr_taken = addresses_taken (stmt); if (addr_taken) ! bitmap_ior_into (gimple_addressable_vars (cfun), addr_taken); /* Process each operand use. For pointers, determine whether they are dereferenced by the statement, or whether their value *************** handle_ptr_arith (VEC (ce_s, heap) *lhsc *** 3573,3578 **** --- 3575,3608 ---- return true; } + /* Create a constraint ID = OP. */ + + static void + make_constraint_to (unsigned id, tree op) + { + VEC(ce_s, heap) *rhsc = NULL; + struct constraint_expr *c; + struct constraint_expr includes; + unsigned int j; + + includes.var = id; + includes.offset = 0; + includes.type = SCALAR; + + get_constraint_for (op, &rhsc); + for (j = 0; VEC_iterate (ce_s, rhsc, j, c); j++) + process_constraint_1 (new_constraint (includes, *c), true); + VEC_free (ce_s, heap, rhsc); + } + + /* Make constraints necessary to make OP escape. */ + + static void + make_escape_constraint (tree op) + { + make_constraint_to (escaped_id, op); + } + /* For non-IPA mode, generate constraints necessary for a call on the RHS. */ *************** handle_rhs_call (tree rhs) *** 3581,3615 **** { tree arg; call_expr_arg_iterator iter; - struct constraint_expr rhsc; - - rhsc.var = anything_id; - rhsc.offset = 0; - rhsc.type = ADDRESSOF; FOR_EACH_CALL_EXPR_ARG (arg, iter, rhs) ! { ! VEC(ce_s, heap) *lhsc = NULL; ! ! /* Find those pointers being passed, and make sure they end up ! pointing to anything. */ ! if (POINTER_TYPE_P (TREE_TYPE (arg))) ! { ! unsigned int j; ! struct constraint_expr *lhsp; ! ! get_constraint_for (arg, &lhsc); ! do_deref (&lhsc); ! for (j = 0; VEC_iterate (ce_s, lhsc, j, lhsp); j++) ! process_constraint_1 (new_constraint (*lhsp, rhsc), true); ! VEC_free (ce_s, heap, lhsc); ! } ! } } /* For non-IPA mode, generate constraints necessary for a call that returns a pointer and assigns it to LHS. This simply makes ! the LHS point to anything. */ static void handle_lhs_call (tree lhs) --- 3611,3631 ---- { tree arg; call_expr_arg_iterator iter; FOR_EACH_CALL_EXPR_ARG (arg, iter, rhs) ! /* Find those pointers being passed, and make sure they end up ! pointing to anything. */ ! if (could_have_pointers (arg)) ! make_escape_constraint (arg); ! ! /* The static chain escapes as well. */ ! if (CALL_EXPR_STATIC_CHAIN (rhs)) ! make_escape_constraint (CALL_EXPR_STATIC_CHAIN (rhs)); } /* For non-IPA mode, generate constraints necessary for a call that returns a pointer and assigns it to LHS. This simply makes ! the LHS point to global and escaped variables. */ static void handle_lhs_call (tree lhs) *************** handle_lhs_call (tree lhs) *** 3619,3630 **** unsigned int j; struct constraint_expr *lhsp; ! rhsc.var = anything_id; rhsc.offset = 0; rhsc.type = ADDRESSOF; get_constraint_for (lhs, &lhsc); for (j = 0; VEC_iterate (ce_s, lhsc, j, lhsp); j++) process_constraint_1 (new_constraint (*lhsp, rhsc), true); VEC_free (ce_s, heap, lhsc); } --- 3635,3704 ---- unsigned int j; struct constraint_expr *lhsp; ! get_constraint_for (lhs, &lhsc); ! rhsc.var = nonlocal_id; ! rhsc.offset = 0; ! rhsc.type = ADDRESSOF; ! for (j = 0; VEC_iterate (ce_s, lhsc, j, lhsp); j++) ! process_constraint_1 (new_constraint (*lhsp, rhsc), true); ! rhsc.var = escaped_id; rhsc.offset = 0; rhsc.type = ADDRESSOF; + for (j = 0; VEC_iterate (ce_s, lhsc, j, lhsp); j++) + process_constraint_1 (new_constraint (*lhsp, rhsc), true); + VEC_free (ce_s, heap, lhsc); + } + + /* For non-IPA mode, generate constraints necessary for a call of a + const function that returns a pointer in the statement STMT. */ + + static void + handle_const_call (tree stmt) + { + tree lhs = GIMPLE_STMT_OPERAND (stmt, 0); + tree call = get_call_expr_in (stmt); + VEC(ce_s, heap) *lhsc = NULL; + struct constraint_expr rhsc; + unsigned int j; + struct constraint_expr *lhsp; + tree arg; + call_expr_arg_iterator iter; + get_constraint_for (lhs, &lhsc); + + /* If this is a nested function then it can return anything. */ + if (CALL_EXPR_STATIC_CHAIN (call)) + { + rhsc.var = anything_id; + rhsc.offset = 0; + rhsc.type = ADDRESSOF; + for (j = 0; VEC_iterate (ce_s, lhsc, j, lhsp); j++) + process_constraint_1 (new_constraint (*lhsp, rhsc), true); + VEC_free (ce_s, heap, lhsc); + return; + } + + /* May return addresses of globals. */ + rhsc.var = nonlocal_id; + rhsc.offset = 0; + rhsc.type = ADDRESSOF; for (j = 0; VEC_iterate (ce_s, lhsc, j, lhsp); j++) process_constraint_1 (new_constraint (*lhsp, rhsc), true); + + /* May return arguments. */ + FOR_EACH_CALL_EXPR_ARG (arg, iter, call) + if (could_have_pointers (arg)) + { + VEC(ce_s, heap) *argc = NULL; + struct constraint_expr *argp; + int i; + get_constraint_for (arg, &argc); + for (i = 0; VEC_iterate (ce_s, argc, i, argp); i++) + for (j = 0; VEC_iterate (ce_s, lhsc, j, lhsp); j++) + process_constraint_1 (new_constraint (*lhsp, *argp), true); + VEC_free (ce_s, heap, argc); + } + VEC_free (ce_s, heap, lhsc); } *************** handle_lhs_call (tree lhs) *** 3636,3645 **** static void find_func_aliases (tree origt) { ! tree t = origt; VEC(ce_s, heap) *lhsc = NULL; VEC(ce_s, heap) *rhsc = NULL; struct constraint_expr *c; if (TREE_CODE (t) == RETURN_EXPR && TREE_OPERAND (t, 0)) t = TREE_OPERAND (t, 0); --- 3710,3721 ---- static void find_func_aliases (tree origt) { ! tree call, t = origt; VEC(ce_s, heap) *lhsc = NULL; VEC(ce_s, heap) *rhsc = NULL; struct constraint_expr *c; + enum escape_type stmt_escape_type; + int flags; if (TREE_CODE (t) == RETURN_EXPR && TREE_OPERAND (t, 0)) t = TREE_OPERAND (t, 0); *************** find_func_aliases (tree origt) *** 3688,3704 **** In non-ipa mode, we need to generate constraints for each pointer passed by address. */ ! else if (((TREE_CODE (t) == GIMPLE_MODIFY_STMT ! && TREE_CODE (GIMPLE_STMT_OPERAND (t, 1)) == CALL_EXPR ! && !(call_expr_flags (GIMPLE_STMT_OPERAND (t, 1)) ! & (ECF_MALLOC | ECF_MAY_BE_ALLOCA))) ! || (TREE_CODE (t) == CALL_EXPR ! && !(call_expr_flags (t) ! & (ECF_MALLOC | ECF_MAY_BE_ALLOCA))))) { if (!in_ipa_mode) { ! if (TREE_CODE (t) == GIMPLE_MODIFY_STMT) { handle_rhs_call (GIMPLE_STMT_OPERAND (t, 1)); if (could_have_pointers (GIMPLE_STMT_OPERAND (t, 1))) --- 3764,3789 ---- In non-ipa mode, we need to generate constraints for each pointer passed by address. */ ! else if ((call = get_call_expr_in (t)) != NULL_TREE ! && !((flags = call_expr_flags (call)) ! & (ECF_MALLOC | ECF_MAY_BE_ALLOCA))) { if (!in_ipa_mode) { ! /* Const functions can return their arguments and addresses ! of global memory but not of escaped memory. */ ! if (flags & ECF_CONST) ! { ! if (TREE_CODE (t) == GIMPLE_MODIFY_STMT ! && could_have_pointers (GIMPLE_STMT_OPERAND (t, 1))) ! handle_const_call (t); ! } ! /* Pure functions can return addresses in and of memory ! reachable from their arguments, but they are not an escape ! point for reachable memory of their arguments. But as we ! do not compute call-used memory separately we cannot do ! something special here. */ ! else if (TREE_CODE (t) == GIMPLE_MODIFY_STMT) { handle_rhs_call (GIMPLE_STMT_OPERAND (t, 1)); if (could_have_pointers (GIMPLE_STMT_OPERAND (t, 1))) *************** find_func_aliases (tree origt) *** 3893,3898 **** --- 3978,4034 ---- get_varinfo (c->var)->no_tbaa_pruning = true; } + stmt_escape_type = is_escape_site (t); + if (stmt_escape_type == ESCAPE_STORED_IN_GLOBAL) + { + tree rhs; + gcc_assert (TREE_CODE (t) == GIMPLE_MODIFY_STMT); + rhs = GIMPLE_STMT_OPERAND (t, 1); + if (TREE_CODE (rhs) == ADDR_EXPR) + { + tree base = get_base_address (TREE_OPERAND (rhs, 0)); + if (base + && (!DECL_P (base) + || !is_global_var (base))) + make_escape_constraint (rhs); + } + else if (TREE_CODE (rhs) == SSA_NAME + && POINTER_TYPE_P (TREE_TYPE (rhs))) + make_escape_constraint (rhs); + else if (could_have_pointers (rhs)) + make_escape_constraint (rhs); + } + else if (stmt_escape_type == ESCAPE_BAD_CAST) + { + tree rhs; + gcc_assert (TREE_CODE (t) == GIMPLE_MODIFY_STMT); + rhs = GIMPLE_STMT_OPERAND (t, 1); + gcc_assert (CONVERT_EXPR_P (rhs) + || TREE_CODE (rhs) == VIEW_CONVERT_EXPR); + rhs = TREE_OPERAND (rhs, 0); + make_escape_constraint (rhs); + } + else if (stmt_escape_type == ESCAPE_TO_ASM) + { + tree link; + int i; + for (i = 0, link = ASM_OUTPUTS (t); link; i++, link = TREE_CHAIN (link)) + { + tree op = TREE_VALUE (link); + if (op && could_have_pointers (op)) + /* Strictly we'd only need the constraints from ESCAPED and + NONLOCAL. */ + make_escape_constraint (op); + } + for (i = 0, link = ASM_INPUTS (t); link; i++, link = TREE_CHAIN (link)) + { + tree op = TREE_VALUE (link); + if (op && could_have_pointers (op)) + /* Strictly we'd only need the constraint to ESCAPED. */ + make_escape_constraint (op); + } + } + /* After promoting variables and computing aliasing we will need to re-scan most statements. FIXME: Try to minimize the number of statements re-scanned. It's not really necessary to *************** push_fields_onto_fieldstack (tree type, *** 4117,4125 **** return count; } ! /* Create a constraint from ANYTHING variable to VI. */ static void ! make_constraint_from_anything (varinfo_t vi) { struct constraint_expr lhs, rhs; --- 4253,4262 ---- return count; } ! /* Create a constraint ID = &FROM. */ ! static void ! make_constraint_from (varinfo_t vi, int from) { struct constraint_expr lhs, rhs; *************** make_constraint_from_anything (varinfo_t *** 4127,4138 **** lhs.offset = 0; lhs.type = SCALAR; ! rhs.var = anything_id; rhs.offset = 0; rhs.type = ADDRESSOF; process_constraint (new_constraint (lhs, rhs)); } /* Count the number of arguments DECL has, and set IS_VARARGS to true if it is a varargs function. */ --- 4264,4282 ---- lhs.offset = 0; lhs.type = SCALAR; ! rhs.var = from; rhs.offset = 0; rhs.type = ADDRESSOF; process_constraint (new_constraint (lhs, rhs)); } + /* Create a constraint from ANYTHING variable to VI. */ + static void + make_constraint_from_anything (varinfo_t vi) + { + make_constraint_from (vi, anything_id); + } + /* Count the number of arguments DECL has, and set IS_VARARGS to true if it is a varargs function. */ *************** intra_create_variable_infos (void) *** 4477,4483 **** struct constraint_expr lhs, rhs; /* For each incoming pointer argument arg, create the constraint ARG ! = ANYTHING or a dummy variable if flag_argument_noalias is set. */ for (t = DECL_ARGUMENTS (current_function_decl); t; t = TREE_CHAIN (t)) { varinfo_t p; --- 4621,4627 ---- struct constraint_expr lhs, rhs; /* For each incoming pointer argument arg, create the constraint ARG ! = NONLOCAL or a dummy variable if flag_argument_noalias is set. */ for (t = DECL_ARGUMENTS (current_function_decl); t; t = TREE_CHAIN (t)) { varinfo_t p; *************** intra_create_variable_infos (void) *** 4538,4544 **** varinfo_t arg_vi = get_vi_for_tree (t); for (p = arg_vi; p; p = p->next) ! make_constraint_from_anything (p); } } } --- 4682,4688 ---- varinfo_t arg_vi = get_vi_for_tree (t); for (p = arg_vi; p; p = p->next) ! make_constraint_from (p, nonlocal_id); } } } *************** find_what_p_points_to (tree p) *** 4793,4799 **** aliases. */ if (vi->id == nothing_id) pi->pt_null = 1; ! else if (vi->id == anything_id) was_pt_anything = 1; else if (vi->id == readonly_id) was_pt_anything = 1; --- 4937,4945 ---- aliases. */ if (vi->id == nothing_id) pi->pt_null = 1; ! else if (vi->id == anything_id ! || vi->id == nonlocal_id ! || vi->id == escaped_id) was_pt_anything = 1; else if (vi->id == readonly_id) was_pt_anything = 1; *************** find_what_p_points_to (tree p) *** 4842,4883 **** return false; } ! /* Mark everything that p points to as call clobbered. Returns true ! if everything is done and false if all addressable variables need to ! be clobbered because p points to anything. */ bool ! clobber_what_p_points_to (tree p) { - tree lookup_p = p; varinfo_t vi; - struct ptr_info_def *pi; unsigned int i; bitmap_iterator bi; if (!have_alias_info) return false; - /* For parameters, get at the points-to set for the actual parm - decl. */ - if (TREE_CODE (p) == SSA_NAME - && TREE_CODE (SSA_NAME_VAR (p)) == PARM_DECL - && SSA_NAME_IS_DEFAULT_DEF (p)) - lookup_p = SSA_NAME_VAR (p); - - vi = lookup_vi_for_tree (lookup_p); - if (!vi) - return false; - - /* We are asking for the points-to solution of pointers. */ - gcc_assert (!vi->is_artificial_var - && vi->size == vi->fullsize); - - pi = get_ptr_info (p); - /* This variable may have been collapsed, let's get the real ! variable. */ ! vi = get_varinfo (find (vi->id)); /* Mark variables in the solution call-clobbered. */ EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, i, bi) --- 4988,5010 ---- return false; } ! /* Mark the ESCAPED solution as call clobbered. Returns false if ! pt_anything escaped which needs all locals that have their address ! taken marked call clobbered as well. */ bool ! clobber_what_escaped (void) { varinfo_t vi; unsigned int i; bitmap_iterator bi; if (!have_alias_info) return false; /* This variable may have been collapsed, let's get the real ! variable for escaped_id. */ ! vi = get_varinfo (find (escaped_id)); /* Mark variables in the solution call-clobbered. */ EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, i, bi) *************** clobber_what_p_points_to (tree p) *** 4902,4913 **** || TREE_CODE (vi->decl) == PARM_DECL || TREE_CODE (vi->decl) == RESULT_DECL) && !unmodifiable_var_p (vi->decl)) ! mark_call_clobbered (vi->decl, pi->escape_mask); } return true; } /* Dump points-to information to OUTFILE. */ void --- 5029,5041 ---- || TREE_CODE (vi->decl) == PARM_DECL || TREE_CODE (vi->decl) == RESULT_DECL) && !unmodifiable_var_p (vi->decl)) ! mark_call_clobbered (vi->decl, ESCAPE_TO_CALL); } return true; } + /* Dump points-to information to OUTFILE. */ void *************** init_base_vars (void) *** 4958,4977 **** /* Create the NULL variable, used to represent that a variable points to NULL. */ nothing_tree = create_tmp_var_raw (void_type_node, "NULL"); ! var_nothing = new_var_info (nothing_tree, 0, "NULL"); insert_vi_for_tree (nothing_tree, var_nothing); var_nothing->is_artificial_var = 1; var_nothing->offset = 0; var_nothing->size = ~0; var_nothing->fullsize = ~0; var_nothing->is_special_var = 1; - nothing_id = 0; VEC_safe_push (varinfo_t, heap, varmap, var_nothing); /* Create the ANYTHING variable, used to represent that a variable points to some unknown piece of memory. */ anything_tree = create_tmp_var_raw (void_type_node, "ANYTHING"); ! var_anything = new_var_info (anything_tree, 1, "ANYTHING"); insert_vi_for_tree (anything_tree, var_anything); var_anything->is_artificial_var = 1; var_anything->size = ~0; --- 5086,5104 ---- /* Create the NULL variable, used to represent that a variable points to NULL. */ nothing_tree = create_tmp_var_raw (void_type_node, "NULL"); ! var_nothing = new_var_info (nothing_tree, nothing_id, "NULL"); insert_vi_for_tree (nothing_tree, var_nothing); var_nothing->is_artificial_var = 1; var_nothing->offset = 0; var_nothing->size = ~0; var_nothing->fullsize = ~0; var_nothing->is_special_var = 1; VEC_safe_push (varinfo_t, heap, varmap, var_nothing); /* Create the ANYTHING variable, used to represent that a variable points to some unknown piece of memory. */ anything_tree = create_tmp_var_raw (void_type_node, "ANYTHING"); ! var_anything = new_var_info (anything_tree, anything_id, "ANYTHING"); insert_vi_for_tree (anything_tree, var_anything); var_anything->is_artificial_var = 1; var_anything->size = ~0; *************** init_base_vars (void) *** 4979,4985 **** var_anything->next = NULL; var_anything->fullsize = ~0; var_anything->is_special_var = 1; - anything_id = 1; /* Anything points to anything. This makes deref constraints just work in the presence of linked list and other p = *p type loops, --- 5106,5111 ---- *************** init_base_vars (void) *** 5000,5006 **** /* Create the READONLY variable, used to represent that a variable points to readonly memory. */ readonly_tree = create_tmp_var_raw (void_type_node, "READONLY"); ! var_readonly = new_var_info (readonly_tree, 2, "READONLY"); var_readonly->is_artificial_var = 1; var_readonly->offset = 0; var_readonly->size = ~0; --- 5126,5132 ---- /* Create the READONLY variable, used to represent that a variable points to readonly memory. */ readonly_tree = create_tmp_var_raw (void_type_node, "READONLY"); ! var_readonly = new_var_info (readonly_tree, readonly_id, "READONLY"); var_readonly->is_artificial_var = 1; var_readonly->offset = 0; var_readonly->size = ~0; *************** init_base_vars (void) *** 5008,5014 **** var_readonly->next = NULL; var_readonly->is_special_var = 1; insert_vi_for_tree (readonly_tree, var_readonly); - readonly_id = 2; VEC_safe_push (varinfo_t, heap, varmap, var_readonly); /* readonly memory points to anything, in order to make deref --- 5134,5139 ---- *************** init_base_vars (void) *** 5019,5033 **** lhs.var = readonly_id; lhs.offset = 0; rhs.type = ADDRESSOF; ! rhs.var = anything_id; rhs.offset = 0; process_constraint (new_constraint (lhs, rhs)); /* Create the INTEGER variable, used to represent that a variable points to an INTEGER. */ integer_tree = create_tmp_var_raw (void_type_node, "INTEGER"); ! var_integer = new_var_info (integer_tree, 3, "INTEGER"); insert_vi_for_tree (integer_tree, var_integer); var_integer->is_artificial_var = 1; var_integer->size = ~0; --- 5144,5201 ---- lhs.var = readonly_id; lhs.offset = 0; rhs.type = ADDRESSOF; ! rhs.var = readonly_id; /* FIXME */ ! rhs.offset = 0; ! process_constraint (new_constraint (lhs, rhs)); ! ! /* Create the ESCAPED variable, used to represent the set of escaped ! memory. */ ! escaped_tree = create_tmp_var_raw (void_type_node, "ESCAPED"); ! var_escaped = new_var_info (escaped_tree, escaped_id, "ESCAPED"); ! insert_vi_for_tree (escaped_tree, var_escaped); ! var_escaped->is_artificial_var = 1; ! var_escaped->offset = 0; ! var_escaped->size = ~0; ! var_escaped->fullsize = ~0; ! var_escaped->is_special_var = 0; ! VEC_safe_push (varinfo_t, heap, varmap, var_escaped); ! gcc_assert (VEC_index (varinfo_t, varmap, 3) == var_escaped); ! ! /* ESCAPED = *ESCAPED, because escaped is may-deref'd at calls, etc. */ ! lhs.type = SCALAR; ! lhs.var = escaped_id; ! lhs.offset = 0; ! rhs.type = DEREF; ! rhs.var = escaped_id; rhs.offset = 0; + process_constraint_1 (new_constraint (lhs, rhs), true); + + /* Create the NONLOCAL variable, used to represent the set of nonlocal + memory. */ + nonlocal_tree = create_tmp_var_raw (void_type_node, "NONLOCAL"); + var_nonlocal = new_var_info (nonlocal_tree, nonlocal_id, "NONLOCAL"); + insert_vi_for_tree (nonlocal_tree, var_nonlocal); + var_nonlocal->is_artificial_var = 1; + var_nonlocal->offset = 0; + var_nonlocal->size = ~0; + var_nonlocal->fullsize = ~0; + var_nonlocal->is_special_var = 1; + VEC_safe_push (varinfo_t, heap, varmap, var_nonlocal); + /* Nonlocal memory points to escaped (which includes nonlocal), + in order to make deref easier. */ + lhs.type = SCALAR; + lhs.var = nonlocal_id; + lhs.offset = 0; + rhs.type = ADDRESSOF; + rhs.var = escaped_id; + rhs.offset = 0; process_constraint (new_constraint (lhs, rhs)); /* Create the INTEGER variable, used to represent that a variable points to an INTEGER. */ integer_tree = create_tmp_var_raw (void_type_node, "INTEGER"); ! var_integer = new_var_info (integer_tree, integer_id, "INTEGER"); insert_vi_for_tree (integer_tree, var_integer); var_integer->is_artificial_var = 1; var_integer->size = ~0; *************** init_base_vars (void) *** 5035,5041 **** var_integer->offset = 0; var_integer->next = NULL; var_integer->is_special_var = 1; - integer_id = 3; VEC_safe_push (varinfo_t, heap, varmap, var_integer); /* INTEGER = ANYTHING, because we don't know where a dereference of --- 5203,5208 ---- *************** init_base_vars (void) *** 5047,5052 **** --- 5214,5239 ---- rhs.var = anything_id; rhs.offset = 0; process_constraint (new_constraint (lhs, rhs)); + + /* *ESCAPED = &ESCAPED. This is true because we have to assume + everything pointed to by escaped can also point to escaped. */ + lhs.type = DEREF; + lhs.var = escaped_id; + lhs.offset = 0; + rhs.type = ADDRESSOF; + rhs.var = escaped_id; + rhs.offset = 0; + process_constraint_1 (new_constraint (lhs, rhs), true); + + /* *ESCAPED = &NONLOCAL. This is true because we have to assume + everything pointed to by escaped can also point to nonlocal. */ + lhs.type = DEREF; + lhs.var = escaped_id; + lhs.offset = 0; + rhs.type = ADDRESSOF; + rhs.var = nonlocal_id; + rhs.offset = 0; + process_constraint_1 (new_constraint (lhs, rhs), true); } /* Initialize things necessary to perform PTA */ Index: trunk/gcc/tree-flow.h =================================================================== *** trunk.orig/gcc/tree-flow.h 2008-06-23 15:52:50.000000000 +0200 --- trunk/gcc/tree-flow.h 2008-06-27 12:07:55.000000000 +0200 *************** tree gimple_fold_indirect_ref (tree); *** 1173,1179 **** /* In tree-ssa-structalias.c */ bool find_what_p_points_to (tree); ! bool clobber_what_p_points_to (tree); /* In tree-ssa-live.c */ extern void remove_unused_locals (void); --- 1173,1179 ---- /* In tree-ssa-structalias.c */ bool find_what_p_points_to (tree); ! bool clobber_what_escaped (void); /* In tree-ssa-live.c */ extern void remove_unused_locals (void); Index: trunk/gcc/tree-ssa-alias.c =================================================================== *** trunk.orig/gcc/tree-ssa-alias.c 2008-06-23 15:52:50.000000000 +0200 --- trunk/gcc/tree-ssa-alias.c 2008-06-27 12:07:55.000000000 +0200 *************** set_initial_properties (struct alias_inf *** 537,542 **** --- 537,548 ---- } } + if (!clobber_what_escaped ()) + { + any_pt_anything = true; + pt_anything_mask |= ESCAPE_TO_CALL; + } + for (i = 0; VEC_iterate (tree, ai->processed_ptrs, i, ptr); i++) { struct ptr_info_def *pi = SSA_NAME_PTR_INFO (ptr); *************** set_initial_properties (struct alias_inf *** 557,574 **** if (tag) mark_call_clobbered (tag, pi->escape_mask); - - /* Defer to points-to analysis if possible, otherwise - clobber all addressable variables. Parameters cannot - point to local memory though. - ??? Properly tracking which pointers point to non-local - memory only would make a big difference here. */ - if (!clobber_what_p_points_to (ptr) - && !(pi->escape_mask & ESCAPE_IS_PARM)) - { - any_pt_anything = true; - pt_anything_mask |= pi->escape_mask; - } } /* If the name tag is call clobbered, so is the symbol tag --- 563,568 ---- *************** is_escape_site (tree stmt) *** 2906,2911 **** --- 2900,2911 ---- if (TREE_CODE (lhs) == SSA_NAME) return NO_ESCAPE; + /* If the LHS is a non-global decl, it isn't a non-local memory store. + If the LHS escapes, the RHS escape is dealt with in the PTA solver. */ + if (DECL_P (lhs) + && !is_global_var (lhs)) + return NO_ESCAPE; + /* FIXME: LHS is not an SSA_NAME. Even if it's an assignment to a local variables we cannot be sure if it will escape, because we don't have information about objects not in SSA form. Need to Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-1.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-1.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,35 ---- + /* { dg-do run } */ + /* { dg-options "-fno-tree-sra" } */ + + extern void abort (void); + struct Bar { + struct Foo { + int *p; + } x; + int *q; + }; + struct Foo __attribute__((noinline)) + bar(int *p) + { + struct Foo f; + f.p = p; + return f; + } + void __attribute__((noinline)) + foo(struct Foo f) + { + *f.p = 0; + } + int main() + { + int a, b; + a = 0; + b = 1; + struct Bar f; + f.x = bar (&b); + f.q = &a; + foo(f.x); + if (b != 0) + abort (); + return 0; + } Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-2.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-2.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,37 ---- + /* { dg-do run } */ + /* { dg-options "-fno-tree-sra" } */ + + extern void abort (void); + struct Foo { + int *p; + }; + struct Bar { + struct Foo *x; + int *q; + }; + struct Foo __attribute__((noinline)) + bar(int *p) + { + struct Foo f; + f.p = p; + return f; + } + void __attribute__((noinline)) + foo(struct Foo f) + { + *f.p = 0; + } + int main() + { + int a, b; + a = 0; + b = 1; + struct Bar f; + struct Foo g = bar (&b); + f.x = &g; + f.q = &a; + foo(*f.x); + if (b != 0) + abort (); + return 0; + } Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-3.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-3.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,36 ---- + /* { dg-do run } */ + + extern void abort (void); + struct Foo { + int *p; + }; + struct Bar { + struct Foo *x; + int *q; + }; + struct Foo __attribute__((noinline)) + bar(int *p) + { + struct Foo f; + f.p = p; + return f; + } + void __attribute__((noinline)) + foo(struct Foo f) + { + *f.p = 0; + } + int main() + { + int a, b; + a = 0; + b = 1; + struct Bar f; + struct Foo g = bar (&b); + f.x = &g; + f.q = &a; + foo(*f.x); + if (b != 0) + abort (); + return 0; + } Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-4.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-4.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,33 ---- + /* { dg-do run } */ + + extern void abort (void); + struct Foo { + int *p; + int *q; + }; + struct Foo __attribute__((noinline)) + bar(int *p) + { + struct Foo f; + f.p = p; + return f; + } + void __attribute__((noinline)) + foo(struct Foo f) + { + *f.p = 0; + } + int main() + { + int a, b; + a = 0; + b = 1; + struct Foo f; + f = bar (&b); + f.q = &a; + foo(f); + if (b != 0) + abort (); + return 0; + } + Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-5.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-5.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,34 ---- + /* { dg-do run } */ + /* { dg-options "-fno-tree-sra" } */ + + extern void abort (void); + struct Foo { + int *p; + int *q; + }; + struct Foo __attribute__((noinline)) + bar(int *p) + { + struct Foo f; + f.p = p; + return f; + } + void __attribute__((noinline)) + foo(struct Foo f) + { + *f.p = 0; + } + int main() + { + int a, b; + a = 0; + b = 1; + struct Foo f; + f = bar (&b); + f.q = &a; + foo(f); + if (b != 0) + abort (); + return 0; + } + Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-6.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-6.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,30 ---- + /* { dg-do run } */ + /* { dg-options "-fno-tree-sra" } */ + + extern void abort (void); + struct Foo { + int *p; + } x; + struct Foo __attribute__((noinline)) + bar(int *p) + { + struct Foo f; + f.p = p; + return f; + } + void __attribute__((noinline)) + foo() + { + *x.p = 0; + } + int main() + { + int b; + b = 1; + struct Foo g = bar (&b); + x = g; + foo(); + if (b != 0) + abort (); + return 0; + } Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-7.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-7.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,29 ---- + /* { dg-do run } */ + + extern void abort (void); + struct Foo { + int *p; + } x; + struct Foo __attribute__((noinline)) + bar(int *p) + { + struct Foo f; + f.p = p; + return f; + } + void __attribute__((noinline)) + foo() + { + *x.p = 0; + } + int main() + { + int b; + b = 1; + struct Foo g = bar (&b); + x = g; + foo(); + if (b != 0) + abort (); + return 0; + } Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-10.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-10.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,21 ---- + /* { dg-do run } */ + + typedef unsigned long uintptr_t; + + void __attribute__((noinline)) + foo(uintptr_t l) + { + int *p = (int *)l; + *p = 1; + } + + extern void abort (void); + int main() + { + int b = 0; + uintptr_t l = (uintptr_t)&b; + foo(l); + if (b != 1) + abort (); + return 0; + } Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-8.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-8.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,24 ---- + /* { dg-do run } */ + /* { dg-options "-fno-tree-sra" } */ + + extern void abort (void); + struct Foo { + int *p; + } x; + void __attribute__((noinline)) + foo() + { + *x.p = 0; + } + int main() + { + int b; + struct Foo g; + b = 1; + g.p = &b; + x = g; + foo(); + if (b != 0) + abort (); + return 0; + } Index: trunk/gcc/testsuite/gcc.dg/torture/pr36373-9.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36373-9.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,23 ---- + /* { dg-do run } */ + + extern void abort (void); + struct Foo { + int *p; + } x; + void __attribute__((noinline)) + foo() + { + *x.p = 0; + } + int main() + { + int b; + struct Foo g; + b = 1; + g.p = &b; + x = g; + foo(); + if (b != 0) + abort (); + return 0; + } Index: trunk/gcc/testsuite/gcc.dg/torture/pr36400.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/torture/pr36400.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,16 ---- + /* { dg-do compile } */ + + struct barstruct { char const* some_string; }; + + void changethepointer(struct barstruct***); + + void baz() + { + struct barstruct bar1; + struct barstruct* barptr = &bar1; + struct barstruct** barptr2 = &barptr; + changethepointer(&barptr2); + barptr->some_string = "Everything OK"; + } + + /* { dg-final { scan-assembler "Everything OK" } } */ Index: trunk/gcc/testsuite/gcc.dg/tree-ssa/loadpre8.c =================================================================== *** trunk.orig/gcc/testsuite/gcc.dg/tree-ssa/loadpre8.c 2008-06-23 15:52:50.000000000 +0200 --- trunk/gcc/testsuite/gcc.dg/tree-ssa/loadpre8.c 2008-06-26 14:16:04.000000000 +0200 *************** rewrite_add_phi_arguments (basic_block b *** 93,97 **** get_reaching_def ((get_def_from_ptr (get_phi_result_ptr (phi)))->ssa_name.var); } } ! /* { dg-final { scan-tree-dump-times "Eliminated: 1" 1 "pre" { xfail *-*-* } } } */ /* { dg-final { cleanup-tree-dump "pre" } } */ --- 93,97 ---- get_reaching_def ((get_def_from_ptr (get_phi_result_ptr (phi)))->ssa_name.var); } } ! /* { dg-final { scan-tree-dump-times "Eliminated: 1" 1 "pre" } } */ /* { dg-final { cleanup-tree-dump "pre" } } */ Index: trunk/gcc/testsuite/gcc.dg/tree-ssa/pr24287-2.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.dg/tree-ssa/pr24287-2.c 2008-06-26 14:16:04.000000000 +0200 *************** *** 0 **** --- 1,25 ---- + /* { dg-do compile } */ + /* { dg-options "-O2 -fdump-tree-optimized" } */ + int g1(int); + int h(int *a, int *b)__attribute__((const)); + void link_error(); + + /* The calls to link_error should be eliminated, since nothing escapes to + non-const functions. */ + int g(void) + { + int t = 0, t1 = 2; + int t2 = h(&t, &t1); + if (t != 0) + link_error (); + if (t1 != 2) + link_error (); + g1(t2); + if (t != 0) + link_error (); + if (t1 != 2) + link_error (); + return t2 == 2; + } + /* { dg-final { scan-tree-dump-times "link_error" 0 "optimized"} } */ + /* { dg-final { cleanup-tree-dump "optimized" } } */ Index: trunk/gcc/testsuite/gcc.dg/tree-ssa/pr24287.c =================================================================== *** trunk.orig/gcc/testsuite/gcc.dg/tree-ssa/pr24287.c 2008-06-23 15:52:50.000000000 +0200 --- trunk/gcc/testsuite/gcc.dg/tree-ssa/pr24287.c 2008-06-27 12:07:55.000000000 +0200 *************** int g(void) *** 21,25 **** link_error (); return t2 == 2; } ! /* { dg-final { scan-tree-dump-times "link_error" 0 "optimized"} } */ /* { dg-final { cleanup-tree-dump "optimized" } } */ --- 21,25 ---- link_error (); return t2 == 2; } ! /* { dg-final { scan-tree-dump-times "link_error" 0 "optimized" { xfail *-*-* } } } */ /* { dg-final { cleanup-tree-dump "optimized" } } */ Index: trunk/gcc/testsuite/gcc.c-torture/execute/pta-field-1.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.c-torture/execute/pta-field-1.c 2008-06-27 12:08:35.000000000 +0200 *************** *** 0 **** --- 1,28 ---- + struct Foo { + int *p; + int *q; + }; + + void __attribute__((noinline)) + bar (int **x) + { + struct Foo *f = (struct Foo *)x; + *(f->q) = 0; + } + + int foo(void) + { + struct Foo f; + int i = 1, j = 2; + f.p = &i; + f.q = &j; + bar(&f.p); + return j; + } + + extern void abort (void); + int main() + { + if (foo () != 0) + abort (); + } Index: trunk/gcc/testsuite/gcc.c-torture/execute/pta-field-2.c =================================================================== *** /dev/null 1970-01-01 00:00:00.000000000 +0000 --- trunk/gcc/testsuite/gcc.c-torture/execute/pta-field-2.c 2008-06-27 12:08:35.000000000 +0200 *************** *** 0 **** --- 1,28 ---- + struct Foo { + int *p; + int *q; + }; + + void __attribute__((noinline)) + bar (int **x) + { + struct Foo *f = (struct Foo *)(x - 1); + *(f->p) = 0; + } + + int foo(void) + { + struct Foo f; + int i = 1, j = 2; + f.p = &i; + f.q = &j; + bar(&f.q); + return i; + } + + extern void abort (void); + int main() + { + if (foo () != 0) + abort (); + }