Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Full support for exception sets in value numbering.#20129

Merged
briansull merged 2 commits intodotnet:masterfrom
briansull:vn-add-exception-sets
Oct 9, 2018
Merged

Full support for exception sets in value numbering.#20129
briansull merged 2 commits intodotnet:masterfrom
briansull:vn-add-exception-sets

Conversation

@briansull
Copy link

@briansull briansull commented Sep 25, 2018

New method that add exception sets:
fgValueNumberAddExceptionSet

  • vnAddExceptionSetForIndirection
  • vnAddExceptionSetForDivision
  • vnAddExceptionSetForOverflow
  • vnAddExceptionSetForCkFinite

Refactoring work added methods:
VNEvalShouldFold - method to decide if constant folding should be performed
EvalUsingMathIdentity - Uses math identities to simplify value number exoressions
Renamed fgValueNumberHelperMethVNFunc to fgValueNumberJitHelperMethodVNFunc

Added standard method header comments for all five overloads of VNForFunc

if (GetVNFunc0Map()->Lookup(func, &res))
// Have we already assigned a ValueNum for 'func' ?
//
if (GetVNFunc0Map()->Lookup(func, &resultVN))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it could more easy to read if if condition would be negated and fused with else statement.

#endif

//----------------------------------------------------------------------------------------
// VNForFunc-2 - Returns the ValueNum associated with 'func'('arg0VN','arg1VN')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From docs perspective VNForFunc-2 could be misleading since it could indicate function name. In reality func name is VNForFunc but is defined as different overload what cen be immediately seen from list of args

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that what I have done with the number suffix -1 -2 -3 is fine here.
As a function name can never contain a "-" symbol.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@briansull
I am raising this problem bcs I have started working on automatic generation of docs for native code using doxygen. First attempts look promising. Current documentation format is not supported by doxygen and good quality docs generation would require applying transforms which most probably could be implemented either by using clang-doxygen build or by sophisticated regex. Having to recognize invalid function name and match it with signature in code could be an additional problem preventing docs generation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does doxygen differenentiate between function overloads with the same name?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doxygen uses either its own fully functional C++ parser or even better integrates with Clang and relies on Clang parser for C/C++ code understanding.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function overloads are displayed in a similar way as C# overloads are displayed in C# docs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the full prototype with all of the types for all arguments, right?

Write(String, Object, Object, Object, Object)
Write(String, Object, Object, Object)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly

{
// We can fold the expression, but we don't want to fold
// when the expression will always throw an exception
shouldFold = VNEvalShouldFold(typ, func, arg0VN, arg1VN);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have analyzed similar problem while writing folding check in importer and it seems that there are 2 possible scenarios with 2 possible decisions:

  1. Expression tree is much bigger in comparison to tree with exception -> decision: it is better to fold.
  2. Expression tree is comparable or smaller than exception tree -> decision: do not fold.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the reason that VNEvalShouldFold returns false is that the folding of some expressions would turn them into an unconditional throw. When the expression unconditionally throws it does not produce a useful "normal" value number and using a dummy value such a zero causes problems because we think that we can use that zero in the CSE phase.

It is simpler to detect such cases and not try to fold them in value numbering.
So we do not fold (5 / 0) or (MIN_INT / -1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Thanks for explanation

}
break;
//----------------------------------------------------------------------------------------
// VNForFunc-3 - Returns the ValueNum associated with 'func'('arg0VN','arg1VN','arg2VN')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same docs problem as above: function name has not changed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that what I have done with the number suffix -1 -2 -3 is fine here.
As a function name can never contain a "-" symbol.

VNFunc fgValueNumberHelperMethVNFunc(CorInfoHelpFunc helpFunc);
// Requires that "helpFunc" is one of the pure Jit Helper methods.
// Returns the corresponding VNFunc to use for value numbering
VNFunc fgValueNumberJitHelperMethodVNFunc(CorInfoHelpFunc helpFunc);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency with existing code can be a good thing but maybe it's time to stop using fg for everything. All these new functions should have a vn prefix.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know, I can do that in an upcoming changeset

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I will change the names of these methods to start with vnAddExceptionSet

ValueNumFuncDef(ArithmeticExc, 2, false, false, false) // E.g., for signed its, MinInt / -1.
ValueNumFuncDef(OverflowExc, 1, false, false, false) // Integer overflow check. Args: 0: expression value, throws when it overflows
ValueNumFuncDef(ArithmeticExc, 2, false, false, false) // Arithmetic exception check, ckfinite and integer division overflow, Args: 0: expression value,
ValueNumFuncDef(OverflowExc, 1, false, false, false) // Integer overflow check. used for chjecked add,sub and mul Args: 0: expression value, throws when it overflows
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for checked add

ValueNum resultVN = NoVN;

// We may create one of these in the switch below.
ValueNum ZeroVN;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be moved inside the switch

if (typ == TYP_BYREF) // We don't want/need to optimize a zero byref
{
return res;
return NoVN;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return resultVN for consistency?

ZeroVN = VNZeroForType(typ);
if (arg1VN == ZeroVN)
{
resultVN = arg0VN;
Copy link

@mikedn mikedn Sep 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 shifted by anything gives 0

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I will add

// This identity does not apply for floating point (when x == -0.0)
// (x + 0) == (0 + x) => x
ZeroVN = VNZeroForType(typ);
if (VNIsEqual(arg0VN, ZeroVN))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be clearer to just wrap the whole thing in an if (!varTypeIsFloating(typ)) instead of relying on VNIsEqual.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will rework this area to be clearer

// First we'll record the exeception set for the rhs and
// later we will union in the exeception set for the lhs
//
ValueNum vnExcSet = ValueNumStore::VNForEmptyExcSet();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't be better to change VNUnpackExc to always initialize pvnx rather than doing this all other the place?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will think about that

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #20185

if (vnStore->IsVNConstant(funcCAttr.m_args[1]) &&
varTypeIsIntegral(vnStore->TypeOfVN(funcCAttr.m_args[1])))
{
offset += vnStore->CoercedConstantValue<ssize_t>(funcCAttr.m_args[1]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't understand this. Looks like the total offset is extracted from both liberal and conservative VNs?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will double check

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I redid this area and fixed it. Thanks


if (typ == TYP_INT)
{
INT32 kVal;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move inside ifs below. There are more cases below.

ValueNumPair vnpTreeExc = ValueNumStore::VNPForEmptyExcSet();
ValueNumPair vnpDivZeroExc = ValueNumStore::VNPForEmptyExcSet();
ValueNumPair vnpArithmExc = ValueNumStore::VNPForEmptyExcSet();
ValueNumPair newExcSet;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move it below, where it is initialized.

//
ValueNumPair vnpTreeNorm;
ValueNumPair vnpTreeExc = ValueNumStore::VNPForEmptyExcSet();
ValueNumPair newExcSet;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to initialization

//
ValueNumPair vnpTreeNorm;
ValueNumPair vnpTreeExc = ValueNumStore::VNPForEmptyExcSet();
ValueNumPair newExcSet;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to initialization

case GT_ARR_LENGTH: // Implicit null check.
case GT_IND: // Implicit null check.
case GT_NULLCHECK: // Explicit null check.
fgValueNumberAddExceptionSetForIndirection(tree);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About other indir-like nodes (atomic ops, HW intrinsics)?

Also, there's a GTF_IND_NONFAULTING flags, should that be tested here?

Copy link
Author

@briansull briansull Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If those nodes return true for OperMayThrow then they must be added here. (otherwise we assert in Debug)
If they don't, then it is a nop to add code to handle them here.

Copy link
Author

@briansull briansull Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OperMayThrow checks for the GTF_IND_NONFAULTING flag, so we don't need to handle it here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If those nodes return true for OperMayThrow then they must be added here

GT_HWIntrinsic can return true if it's a load/store. I don't see atomic ops in OperMayThrow but they probably should be there too.

@briansull
Copy link
Author

briansull commented Sep 28, 2018

@dotnet/jit-contrib PTAL
@AndyAyersMS PTAL

I have investigated the diffs and they are very few and are a very small.
They are caused when a CSE def has no NullPtrExc and the CSE use has a NullPtrExc.

@briansull
Copy link
Author

briansull commented Sep 28, 2018

Fixes #8648 and #10111

@briansull briansull force-pushed the vn-add-exception-sets branch from 5a8d9f3 to 1fc0560 Compare September 28, 2018 18:35
{
unsigned int cseBit = genCSEnum2bit(tree->gtCSEnum);
CSEdsc* desc = optCSEfindDsc(tree->gtCSEnum);
unsigned CSEnum = GET_CSE_INDEX(tree->gtCSEnum);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like somewhere in here (or perhaps in the method header) you should outline the general approach: the intention is to determine for each candidate if the intersection of all the exceptions provided by the defs is a superset of the union of the exceptions expected by the (qualified) uses.

The algorithm uncovers the defs and uses gradually and so incrementally building up both the intersection def set and the union use set.

It would be good to make sure we have (or can point at) test cases that show the various cases of this code getting covered.

I am still thinking about it but wonder if there is some implicit priority in choosing uses that should be thought about more carefully. Suppose we first find a def with exceptions 1, 2, 3 and then a use with exceptions 1, 2, 3, and then a def with exceptions 2, 3 and then a use with exceptions 2, 3. By the current algorithm we would not do any CSE here. But we could still CSE the 2,3 use.

So I wonder if instead we should not disqualify any uses until we've seen them all, and then pick the subset of uses that is compatible with what the definitions can provide.

Copy link
Author

@briansull briansull Sep 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically expressions with the same Normal ValueNum generate exactly the same exception sets. There are two way that we can get different exception sets with the same Normal value number.

  1. We used an arithmetic identiity:
    e.g.: (p.a + q.b) * 0 -- The normal value for the expression is set to zero because of the multiply by zero.
    e.g. (p.a - p.a ) -- The normal value for the expression is set to zero because of the subtraction of the same value.

  2. We stored an expression into a LclVar or into Memory and used it later in a CSE:
    e.g. t = p.a; e1=(t + q.b); e2=(p.a+q.b) -- e1 has one NullPtrExc and e2 has two.
    e.g. m.a = p.a; e1=(m.a + q.b); e2=(p.a + q.b) -- e1 and e2 have different exception sets.

The bug cases were due to case 1.
The small code regressions are due to case 2.

I believe that case 2 can be fixed by adding exception sets to the ValueNum that we use to track the Global Heap. After each operation we would update the Global Heap with any new NullPtrExc or other exception sets. Then the CSE uses could also rely upon exception sets tracked by the Global Heap.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added large comment addressing this to the method header

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment -- my main concern was not how this can happen (though it is nice to now have specifics here), but how often it can happen.

As long as it's "rare" that different CSE defs have different exception sets then it doesn't matter much whether you disqualify uses eagerly or wait until you've seen them all, as it is unlikely you'll ever hit the case I describe where you miss CSEing some use because you saw some other more constraining use first.

But maybe it's worth mentioning that the approach you take here may not find the "maximal" set of CSEs in some rare cases.

if (theConservNormVN != desc->defConservNormVN)
{
// This candidate has defs with differing conservative normal VNs, mark it with NoVN
desc->defConservNormVN = ValueNumStore::NoVN; // record the marker for differing VNs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also set NO_CSE and then continue, right?

Copy link
Author

@briansull briansull Sep 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, This is existing code that is there for rangecheck elimation. (I believe)

I renamed the existing field to `defConservNormVN' and maintained its existing behavior.

Here is the old existing field definition in compiler.h
ValueNum defConservativeVN; // if all def occurrences share the same conservative value

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be useful to mention how this can happen and why even if so, the candidate is still a viable CSE.

//
if (desc->defExcSetCurrent !=
theLiberalExcSet) // no update is needed when these are the same VN
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the code reads better if you don't use these end of line comments.

Also it would be nice to try and bubble all the fail/continue cases upwards so that we don't get so deeply nested, eg structure the code as the loop-equivalent of early return style, eg:

  if (something disqualifying 1)
  { 
         continue;
  }

  if (something disqualifying 2)
  { 
         continue;
  }

  // success !

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure exactly you want here. As far as I can tell this isn't a straight forward change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now maybe just move the comments.

I think there may be simpler/cleaner ways to express the code too, but I'm ok leaving it as is for now.

Copy link
Member

@AndyAyersMS AndyAyersMS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't looked at the valuenum.cpp changes yet in much detail.

// use to fetch the same value with no reload, so we can safely propagate that
// conservative VN to this use. This can help range check elimination later on.
cse->gtVNPair.SetConservative(defConservativeVN);
cse->gtVNPair.SetConservative(theConservativeVN);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the existing code that depends upon theConservativeVN getting set to NoVN when there are differing conservative values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now. Thanks.

@briansull briansull force-pushed the vn-add-exception-sets branch from 1fc0560 to 3e5b2a9 Compare October 1, 2018 23:03
New method that add exception sets:
  fgValueNumberAddExceptionSet
  - fgValueNumberAddExceptionSetForIndirection
  - fgValueNumberAddExceptionSetForDivision
  - fgValueNumberAddExceptionSetForOverflow
  - fgValueNumberAddExceptionSetForCkFinite

Refactoring work added methods:
 VNEvalShouldFold - method to decide if constant folding should be performed
 EvalUsingMathIdentity - Uses math identities to simplify value number exoressions
 Renamed fgValueNumberHelperMethVNFunc to fgValueNumberJitHelperMethodVNFunc

Removed the suffixes from the method headers comments
@briansull briansull force-pushed the vn-add-exception-sets branch from 3e5b2a9 to a552cfe Compare October 5, 2018 22:40
@briansull
Copy link
Author

@AndyAyersMS PTAL
@dotnet/jit-contrib PTAL

Copy link
Member

@AndyAyersMS AndyAyersMS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked at valuenum.cpp now too.

@briansull
Copy link
Author

@dotnet-bot retest Windows_NT x64 Release CoreFX Tests

@briansull
Copy link
Author

Fixes #8648

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants