| Index: gcc/gcc/tree-ssa-copy.c
|
| diff --git a/gcc/gcc/tree-ssa-copy.c b/gcc/gcc/tree-ssa-copy.c
|
| index 64c697a51968084608df3665add68ae1930aacb0..9f1f9c41fde11aa48a89abf243cf25a5a5333cde 100644
|
| --- a/gcc/gcc/tree-ssa-copy.c
|
| +++ b/gcc/gcc/tree-ssa-copy.c
|
| @@ -1,5 +1,6 @@
|
| /* Copy propagation and SSA_NAME replacement support routines.
|
| - Copyright (C) 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
|
| + Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010
|
| + Free Software Foundation, Inc.
|
|
|
| This file is part of GCC.
|
|
|
| @@ -37,6 +38,7 @@ along with GCC; see the file COPYING3. If not see
|
| #include "tree-pass.h"
|
| #include "tree-ssa-propagate.h"
|
| #include "langhooks.h"
|
| +#include "cfgloop.h"
|
|
|
| /* This file implements the copy propagation pass and provides a
|
| handful of interfaces for performing const/copy propagation and
|
| @@ -73,111 +75,17 @@ may_propagate_copy (tree dest, tree orig)
|
| && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (dest))
|
| return false;
|
|
|
| - /* For memory partitions, copies are OK as long as the memory symbol
|
| - belongs to the partition. */
|
| - if (TREE_CODE (dest) == SSA_NAME
|
| - && TREE_CODE (SSA_NAME_VAR (dest)) == MEMORY_PARTITION_TAG)
|
| - return (TREE_CODE (orig) == SSA_NAME
|
| - && !is_gimple_reg (orig)
|
| - && (SSA_NAME_VAR (dest) == SSA_NAME_VAR (orig)
|
| - || bitmap_bit_p (MPT_SYMBOLS (SSA_NAME_VAR (dest)),
|
| - DECL_UID (SSA_NAME_VAR (orig)))));
|
| -
|
| - if (TREE_CODE (orig) == SSA_NAME
|
| - && TREE_CODE (SSA_NAME_VAR (orig)) == MEMORY_PARTITION_TAG)
|
| - return (TREE_CODE (dest) == SSA_NAME
|
| - && !is_gimple_reg (dest)
|
| - && (SSA_NAME_VAR (dest) == SSA_NAME_VAR (orig)
|
| - || bitmap_bit_p (MPT_SYMBOLS (SSA_NAME_VAR (orig)),
|
| - DECL_UID (SSA_NAME_VAR (dest)))));
|
| -
|
| /* Do not copy between types for which we *do* need a conversion. */
|
| if (!useless_type_conversion_p (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 = symbol_mem_tag (SSA_NAME_VAR (dest));
|
| - tree mt_orig = symbol_mem_tag (SSA_NAME_VAR (orig));
|
| - if (mt_dest && mt_orig && mt_dest != mt_orig)
|
| - return false;
|
| - else if (get_alias_set (TREE_TYPE (type_d)) !=
|
| - get_alias_set (TREE_TYPE (type_o)))
|
| - return false;
|
| - else if (!MTAG_P (SSA_NAME_VAR (dest))
|
| - && !MTAG_P (SSA_NAME_VAR (orig))
|
| - && (DECL_NO_TBAA_P (SSA_NAME_VAR (dest))
|
| - != DECL_NO_TBAA_P (SSA_NAME_VAR (orig))))
|
| - return false;
|
| -
|
| - /* Also verify flow-sensitive information is compatible. */
|
| - if (SSA_NAME_PTR_INFO (orig) && SSA_NAME_PTR_INFO (dest))
|
| - {
|
| - struct ptr_info_def *orig_ptr_info = SSA_NAME_PTR_INFO (orig);
|
| - struct ptr_info_def *dest_ptr_info = SSA_NAME_PTR_INFO (dest);
|
| -
|
| - if (orig_ptr_info->name_mem_tag
|
| - && dest_ptr_info->name_mem_tag
|
| - && orig_ptr_info->pt_vars
|
| - && dest_ptr_info->pt_vars
|
| - && !bitmap_intersect_p (dest_ptr_info->pt_vars,
|
| - orig_ptr_info->pt_vars))
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - /* If the destination is a SSA_NAME for a virtual operand, then we have
|
| - some special cases to handle. */
|
| + /* Propagating virtual operands is always ok. */
|
| 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;
|
| + /* But only between virtual operands. */
|
| + gcc_assert (TREE_CODE (orig) == SSA_NAME && !is_gimple_reg (orig));
|
| +
|
| + return true;
|
| }
|
|
|
| /* Anything else is OK. */
|
| @@ -211,8 +119,7 @@ may_propagate_copy_into_stmt (gimple dest, tree orig)
|
| is much simpler. */
|
|
|
| if (TREE_CODE (orig) == SSA_NAME
|
| - && (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (orig)
|
| - || TREE_CODE (SSA_NAME_VAR (orig)) == MEMORY_PARTITION_TAG))
|
| + && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (orig))
|
| return false;
|
|
|
| if (is_gimple_assign (dest))
|
| @@ -245,106 +152,6 @@ may_propagate_copy_into_asm (tree 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_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));
|
| - }
|
| -}
|
| -
|
| -
|
| /* Common code for propagate_value and replace_exp.
|
|
|
| Replace use operand OP_P with VAL. FOR_PROPAGATION indicates if the
|
| @@ -354,9 +161,9 @@ static void
|
| replace_exp_1 (use_operand_p op_p, tree val,
|
| bool for_propagation ATTRIBUTE_UNUSED)
|
| {
|
| +#if defined ENABLE_CHECKING
|
| tree op = USE_FROM_PTR (op_p);
|
|
|
| -#if defined ENABLE_CHECKING
|
| gcc_assert (!(for_propagation
|
| && TREE_CODE (op) == SSA_NAME
|
| && TREE_CODE (val) == SSA_NAME
|
| @@ -364,11 +171,7 @@ replace_exp_1 (use_operand_p op_p, tree val,
|
| #endif
|
|
|
| if (TREE_CODE (val) == SSA_NAME)
|
| - {
|
| - if (TREE_CODE (op) == SSA_NAME && POINTER_TYPE_P (TREE_TYPE (op)))
|
| - merge_alias_info (op, val);
|
| - SET_USE (op_p, val);
|
| - }
|
| + SET_USE (op_p, val);
|
| else
|
| SET_USE (op_p, unsave_expr_now (val));
|
| }
|
| @@ -418,11 +221,7 @@ propagate_tree_value (tree *op_p, tree val)
|
| #endif
|
|
|
| if (TREE_CODE (val) == SSA_NAME)
|
| - {
|
| - if (*op_p && TREE_CODE (*op_p) == SSA_NAME && POINTER_TYPE_P (TREE_TYPE (*op_p)))
|
| - merge_alias_info (*op_p, val);
|
| - *op_p = val;
|
| - }
|
| + *op_p = val;
|
| else
|
| *op_p = unsave_expr_now (val);
|
| }
|
| @@ -464,8 +263,7 @@ propagate_tree_value_into_stmt (gimple_stmt_iterator *gsi, tree val)
|
|
|
| tree expr = NULL_TREE;
|
| propagate_tree_value (&expr, val);
|
| - new_stmt = gimple_build_assign (gimple_call_lhs (stmt), expr);
|
| - copy_virtual_operands (new_stmt, stmt);
|
| + new_stmt = gimple_build_assign (gimple_call_lhs (stmt), expr);
|
| move_ssa_defining_stmt_for_defs (new_stmt, stmt);
|
| gsi_replace (gsi, new_stmt, false);
|
| }
|
| @@ -513,7 +311,7 @@ stmt_may_generate_copy (gimple stmt)
|
| return false;
|
|
|
| /* Statements with loads and/or stores will never generate a useful copy. */
|
| - if (!ZERO_SSA_OPERANDS (stmt, SSA_OP_ALL_VIRTUALS))
|
| + if (gimple_vuse (stmt))
|
| return false;
|
|
|
| /* Otherwise, the only statements that generate useful copies are
|
| @@ -554,7 +352,7 @@ get_last_copy_of (tree var)
|
| /* Traverse COPY_OF starting at VAR until we get to the last
|
| link in the chain. Since it is possible to have cycles in PHI
|
| nodes, the copy-of chain may also contain cycles.
|
| -
|
| +
|
| To avoid infinite loops and to avoid traversing lengthy copy-of
|
| chains, we artificially limit the maximum number of chains we are
|
| willing to traverse.
|
| @@ -593,7 +391,7 @@ set_copy_of_val (tree dest, tree first)
|
| {
|
| unsigned int dest_ver = SSA_NAME_VERSION (dest);
|
| tree old_first, old_last, new_last;
|
| -
|
| +
|
| /* Set FIRST to be the first link in COPY_OF[DEST]. If that
|
| changed, return true. */
|
| old_first = copy_of[dest_ver].value;
|
| @@ -633,11 +431,11 @@ dump_copy_of (FILE *file, tree var)
|
|
|
| if (TREE_CODE (var) != SSA_NAME)
|
| return;
|
| -
|
| +
|
| visited = sbitmap_alloc (num_ssa_names);
|
| sbitmap_zero (visited);
|
| SET_BIT (visited, SSA_NAME_VERSION (var));
|
| -
|
| +
|
| fprintf (file, " copy-of chain: ");
|
|
|
| val = var;
|
| @@ -661,7 +459,7 @@ dump_copy_of (FILE *file, tree var)
|
| fprintf (file, "[COPY]");
|
| else
|
| fprintf (file, "[NOT A COPY]");
|
| -
|
| +
|
| sbitmap_free (visited);
|
| }
|
|
|
| @@ -680,7 +478,7 @@ copy_prop_visit_assignment (gimple stmt, tree *result_p)
|
|
|
| lhs = gimple_assign_lhs (stmt);
|
| rhs = gimple_assign_rhs1 (stmt);
|
| -
|
| +
|
|
|
| gcc_assert (gimple_assign_rhs_code (stmt) == SSA_NAME);
|
|
|
| @@ -697,7 +495,7 @@ copy_prop_visit_assignment (gimple stmt, tree *result_p)
|
| copy of RHS's value, not of RHS itself. This avoids keeping
|
| unnecessary copy-of chains (assignments cannot be in a cycle
|
| like PHI nodes), speeding up the propagation process.
|
| - This is different from what we do in copy_prop_visit_phi_node.
|
| + This is different from what we do in copy_prop_visit_phi_node.
|
| In those cases, we are interested in the copy-of chains. */
|
| *result_p = lhs;
|
| if (set_copy_of_val (*result_p, rhs_val->value))
|
| @@ -718,6 +516,7 @@ static enum ssa_prop_result
|
| copy_prop_visit_cond_stmt (gimple stmt, edge *taken_edge_p)
|
| {
|
| enum ssa_prop_result retval = SSA_PROP_VARYING;
|
| + location_t loc = gimple_location (stmt);
|
|
|
| tree op0 = gimple_cond_lhs (stmt);
|
| tree op1 = gimple_cond_rhs (stmt);
|
| @@ -741,7 +540,7 @@ copy_prop_visit_cond_stmt (gimple stmt, edge *taken_edge_p)
|
| the same SSA_NAME on both sides of a comparison operator. */
|
| if (op0 == op1)
|
| {
|
| - tree folded_cond = fold_binary (gimple_cond_code (stmt),
|
| + tree folded_cond = fold_binary_loc (loc, gimple_cond_code (stmt),
|
| boolean_type_node, op0, op1);
|
| if (folded_cond)
|
| {
|
| @@ -864,8 +663,10 @@ copy_prop_visit_phi_node (gimple phi)
|
| Otherwise, this may move loop variant variables outside of
|
| their loops and prevent coalescing opportunities. If the
|
| value was loop invariant, it will be hoisted by LICM and
|
| - exposed for copy propagation. */
|
| - if (loop_depth_of_name (arg) > loop_depth_of_name (lhs))
|
| + exposed for copy propagation. Not a problem for virtual
|
| + operands though. */
|
| + if (is_gimple_reg (lhs)
|
| + && loop_depth_of_name (arg) > loop_depth_of_name (lhs))
|
| {
|
| phi_val.value = lhs;
|
| break;
|
| @@ -892,7 +693,7 @@ copy_prop_visit_phi_node (gimple phi)
|
| memory reference of all the other arguments. */
|
| if (phi_val.value == NULL_TREE)
|
| {
|
| - phi_val.value = arg;
|
| + phi_val.value = arg_val->value ? arg_val->value : arg;
|
| continue;
|
| }
|
|
|
| @@ -949,6 +750,7 @@ init_copy_prop (void)
|
| {
|
| gimple_stmt_iterator si;
|
| int depth = bb->loop_depth;
|
| + bool loop_exit_p = false;
|
|
|
| for (si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si))
|
| {
|
| @@ -986,13 +788,26 @@ init_copy_prop (void)
|
| cached_last_copy_of[SSA_NAME_VERSION (def)] = def;
|
| }
|
|
|
| + /* In loop-closed SSA form do not copy-propagate through
|
| + PHI nodes in blocks with a loop exit edge predecessor. */
|
| + if (current_loops
|
| + && loops_state_satisfies_p (LOOP_CLOSED_SSA))
|
| + {
|
| + edge_iterator ei;
|
| + edge e;
|
| + FOR_EACH_EDGE (e, ei, bb->preds)
|
| + if (loop_exit_edge_p (e->src->loop_father, e))
|
| + loop_exit_p = true;
|
| + }
|
| +
|
| for (si = gsi_start_phis (bb); !gsi_end_p (si); gsi_next (&si))
|
| {
|
| gimple phi = gsi_stmt (si);
|
| tree def;
|
|
|
| def = gimple_phi_result (phi);
|
| - if (!is_gimple_reg (def))
|
| + if (!is_gimple_reg (def)
|
| + || loop_exit_p)
|
| prop_set_simulate_again (phi, false);
|
| else
|
| prop_set_simulate_again (phi, true);
|
| @@ -1014,18 +829,34 @@ fini_copy_prop (void)
|
| {
|
| size_t i;
|
| prop_value_t *tmp;
|
| -
|
| +
|
| /* Set the final copy-of value for each variable by traversing the
|
| copy-of chains. */
|
| tmp = XCNEWVEC (prop_value_t, num_ssa_names);
|
| for (i = 1; i < num_ssa_names; i++)
|
| {
|
| tree var = ssa_name (i);
|
| - if (var && copy_of[i].value && copy_of[i].value != var)
|
| - tmp[i].value = get_last_copy_of (var);
|
| + if (!var
|
| + || !copy_of[i].value
|
| + || copy_of[i].value == var)
|
| + continue;
|
| +
|
| + tmp[i].value = get_last_copy_of (var);
|
| +
|
| + /* In theory the points-to solution of all members of the
|
| + copy chain is their intersection. For now we do not bother
|
| + to compute this but only make sure we do not lose points-to
|
| + information completely by setting the points-to solution
|
| + of the representative to the first solution we find if
|
| + it doesn't have one already. */
|
| + if (tmp[i].value != var
|
| + && POINTER_TYPE_P (TREE_TYPE (var))
|
| + && SSA_NAME_PTR_INFO (var)
|
| + && !SSA_NAME_PTR_INFO (tmp[i].value))
|
| + duplicate_ssa_name_ptr_info (tmp[i].value, SSA_NAME_PTR_INFO (var));
|
| }
|
|
|
| - substitute_and_fold (tmp, false);
|
| + substitute_and_fold (tmp, NULL, true);
|
|
|
| free (cached_last_copy_of);
|
| free (copy_of);
|
| @@ -1036,7 +867,7 @@ fini_copy_prop (void)
|
| /* Main entry point to the copy propagator.
|
|
|
| PHIS_ONLY is true if we should only consider PHI nodes as generating
|
| - copy propagation opportunities.
|
| + copy propagation opportunities.
|
|
|
| The algorithm propagates the value COPY-OF using ssa_propagate. For
|
| every variable X_i, COPY-OF(X_i) indicates which variable is X_i created
|
| @@ -1059,7 +890,7 @@ fini_copy_prop (void)
|
| Visit #2: a_2 is copy-of x_298. Value changed.
|
| Visit #3: a_5 is copy-of x_298. Value changed.
|
| Visit #4: x_1 is copy-of x_298. Stable state reached.
|
| -
|
| +
|
| When visiting PHI nodes, we only consider arguments that flow
|
| through edges marked executable by the propagation engine. So,
|
| when visiting statement #2 for the first time, we will only look at
|
| @@ -1096,7 +927,7 @@ fini_copy_prop (void)
|
|
|
| 1 x_54 = PHI <x_53, x_52>
|
| 2 x_53 = PHI <x_898, x_54>
|
| -
|
| +
|
| Visit #1: x_54 is copy-of x_53 (because x_52 is copy-of x_53)
|
| Visit #2: x_53 is copy-of x_898 (because x_54 is a copy of x_53,
|
| so it is considered irrelevant
|
| @@ -1113,7 +944,7 @@ fini_copy_prop (void)
|
| same variable. So, as long as their copy-of chains overlap, we
|
| know that they will be a copy of the same variable, regardless of
|
| which variable that may be).
|
| -
|
| +
|
| Propagation would then proceed as follows (the notation a -> b
|
| means that a is a copy-of b):
|
|
|
|
|