On Thu, 31 Mar 2022, Richard Sandiford wrote: > Richard Biener <rguent...@suse.de> writes: > > The following makes sure that when we build the versioning condition > > for vectorization including the cost model check, we check for the > > cost model and branch over other versioning checks. That is what > > the cost modeling assumes, since the cost model check is the only > > one accounted for in the scalar outside cost. Currently we emit > > all checks as straight-line code combined with bitwise ops which > > can result in surprising ordering of checks in the final assembly. > > Yeah, this had bugged me too, and meant that we made some bad > decisions in some of the local benchmarks we use. Was just afraid > to poke at it, since it seemed like a deliberate decision. :-)
I guess it was rather giving up because of the way our loop-versioning infrastructure works ... > > Since loop_version accepts only a single versioning condition > > the splitting is done after the fact. > > > > The result is a 1.5% speedup of 416.gamess on x86_64 when compiling > > with -Ofast and tuning for generic or skylake. That's not enough > > to recover from the slowdown when vectorizing but it now cuts off > > the expensive alias versioning test. > > > > Bootstrapped and tested on x86_64-unknown-linux-gnu. > > > > OK for trunk? > > > > For the rest of the regression my plan is to somehow factor in > > the evolution of the number of iterations in the outer loop > > (which is {1, +, 1}) to somehow bump the static profitability > > estimate and together with the "cheap" cost model check never > > execute the vectorized version (well, it is actually never executed, > > but only because the alias check fails). > > > > Thanks, > > Richard. > > > > 2022-03-21 Richard Biener <rguent...@suse.de> > > > > PR tree-optimization/104912 > > * tree-vect-loop-manip.cc (vect_loop_versioning): Split > > the cost model check to a separate BB to make sure it is > > checked first and not combined with other version checks. > > --- > > gcc/tree-vect-loop-manip.cc | 53 ++++++++++++++++++++++++++++++++++--- > > 1 file changed, 50 insertions(+), 3 deletions(-) > > > > diff --git a/gcc/tree-vect-loop-manip.cc b/gcc/tree-vect-loop-manip.cc > > index a7bbc916bbc..8ef333eb31b 100644 > > --- a/gcc/tree-vect-loop-manip.cc > > +++ b/gcc/tree-vect-loop-manip.cc > > @@ -3445,13 +3445,28 @@ vect_loop_versioning (loop_vec_info loop_vinfo, > > cond_expr = expr; > > } > > > > + tree cost_name = NULL_TREE; > > + if (cond_expr > > + && !integer_truep (cond_expr) > > + && (version_niter > > + || version_align > > + || version_alias > > + || version_simd_if_cond)) > > + cost_name = cond_expr = force_gimple_operand_1 (unshare_expr > > (cond_expr), > > + &cond_expr_stmt_list, > > + is_gimple_val, NULL_TREE); > > + > > if (version_niter) > > vect_create_cond_for_niters_checks (loop_vinfo, &cond_expr); > > > > if (cond_expr) > > - cond_expr = force_gimple_operand_1 (unshare_expr (cond_expr), > > - &cond_expr_stmt_list, > > - is_gimple_condexpr, NULL_TREE); > > + { > > + gimple_seq tem = NULL; > > + cond_expr = force_gimple_operand_1 (unshare_expr (cond_expr), > > + &tem, > > + is_gimple_condexpr, NULL_TREE); > > + gimple_seq_add_seq (&cond_expr_stmt_list, tem); > > + } > > > > if (version_align) > > vect_create_cond_for_align_checks (loop_vinfo, &cond_expr, > > @@ -3654,6 +3669,38 @@ vect_loop_versioning (loop_vec_info loop_vinfo, > > update_ssa (TODO_update_ssa); > > } > > > > + /* Split the cost model check off to a separate BB. Costing assumes > > + this is the only thing we perform when we enter the scalar loop. */ > > Maybe “…from a failed cost decision” or something? Might sounds out > of context like it applied more generally. Fixed. > > + if (cost_name) > > + { > > + gimple *def = SSA_NAME_DEF_STMT (cost_name); > > I realise it should only happen very rarely if at all, but is it > absolutely guaranteed that the cost condition doesn't fold to a > constant? OK, better be safe than sorry - I added a SSA_NAME check. > > + /* All uses of the cost check are 'true' after the check we > > + are going to insert. */ > > + replace_uses_by (cost_name, boolean_true_node); > > + /* And we're going to build the new single use of it. */ > > + gcond *cond = gimple_build_cond (NE_EXPR, cost_name, > > boolean_false_node, > > + NULL_TREE, NULL_TREE); > > + edge e = split_block (gimple_bb (def), def); > > + gimple_stmt_iterator gsi = gsi_for_stmt (def); > > + gsi_insert_after (&gsi, cond, GSI_NEW_STMT); > > + edge true_e, false_e; > > + extract_true_false_edges_from_block (e->dest, &true_e, &false_e); > > + e->flags &= ~EDGE_FALLTHRU; > > + e->flags |= EDGE_TRUE_VALUE; > > + edge e2 = make_edge (e->src, false_e->dest, EDGE_FALSE_VALUE); > > + e->probability = prob; > > + e2->probability = prob.invert (); > > Is reusing prob the right thing to do? Wouldn't the path to the vector > loop end up with a probability of PROB^2? We're statically using profile_probability::likely () for the vector path. But you are right, and we scale loop frequencies with prop. So when we replace if () { with-prob A } with if () if () { with-prob A } then we probably want both true edges to have a probability of sqrt (prob) and the false edges sqrt (~prob)? It _looks_ like profile_probability::split (prob) would do the trick. Here we replace if (X) with if (X && Y) with overall likely() probability. So something like (incremental): @@ -3446,15 +3446,21 @@ vect_loop_versioning (loop_vec_info loop_vinfo, } tree cost_name = NULL_TREE; + profile_probability prob2 = profile_probability::uninitialized (); if (cond_expr && !integer_truep (cond_expr) && (version_niter || version_align || version_alias || version_simd_if_cond)) - cost_name = cond_expr = force_gimple_operand_1 (unshare_expr (cond_expr), - &cond_expr_stmt_list, - is_gimple_val, NULL_TREE); + { + cost_name = cond_expr = force_gimple_operand_1 (unshare_expr (cond_expr), + &cond_expr_stmt_list, + is_gimple_val, NULL_TREE); + /* Split prob () into two so that the overall probability of passing + both the cost-model and versioning checks is the orig prob. */ + prob2 = prob.split (prob); + } if (version_niter) vect_create_cond_for_niters_checks (loop_vinfo, &cond_expr); @@ -3688,8 +3695,8 @@ vect_loop_versioning (loop_vec_info loop_vinfo, e->flags &= ~EDGE_FALLTHRU; e->flags |= EDGE_TRUE_VALUE; edge e2 = make_edge (e->src, false_e->dest, EDGE_FALSE_VALUE); - e->probability = prob; - e2->probability = prob.invert (); + e->probability = prob2; + e2->probability = prob2.invert (); set_immediate_dominator (CDI_DOMINATORS, false_e->dest, e->src); auto_vec<basic_block, 3> adj; for (basic_block son = first_dom_son (CDI_DOMINATORS, e->dest); might do the trick? Honza? > > + set_immediate_dominator (CDI_DOMINATORS, false_e->dest, e->src); > > + auto_vec<basic_block, 3> adj; > > + for (basic_block son = first_dom_son (CDI_DOMINATORS, e->dest); > > + son; > > + son = next_dom_son (CDI_DOMINATORS, son)) > > + if (EDGE_COUNT (son->preds) > 1) > > + adj.safe_push (son); > > + for (auto son : adj) > > + set_immediate_dominator (CDI_DOMINATORS, son, e->src); > > Seems unfortunate that so much bespoke code is needed to do an operation > like this, but that's not a very constructive comment, sorry. :-) Yeah :/ > Anyway, LGTM, but I don't think I'd really be able to spot problems. Let's see what Honza thinks about the probability issue. Thanks, Richard. > Thanks, > Richard > > > + } > > + > > if (version_niter) > > { > > /* The versioned loop could be infinite, we need to clear existing > -- Richard Biener <rguent...@suse.de> SUSE Software Solutions Germany GmbH, Maxfeldstrasse 5, 90409 Nuernberg, Germany; GF: Ivo Totev; HRB 36809 (AG Nuernberg)