static void verify_flow_insensitive_alias_info (void) { tree var; bitmap visited = BITMAP_ALLOC (NULL); referenced_var_iterator rvi; FOR_EACH_REFERENCED_VAR (var, rvi) { size_t j; var_ann_t ann; VEC(tree,gc) *may_aliases; tree alias; ann = var_ann (var); may_aliases = ann->may_aliases; for (j = 0; VEC_iterate (tree, may_aliases, j, alias); j++) { bitmap_set_bit (visited, DECL_UID (alias)); if (!may_be_aliased (alias)) { error ("non-addressable variable inside an alias set"); debug_variable (alias); goto err; } } }
static void init_parameter_lattice_values (void) { tree parm, ssa_name; for (parm = DECL_ARGUMENTS (cfun->decl); parm ; parm = TREE_CHAIN (parm)) if (is_complex_reg (parm) && var_ann (parm) != NULL && (ssa_name = gimple_default_def (cfun, parm)) != NULL_TREE) VEC_replace (complex_lattice_t, complex_lattice_values, SSA_NAME_VERSION (ssa_name), VARYING); }
FOR_EACH_REFERENCED_VAR (var, rvi) { var_ann_t ann; ann = var_ann (var); if (!MTAG_P (var) && ann->is_aliased && !bitmap_bit_p (visited, DECL_UID (var))) { error ("addressable variable that is aliased but is not in any alias set"); goto err; } }
bool is_gimple_reg (tree t) { var_ann_t ann; if (TREE_CODE (t) == SSA_NAME) t = SSA_NAME_VAR (t); if (!is_gimple_variable (t)) return false; if (!is_gimple_reg_type (TREE_TYPE (t))) return false; /* A volatile decl is not acceptable because we can't reuse it as needed. We need to copy it into a temp first. */ if (TREE_THIS_VOLATILE (t)) return false; /* We define "registers" as things that can be renamed as needed, which with our infrastructure does not apply to memory. */ if (needs_to_live_in_memory (t)) return false; /* Hard register variables are an interesting case. For those that are call-clobbered, we don't know where all the calls are, since we don't (want to) take into account which operations will turn into libcalls at the rtl level. For those that are call-saved, we don't currently model the fact that calls may in fact change global hard registers, nor do we examine ASM_CLOBBERS at the tree level, and so miss variable changes that might imply. All around, it seems safest to not do too much optimization with these at the tree level at all. We'll have to rely on the rtl optimizers to clean this up, as there we've got all the appropriate bits exposed. */ if (TREE_CODE (t) == VAR_DECL && DECL_HARD_REGISTER (t)) return false; /* Complex values must have been put into ssa form. That is, no assignments to the individual components. */ if (TREE_CODE (TREE_TYPE (t)) == COMPLEX_TYPE) return DECL_COMPLEX_GIMPLE_REG_P (t); /* Some compiler temporaries are created to be used exclusively in virtual operands (currently memory tags and sub-variables). These variables should never be considered GIMPLE registers. */ if (DECL_ARTIFICIAL (t) && (ann = var_ann (t)) != NULL) return ann->mem_tag_kind == NOT_A_TAG; return true; }
static bool expand_var_p (tree var) { struct var_ann_d *ann; if (TREE_CODE (var) != VAR_DECL) return true; /* Leave statics and externals alone. */ if (TREE_STATIC (var) || DECL_EXTERNAL (var)) return true; /* Remove all unused local variables. */ ann = var_ann (var); if (!ann || !ann->used) return false; return true; }
static bool verify_ssa_name (tree ssa_name, bool is_virtual) { if (TREE_CODE (ssa_name) != SSA_NAME) { error ("expected an SSA_NAME object"); return true; } if (TREE_TYPE (ssa_name) != TREE_TYPE (SSA_NAME_VAR (ssa_name))) { error ("type mismatch between an SSA_NAME and its symbol"); return true; } if (SSA_NAME_IN_FREE_LIST (ssa_name)) { error ("found an SSA_NAME that had been released into the free pool"); return true; } if (is_virtual && is_gimple_reg (ssa_name)) { error ("found a virtual definition for a GIMPLE register"); return true; } if (!is_virtual && !is_gimple_reg (ssa_name)) { error ("found a real definition for a non-register"); return true; } if (is_virtual && var_ann (SSA_NAME_VAR (ssa_name)) && get_subvars_for_var (SSA_NAME_VAR (ssa_name)) != NULL) { error ("found real variable when subvariables should have appeared"); return true; } return false; }
static void tree_nrv (void) { tree result = DECL_RESULT (current_function_decl); tree result_type = TREE_TYPE (result); tree found = NULL; basic_block bb; block_stmt_iterator bsi; struct nrv_data data; /* If this function does not return an aggregate type in memory, then there is nothing to do. */ if (!aggregate_value_p (result, current_function_decl)) return; /* Look through each block for assignments to the RESULT_DECL. */ FOR_EACH_BB (bb) { for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi)) { tree stmt = bsi_stmt (bsi); tree ret_expr; if (TREE_CODE (stmt) == RETURN_EXPR) { /* In a function with an aggregate return value, the gimplifier has changed all non-empty RETURN_EXPRs to return the RESULT_DECL. */ ret_expr = TREE_OPERAND (stmt, 0); if (ret_expr) gcc_assert (ret_expr == result); } else if (TREE_CODE (stmt) == MODIFY_EXPR && TREE_OPERAND (stmt, 0) == result) { ret_expr = TREE_OPERAND (stmt, 1); /* Now verify that this return statement uses the same value as any previously encountered return statement. */ if (found != NULL) { /* If we found a return statement using a different variable than previous return statements, then we can not perform NRV optimizations. */ if (found != ret_expr) return; } else found = ret_expr; /* The returned value must be a local automatic variable of the same type and alignment as the function's result. */ if (TREE_CODE (found) != VAR_DECL || TREE_THIS_VOLATILE (found) || DECL_CONTEXT (found) != current_function_decl || TREE_STATIC (found) || TREE_ADDRESSABLE (found) || DECL_ALIGN (found) > DECL_ALIGN (result) || !lang_hooks.types_compatible_p (TREE_TYPE (found), result_type)) return; } } } if (!found) return; /* If dumping details, then note once and only the NRV replacement. */ if (dump_file && (dump_flags & TDF_DETAILS)) { fprintf (dump_file, "NRV Replaced: "); print_generic_expr (dump_file, found, dump_flags); fprintf (dump_file, " with: "); print_generic_expr (dump_file, result, dump_flags); fprintf (dump_file, "\n"); } /* At this point we know that all the return statements return the same local which has suitable attributes for NRV. Copy debugging information from FOUND to RESULT. */ DECL_NAME (result) = DECL_NAME (found); DECL_SOURCE_LOCATION (result) = DECL_SOURCE_LOCATION (found); DECL_ABSTRACT_ORIGIN (result) = DECL_ABSTRACT_ORIGIN (found); TREE_ADDRESSABLE (result) = TREE_ADDRESSABLE (found); /* Now walk through the function changing all references to VAR to be RESULT. */ data.var = found; data.result = result; FOR_EACH_BB (bb) { for (bsi = bsi_start (bb); !bsi_end_p (bsi); ) { tree *tp = bsi_stmt_ptr (bsi); /* If this is a copy from VAR to RESULT, remove it. */ if (TREE_CODE (*tp) == MODIFY_EXPR && TREE_OPERAND (*tp, 0) == result && TREE_OPERAND (*tp, 1) == found) bsi_remove (&bsi); else { walk_tree (tp, finalize_nrv_r, &data, 0); bsi_next (&bsi); } } } /* FOUND is no longer used. Ensure it gets removed. */ var_ann (found)->used = 0; }
bool may_propagate_copy (tree dest, tree orig) { tree type_d = TREE_TYPE (dest); tree type_o = TREE_TYPE (orig); /* Do not copy between types for which we *do* need a conversion. */ if (!tree_ssa_useless_type_conversion_1 (type_d, type_o)) return false; /* FIXME. GIMPLE is allowing pointer assignments and comparisons of pointers that have different alias sets. This means that these pointers will have different memory tags associated to them. If we allow copy propagation in these cases, statements de-referencing the new pointer will now have a reference to a different memory tag with potentially incorrect SSA information. This was showing up in libjava/java/util/zip/ZipFile.java with code like: struct java.io.BufferedInputStream *T.660; struct java.io.BufferedInputStream *T.647; struct java.io.InputStream *is; struct java.io.InputStream *is.662; [ ... ] T.660 = T.647; is = T.660; <-- This ought to be type-casted is.662 = is; Also, f/name.c exposed a similar problem with a COND_EXPR predicate that was causing DOM to generate and equivalence with two pointers of alias-incompatible types: struct _ffename_space *n; struct _ffename *ns; [ ... ] if (n == ns) goto lab; ... lab: return n; I think that GIMPLE should emit the appropriate type-casts. For the time being, blocking copy-propagation in these cases is the safe thing to do. */ if (TREE_CODE (dest) == SSA_NAME && TREE_CODE (orig) == SSA_NAME && POINTER_TYPE_P (type_d) && POINTER_TYPE_P (type_o)) { tree mt_dest = var_ann (SSA_NAME_VAR (dest))->type_mem_tag; tree mt_orig = var_ann (SSA_NAME_VAR (orig))->type_mem_tag; if (mt_dest && mt_orig && mt_dest != mt_orig) return false; else if (!lang_hooks.types_compatible_p (type_d, type_o)) return false; else if (get_alias_set (TREE_TYPE (type_d)) != get_alias_set (TREE_TYPE (type_o))) return false; } /* If the destination is a SSA_NAME for a virtual operand, then we have some special cases to handle. */ if (TREE_CODE (dest) == SSA_NAME && !is_gimple_reg (dest)) { /* If both operands are SSA_NAMEs referring to virtual operands, then we can always propagate. */ if (TREE_CODE (orig) == SSA_NAME && !is_gimple_reg (orig)) return true; /* We have a "copy" from something like a constant into a virtual operand. Reject these. */ return false; } /* If ORIG flows in from an abnormal edge, it cannot be propagated. */ if (TREE_CODE (orig) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (orig)) return false; /* If DEST is an SSA_NAME that flows from an abnormal edge, then it cannot be replaced. */ if (TREE_CODE (dest) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (dest)) return false; /* Anything else is OK. */ return true; }
return !(TREE_CODE (dest) == SSA_NAME && TREE_CODE (SSA_NAME_VAR (dest)) == VAR_DECL && DECL_HARD_REGISTER (SSA_NAME_VAR (dest))); } /* Given two SSA_NAMEs pointers ORIG and NEW such that we are copy propagating NEW into ORIG, consolidate aliasing information so that they both share the same memory tags. */ void merge_alias_info (tree orig, tree new) { tree new_sym = SSA_NAME_VAR (new); tree orig_sym = SSA_NAME_VAR (orig); var_ann_t new_ann = var_ann (new_sym); var_ann_t orig_ann = var_ann (orig_sym); gcc_assert (POINTER_TYPE_P (TREE_TYPE (orig))); gcc_assert (POINTER_TYPE_P (TREE_TYPE (new))); #if defined ENABLE_CHECKING gcc_assert (lang_hooks.types_compatible_p (TREE_TYPE (orig), TREE_TYPE (new))); /* If the pointed-to alias sets are different, these two pointers would never have the same memory tag. In this case, NEW should not have been propagated into ORIG. */ gcc_assert (get_alias_set (TREE_TYPE (TREE_TYPE (new_sym))) == get_alias_set (TREE_TYPE (TREE_TYPE (orig_sym)))); #endif
void merge_alias_info (tree orig_name, tree new_name) { tree new_sym = SSA_NAME_VAR (new_name); tree orig_sym = SSA_NAME_VAR (orig_name); var_ann_t new_ann = var_ann (new_sym); var_ann_t orig_ann = var_ann (orig_sym); /* No merging necessary when memory partitions are involved. */ if (factoring_name_p (new_name)) { gcc_assert (!is_gimple_reg (orig_sym)); return; } else if (factoring_name_p (orig_name)) { gcc_assert (!is_gimple_reg (new_sym)); return; } gcc_assert (POINTER_TYPE_P (TREE_TYPE (orig_name)) && POINTER_TYPE_P (TREE_TYPE (new_name))); #if defined ENABLE_CHECKING gcc_assert (useless_type_conversion_p (TREE_TYPE (orig_name), TREE_TYPE (new_name))); /* Check that flow-sensitive information is compatible. Notice that we may not merge flow-sensitive information here. This function is called when propagating equivalences dictated by the IL, like a copy operation P_i = Q_j, and from equivalences dictated by control-flow, like if (P_i == Q_j). In the former case, P_i and Q_j are equivalent in every block dominated by the assignment, so their flow-sensitive information is always the same. However, in the latter case, the pointers P_i and Q_j are only equivalent in one of the sub-graphs out of the predicate, so their flow-sensitive information is not the same in every block dominated by the predicate. Since we cannot distinguish one case from another in this function, we can only make sure that if P_i and Q_j have flow-sensitive information, they should be compatible. As callers of merge_alias_info are supposed to call may_propagate_copy first, the following check is redundant. Thus, only do it if checking is enabled. */ if (SSA_NAME_PTR_INFO (orig_name) && SSA_NAME_PTR_INFO (new_name)) { struct ptr_info_def *orig_ptr_info = SSA_NAME_PTR_INFO (orig_name); struct ptr_info_def *new_ptr_info = SSA_NAME_PTR_INFO (new_name); /* Note that pointer NEW and ORIG may actually have different pointed-to variables (e.g., PR 18291 represented in testsuite/gcc.c-torture/compile/pr18291.c). However, since NEW is being copy-propagated into ORIG, it must always be true that the pointed-to set for pointer NEW is the same, or a subset, of the pointed-to set for pointer ORIG. If this isn't the case, we shouldn't have been able to do the propagation of NEW into ORIG. */ if (orig_ptr_info->name_mem_tag && new_ptr_info->name_mem_tag && orig_ptr_info->pt_vars && new_ptr_info->pt_vars) gcc_assert (bitmap_intersect_p (new_ptr_info->pt_vars, orig_ptr_info->pt_vars)); } #endif /* Synchronize the symbol tags. If both pointers had a tag and they are different, then something has gone wrong. Symbol tags can always be merged because they are flow insensitive, all the SSA names of the same base DECL share the same symbol tag. */ if (new_ann->symbol_mem_tag == NULL_TREE) new_ann->symbol_mem_tag = orig_ann->symbol_mem_tag; else if (orig_ann->symbol_mem_tag == NULL_TREE) orig_ann->symbol_mem_tag = new_ann->symbol_mem_tag; else gcc_assert (new_ann->symbol_mem_tag == orig_ann->symbol_mem_tag); /* Copy flow-sensitive alias information in case that NEW_NAME didn't get a NMT but was set to pt_anything for optimization purposes. In case ORIG_NAME has a NMT we can safely use its flow-sensitive alias information as a conservative estimate. */ if (SSA_NAME_PTR_INFO (orig_name) && SSA_NAME_PTR_INFO (orig_name)->name_mem_tag && (!SSA_NAME_PTR_INFO (new_name) || !SSA_NAME_PTR_INFO (new_name)->name_mem_tag)) { struct ptr_info_def *orig_ptr_info = SSA_NAME_PTR_INFO (orig_name); struct ptr_info_def *new_ptr_info = get_ptr_info (new_name); memcpy (new_ptr_info, orig_ptr_info, sizeof (struct ptr_info_def)); } }
static unsigned int tree_nrv (void) { tree result = DECL_RESULT (current_function_decl); tree result_type = TREE_TYPE (result); tree found = NULL; basic_block bb; gimple_stmt_iterator gsi; struct nrv_data data; /* If this function does not return an aggregate type in memory, then there is nothing to do. */ if (!aggregate_value_p (result, current_function_decl)) return 0; /* If a GIMPLE type is returned in memory, finalize_nrv_r might create non-GIMPLE. */ if (is_gimple_reg_type (result_type)) return 0; /* Look through each block for assignments to the RESULT_DECL. */ FOR_EACH_BB (bb) { for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) { gimple stmt = gsi_stmt (gsi); tree ret_val; if (gimple_code (stmt) == GIMPLE_RETURN) { /* In a function with an aggregate return value, the gimplifier has changed all non-empty RETURN_EXPRs to return the RESULT_DECL. */ ret_val = gimple_return_retval (stmt); if (ret_val) gcc_assert (ret_val == result); } else if (is_gimple_assign (stmt) && gimple_assign_lhs (stmt) == result) { tree rhs; if (!gimple_assign_copy_p (stmt)) return 0; rhs = gimple_assign_rhs1 (stmt); /* Now verify that this return statement uses the same value as any previously encountered return statement. */ if (found != NULL) { /* If we found a return statement using a different variable than previous return statements, then we can not perform NRV optimizations. */ if (found != rhs) return 0; } else found = rhs; /* The returned value must be a local automatic variable of the same type and alignment as the function's result. */ if (TREE_CODE (found) != VAR_DECL || TREE_THIS_VOLATILE (found) || DECL_CONTEXT (found) != current_function_decl || TREE_STATIC (found) || TREE_ADDRESSABLE (found) || DECL_ALIGN (found) > DECL_ALIGN (result) || !useless_type_conversion_p (result_type, TREE_TYPE (found))) return 0; } else if (is_gimple_assign (stmt)) { tree addr = get_base_address (gimple_assign_lhs (stmt)); /* If there's any MODIFY of component of RESULT, then bail out. */ if (addr && addr == result) return 0; } } } if (!found) return 0; /* If dumping details, then note once and only the NRV replacement. */ if (dump_file && (dump_flags & TDF_DETAILS)) { fprintf (dump_file, "NRV Replaced: "); print_generic_expr (dump_file, found, dump_flags); fprintf (dump_file, " with: "); print_generic_expr (dump_file, result, dump_flags); fprintf (dump_file, "\n"); } /* At this point we know that all the return statements return the same local which has suitable attributes for NRV. Copy debugging information from FOUND to RESULT. */ DECL_NAME (result) = DECL_NAME (found); DECL_SOURCE_LOCATION (result) = DECL_SOURCE_LOCATION (found); DECL_ABSTRACT_ORIGIN (result) = DECL_ABSTRACT_ORIGIN (found); TREE_ADDRESSABLE (result) = TREE_ADDRESSABLE (found); /* Now walk through the function changing all references to VAR to be RESULT. */ data.var = found; data.result = result; FOR_EACH_BB (bb) { for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); ) { gimple stmt = gsi_stmt (gsi); /* If this is a copy from VAR to RESULT, remove it. */ if (gimple_assign_copy_p (stmt) && gimple_assign_lhs (stmt) == result && gimple_assign_rhs1 (stmt) == found) gsi_remove (&gsi, true); else { struct walk_stmt_info wi; memset (&wi, 0, sizeof (wi)); wi.info = &data; walk_gimple_op (stmt, finalize_nrv_r, &wi); gsi_next (&gsi); } } } /* FOUND is no longer used. Ensure it gets removed. */ var_ann (found)->used = 0; return 0; }