undefined behavior in value_range::equiv_add()?
Jeff Law
law@redhat.com
Wed Jun 5 21:12:00 GMT 2019
On 6/4/19 9:04 AM, Richard Biener wrote:
> On Tue, Jun 4, 2019 at 3:40 PM Jeff Law <law@redhat.com> wrote:
>>
>> On 6/4/19 5:23 AM, Richard Biener wrote:
>>> On Tue, Jun 4, 2019 at 12:30 AM Jeff Law <law@redhat.com> wrote:
>>>>
>>>> On 6/3/19 7:13 AM, Aldy Hernandez wrote:
>>>>> On 5/31/19 5:00 AM, Richard Biener wrote:
>>>>>> On Fri, May 31, 2019 at 2:27 AM Jeff Law <law@redhat.com>
>>>>>> wrote:
>>>>>>>
>>>>>>> On 5/29/19 10:20 AM, Aldy Hernandez wrote:
>>>>>>>> On 5/29/19 12:12 PM, Jeff Law wrote:
>>>>>>>>> On 5/29/19 9:58 AM, Aldy Hernandez wrote:
>>>>>>>>>> On 5/29/19 9:24 AM, Richard Biener wrote:
>>>>>>>>>>> On Wed, May 29, 2019 at 2:18 PM Aldy Hernandez
>>>>>>>>>>> <aldyh@redhat.com> wrote:
>>>>>>>>>>>>
>>>>>>>>>>>> As per the API, and the original documentation
>>>>>>>>>>>> to value_range, VR_UNDEFINED and VR_VARYING
>>>>>>>>>>>> should never have equivalences. However,
>>>>>>>>>>>> equiv_add is tacking on equivalences blindly,
>>>>>>>>>>>> and there are various regressions that happen
>>>>>>>>>>>> if I fix this oversight.
>>>>>>>>>>>>
>>>>>>>>>>>> void value_range::equiv_add (const_tree var,
>>>>>>>>>>>> const value_range *var_vr, bitmap_obstack
>>>>>>>>>>>> *obstack) { if (!m_equiv) m_equiv =
>>>>>>>>>>>> BITMAP_ALLOC (obstack); unsigned ver =
>>>>>>>>>>>> SSA_NAME_VERSION (var); bitmap_set_bit
>>>>>>>>>>>> (m_equiv, ver); if (var_vr && var_vr->m_equiv)
>>>>>>>>>>>> bitmap_ior_into (m_equiv, var_vr->m_equiv); }
>>>>>>>>>>>>
>>>>>>>>>>>> Is this a bug in the documentation / API, or is
>>>>>>>>>>>> equiv_add incorrect and we should fix the
>>>>>>>>>>>> fall-out elsewhere?
>>>>>>>>>>>
>>>>>>>>>>> I think this must have been crept in during the
>>>>>>>>>>> classification. If you go back to say GCC 7 you
>>>>>>>>>>> shouldn't see value-ranges with UNDEFINED/VARYING
>>>>>>>>>>> state in the lattice that have equivalences.
>>>>>>>>>>>
>>>>>>>>>>> It may not be easy to avoid with the new classy
>>>>>>>>>>> interface but we're certainly not tacking on them
>>>>>>>>>>> "blindly". At least we're not supposed to. As
>>>>>>>>>>> usual the intermediate state might be "broken"
>>>>>>>>>>> but intermediateness is not sth the new class
>>>>>>>>>>> "likes".
>>>>>>>>>>
>>>>>>>>>> It looks like extract_range_from_stmt (by virtue
>>>>>>>>>> of vrp_visit_assignment_or_call and then
>>>>>>>>>> extract_range_from_ssa_name) returns one of these
>>>>>>>>>> intermediate ranges. It would seem to me that an
>>>>>>>>>> outward looking API method like
>>>>>>>>>> vr_values::extract_range_from_stmt shouldn't be
>>>>>>>>>> returning inconsistent ranges. Or are there no
>>>>>>>>>> guarantees for value_ranges from within all of
>>>>>>>>>> vr_values?
>>>>>>>>> ISTM that if we have an implementation constraint
>>>>>>>>> that says a VR_VARYING or VR_UNDEFINED range can't
>>>>>>>>> have equivalences, then we need to honor that at the
>>>>>>>>> minimum for anything returned by an external API.
>>>>>>>>> Returning an inconsistent state is bad. I'd even
>>>>>>>>> state that we should try damn hard to avoid it in
>>>>>>>>> internal APIs as well.
>>>>>>>>
>>>>>>>> Agreed * 2.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Perhaps I should give a little background. As part
>>>>>>>>>> of your value_range_base re-factoring last year,
>>>>>>>>>> you mentioned that you didn't split out intersect
>>>>>>>>>> like you did union because of time or oversight.
>>>>>>>>>> I have code to implement intersect (attached), for
>>>>>>>>>> which I've noticed that I must leave equivalences
>>>>>>>>>> intact, even when transitioning to VR_UNDEFINED:
>>>>>>>>>>
>>>>>>>>>> [from the attached patch] + /* If THIS is varying
>>>>>>>>>> we want to pick up equivalences from OTHER. +
>>>>>>>>>> Just special-case this here rather than trying to
>>>>>>>>>> fixup after the + fact. */ + if
>>>>>>>>>> (this->varying_p ()) + this->deep_copy (other);
>>>>>>>>>> + else if (this->undefined_p ()) + /* ?? Leave
>>>>>>>>>> any equivalences already present in an undefined. +
>>>>>>>>>> This is technically not allowed, but we may get an
>>>>>>>>>> in-flight + value_range in an intermediate
>>>>>>>>>> state. */
>>>>>>>>> Where/when does this happen?
>>>>>>>>
>>>>>>>> The above snippet is not currently in mainline. It's
>>>>>>>> in the patch I'm proposing to clean up intersect. It's
>>>>>>>> just that while cleaning up intersect I noticed that if
>>>>>>>> we keep to the value_range API, we end up clobbering an
>>>>>>>> equivalence to a VR_UNDEFINED that we depend up further
>>>>>>>> up the call chain.
>>>>>>>>
>>>>>>>> The reason it doesn't happen in mainline is because
>>>>>>>> intersect_helper bails early on an undefined, thus
>>>>>>>> leaving the problematic equivalence intact.
>>>>>>>>
>>>>>>>> You can see it in mainline though, with the following
>>>>>>>> testcase:
>>>>>>>>
>>>>>>>> int f(int x) { if (x != 0 && x != 1) return -2;
>>>>>>>>
>>>>>>>> return !x; }
>>>>>>>>
>>>>>>>> Break in evrp_range_analyzer::record_ranges_from_stmt()
>>>>>>>> and see that the call to extract_range_from_stmt()
>>>>>>>> returns a VR of undefined *WITH* equivalences:
>>>>>>>>
>>>>>>>> vr_values->extract_range_from_stmt (stmt, &taken_edge,
>>>>>>>> &output, &vr);
>>>>>>>>
>>>>>>>> This VR is later fed to update_value_range() and
>>>>>>>> ultimately intersect(), which in mainline, leaves the
>>>>>>>> equivalences alone, but IMO should respect that API and
>>>>>>>> nuke them.
>>>>>>> So I think this is coming from
>>>>>>> extract_range_from_ssa_name:
>>>>>>>
>>>>>>>> void vr_values::extract_range_from_ssa_name
>>>>>>>> (value_range *vr, tree var) { value_range *var_vr =
>>>>>>>> get_value_range (var);
>>>>>>>>
>>>>>>>> if (!var_vr->varying_p ()) vr->deep_copy (var_vr);
>>>>>>>> else vr->set (var);
>>>>>>>>
>>>>>>>> vr->equiv_add (var, get_value_range (var),
>>>>>>>> &vrp_equiv_obstack); }
>>>>>>>
>>>>>>> Where we blindly add VAR to the equiv bitmap in the
>>>>>>> range.
>>>>>>>
>>>>>>> AFAICT gcc-7 has equivalent code, it's just not inside
>>>>>>> the class.
>>>>>>>
>>>>>>> I don't know what the fallout might be, but we could
>>>>>>> conditionalize the call to add_equivalence to avoid the
>>>>>>> inconsistent state.
>>>>>>
>>>>>> Well, the issue is that the equivalence _is_ there. So
>>>>>> above we know that we never end up with VARYING but the
>>>>>> deep_copy can bring over UNDEFINED to us. I guess we
>>>>>> _could_ elide the equiv adding then. OTOH the equivalence
>>>>>> does look useful for predicate simplification which IIRC
>>>>>> doesn't treat UNDEFINED != x in a useful way. Which would
>>>>>> mean allowing equivalences on UNDEFINED. That somewhat
>>>>>> makes sense since UNDEFINED is bottom-most in the lattice
>>>>>> and one up (CONSTANT) we have equivalences while just on
>>>>>> topmost (VARYING) we do not.
>>>>>>
>>>>>> That said, I never liked equivalences - they are an odd
>>>>>> way to represent ranges intersected from multiple ranges
>>>>>> thus a way out of our too simplistic range representation.
>>>>>>
>>>>>> So lets fix this in a way avoiding testsuite fallout. But
>>>>>> please not with special hacks in consumers. Does guariding
>>>>>> the above equiv_add with !vr->undefined_p () fix things?
>>>>>
>>>>> Agreed. I never suggested we add special hacks to intersect.
>>>>> The two-liner was only to explain why equivalences in
>>>>> varying/undefined currently matter in intersect.
>>>>>
>>>>> Guarding the equiv_add causes regressions. For example, for
>>>>> this simple testcase we incorrectly get rid of the final
>>>>> PHI:
>>>>>
>>>>> int f(int x) { if (x != 0 && x != 1) return -2;
>>>>>
>>>>> return !x; }
>>>>>
>>>>> In the evrp dump we now see:
>>>>>
>>>>> Visiting PHI node: _3 = PHI <-2(3), _5(4)> Argument #0 (3 ->
>>>>> 5 executable) -2: int [-2, -2] Argument #1 (4 -> 5
>>>>> executable) _5: UNDEFINED Meeting int [-2, -2] and UNDEFINED
>>>>> to int [-2, -2]
>>>>>
>>>>> whereas before the change, argument #1 was:
>>>>>
>>>>> Argument #1 (4 -> 5 executable) _5: int [_5, _5]
>>>>>
>>>>> and the meeting result was VARYING.
>>>>>
>>>>> I *think* this starts somewhere up the call chain in
>>>>> update_value_range, which as I've described earlier, will no
>>>>> longer make a difference between UNDEFINED and UNDEFINED +
>>>>> equivalence. This causes that when transitioning from
>>>>> UNDEFINED to UNDEFINED + equivalence, we actually transition
>>>>> to VARYING:
>>>>>
>>>>> if (is_new) { if (old_vr->varying_p ()) { new_vr->set_varying
>>>>> (); is_new = false; } else if (new_vr->undefined_p ()) {
>>>>> old_vr->set_varying (); new_vr->set_varying (); return true;
>>>>> }
>>>>>
>>>>> Could someone better versed in this take a peek, please?
>>>>> It's just a matter of applying the attached one-liner, and
>>>>> looking at "Visiting PHI" in the evrp dump.
>>>> As I mentioned in IRC. I know what's going on here and see a
>>>> path to fix it. Hoping to get a patch into my tester
>>>> overnight, but life seems to be getting in the way.
>>>
>>> It's of course a latent issue. One I fixed in DOMs use of the
>>> EVRP machinery. The following fixes it in a conservative way:
>>>
>>> Index: gcc/gimple-ssa-evrp.c
>>> ===================================================================
>>>
>>>
--- gcc/gimple-ssa-evrp.c (revision 271904)
>>> +++ gcc/gimple-ssa-evrp.c (working copy) @@ -175,6 +175,8
>>> @@ evrp_dom_walker::before_dom_children (ba
>>>
>>> /* Try folding stmts with the VR discovered. */ bool did_replace
>>> = evrp_folder.replace_uses_in (stmt); + gimple_stmt_iterator
>>> prev_gsi = gsi; + gsi_prev (&prev_gsi); if (fold_stmt (&gsi,
>>> follow_single_use_edges) || did_replace) { @@ -191,6 +193,17 @@
>>> evrp_dom_walker::before_dom_children (ba
>>>
>>> if (did_replace) { + /* If we wound up generating new
>>> stmts during folding + drop all their defs to
>>> VARYING. + ??? Properly process them. */ +
>>> if (gsi_end_p (prev_gsi)) + prev_gsi = gsi_start_bb
>>> (bb); + while (gsi_stmt (prev_gsi) != gsi_stmt (gsi)) +
>>> { + evrp_range_analyzer.get_vr_values () +
>>> ->set_defs_to_varying (gsi_stmt (prev_gsi)); +
>>> gsi_next (&prev_gsi); + } /* If we cleaned up EH
>>> information from the statement, remove EH edges. */ if
>>> (maybe_clean_or_replace_eh_stmt (old_stmt, stmt))
>>>
>>> the more "proper" fix requires keeping track of stmts already
>>> processed (you don't want to re-process stmts, I ran into
>>> "issues" when doing that for the same issue in DOM). For DOM I
>>> used the visited flag (see
>>> dom_opt_dom_walker::before_dom_children).
>>>
>>> You might want to copy that handling.
>>>
>>> Just give me a note if you want to work on that, otherwise I'll
>>> try to queue it for somewhen this week.Umm, I think you're
>>> working around a problem in the simplifier code
>> which creates SSA_NAMEs, leaving them in an VR_UNDEFINED state.
>
> But the simplifier is / can be just fold_stmt () - it doesn't know
> it has to populate a value-range lattice. But yes, the VRP specific
> simplifier likely has the same issue in the EVRP context (in SSA VRP
> all "new" SSA names are beyond the static allocated array and get
> VARYING automatically). So I think handling the "new stmts" in the
> propagator is correct?
I didn't realize we had so many calls to make_ssa_name in gimple-fold.
I was primarily concerned with the ones in the evrp simplification
engine which are easy to fix in-place. Looking at gimple-fold.c I agree
we do need to address the walking problem.
I'm not sure we can drop to varying though -- my first twiddle of this
did precisely that, but that'll likely regress vrp47 where we know the
resultant range after simplification is just [0,1].
Ideally we wouldn't have the simplifiers creating new names or we'd have
a more robust mechanism for identifying when we've created a new name
and doing the right thing. I suspect whatever we do here right now is
going to be a bandaid and as long as we keep creating new names in the
simplifier we're likely going to be coming back and applying more bandaids.
jeff
More information about the Gcc-patches
mailing list