Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect IND(ADDR(LCL_VAR)) folding when types do not match #620

Closed
mikedn opened this issue Dec 6, 2019 · 5 comments
Closed

Incorrect IND(ADDR(LCL_VAR)) folding when types do not match #620

mikedn opened this issue Dec 6, 2019 · 5 comments
Assignees
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI bug
Milestone

Comments

@mikedn
Copy link
Contributor

mikedn commented Dec 6, 2019

I was looking at reimplementing this kind of folding in LocalAddressVisitor and ran into this morph code:

// If the type of the IND (typ) is a "small int", and the type of the local has the
// same width, then we can reduce to just the local variable -- it will be
// correctly normalized, and signed/unsigned differences won't matter.
//
// The below transformation cannot be applied if the local var needs to be normalized on load.
else if (varTypeIsSmall(typ) && (genTypeSize(lvaTable[lclNum].lvType) == genTypeSize(typ)) &&
!lvaTable[lclNum].lvNormalizeOnLoad())
{
tree->gtType = typ = temp->TypeGet();
foldAndReturnTemp = true;
}
else if (!varTypeIsStruct(typ) && (lvaTable[lclNum].lvType == typ) &&

I don't think that "signed/unsigned differences won't matter":

static void Main() => Test(-1, 1);

[MethodImpl(MethodImplOptions.NoInlining)]
static unsafe void Test(short a, int b)
{
    short c = (short)(a * 2);
    int d = *((ushort*)&c) / b;
    Console.WriteLine(d);
}

generates

G_M41262_IG01:
       4883EC28             sub      rsp, 40
       448BC2               mov      r8d, edx
G_M41262_IG02:
       480FBFC1             movsx    rax, cx
       D1E0                 shl      eax, 1
       480FBFC0             movsx    rax, ax
       99                   cdq
       41F7F8               idiv     edx:eax, r8d
       8BC8                 mov      ecx, eax
       E8A4FCFFFF           call     System.Console:WriteLine(int)
       90                   nop
G_M41262_IG03:
       4883C428             add      rsp, 40
       C3                   ret

and prints [edit] -2. The result is obviously incorrect, ushort / 1 can't ever be negative.

category:correctness
theme:morph
skill-level:intermediate
cost:medium

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI untriaged New issue has not been triaged by the area owner labels Dec 6, 2019
@sandreenko sandreenko removed the untriaged New issue has not been triaged by the area owner label Dec 6, 2019
@sandreenko
Copy link
Contributor

@dotnet/jit-contrib

@sandreenko sandreenko added this to the 5.0 milestone Dec 6, 2019
@sandreenko sandreenko added the bug label Dec 6, 2019
@mikedn
Copy link
Contributor Author

mikedn commented Dec 7, 2019

LHS version of the above:

static unsafe void Test(short a, int b)
{
    short c = (short)(a * 2);
    *((ushort*)&c) = (ushort)(a * b);
     Console.WriteLine(c);
}

generates:

480FBFC9             movsx    rcx, cx
0FAFCA               imul     ecx, edx
0FB7C9               movzx    rcx, cx
E8ADFCFFFF           call     System.Console:WriteLine(int)

and prints 65535. This is clearly incorrect as the value of a short variable can't ever be greater than 32767.

Despite the similarity with the RHS version of the bug, this might require a different fix. The pre-morph tree is:

[000013] -A-XG-------              *  ASG       short 
[000012] *------N----              +--*  IND       short 
[000007] ------------              |  \--*  ADDR      long  
[000006] ------------              |     \--*  LCL_VAR   int    V02 loc0         
[000011] ------------              \--*  CAST      int <- ushort <- int
[000010] ------------                 \--*  MUL       int   
[000008] ------------                    +--*  LCL_VAR   short  V00 arg0         
[000009] ------------                    \--*  LCL_VAR   int    V01 arg1         

Note that if the LHS was a LCL_VAR the pre-order morphing of ASG would have inserted a cast to short node on the RHS for "normalize on store". But it's not yet a LCL_VAR and there's also a pre-existing cast to ushort that was imported from IL (BTW why on earth is Roslyn insisting on inserting a narrowing cast before a store that truncates anyway?!)

Post-morph the tree becomes:

[000013] -A--G+------              *  ASG       short 
[000006] D----+-N----              +--*  LCL_VAR   int    V02 loc0         
[000011] -----+------              \--*  CAST      int <- ushort <- int
[000010] -----+------                 \--*  MUL       int   
[000018] -----+------                    +--*  CAST      int <- short <- int
[000008] -----+------                    |  \--*  LCL_VAR   int    V00 arg0         
[000009] -----+------                    \--*  LCL_VAR   int    V01 arg1         

So the problem is the missing normalize on store cast, that would have to be somehow added if we want to continue folding the LHS.

And the fun part is that in this case the signed/unsigned difference the comment talks about can't even be detected - the LHS indir has type short, not ushort, because there are no unsigned stind instructions. In other words, this can't be fixed by changing (genTypeSize(lvaTable[lclNum].lvType) == genTypeSize(typ)) to the stronger (lvaTable[lclNum].lvType == typ). A fix will likely need to figure out that the indir is LHS or RHS. Talk about getting rid of ASG...

@mikedn
Copy link
Contributor Author

mikedn commented Dec 7, 2019

But it's not yet a LCL_VAR and there's also a pre-existing cast to ushort that was imported from IL (BTW why on earth is Roslyn insisting on inserting a narrowing cast before a store that truncates anyway?!)

And if Roslyn wouldn't generate that narrowing cast then you could make this print ever larger values because the narrowing effect of the stind.i2 is effectively lost:

fgMorphTree BB01, STMT00001 (after)
               [000012] -A--G+------              *  ASG       short 
               [000006] D----+-N----              +--*  LCL_VAR   int    V02 loc0         
               [000010] -----+------              \--*  MUL       int   
               [000017] -----+------                 +--*  CAST      int <- short <- int
               [000008] -----+------                 |  \--*  LCL_VAR   int    V00 arg0         
               [000009] -----+------                 \--*  LCL_VAR   int    V01 arg1         

So Test(32767, 32767); would print 1073676289. Who would have thought that you could stuff 1 billion in a short :)

@mikedn
Copy link
Contributor Author

mikedn commented Dec 9, 2019

Unrelated to this bug but since it's in the same area I'll included this here for completness:
Based on a cursory look at the existing IND(ADD(local)) morphing code I would expect the following to lead to an assert:

unsafe struct Arr
{
    public int len;
    public fixed byte a1[65536];
}

[MethodImpl(MethodImplOptions.NoInlining)]
static unsafe void Test(short a, int b)
{
    Arr r = default;
    Console.WriteLine(r.a1[65535]);
}

The tree is:

[000014] --CXG+------              *  CALL      void   System.Console.WriteLine
[000013] *--XG+------ arg0 in rcx  \--*  IND       ubyte 
[000012] ----G+------                 \--*  ADD       byref 
[000009] ----G+------                    +--*  ADDR      byref 
[000004] ----G+-N----                    |  \--*  LCL_FLD   ubyte  V02 loc0         [+4] Fseq[a1, FixedElementField]
[000011] -----+------                    \--*  CNS_INT   long   0xFFFF

Morph code checks if the ADD offset fits in 16 bit:

if (ival1 != (unsigned short)ival1)

but then fails to check if the sum of the ADD offset and LCL_FLD also fits in 16 bit:
lclFld->SetLclOffs(lclFld->GetLclOffs() + static_cast<unsigned>(ival1));

So why doesn't the assert in SetLclOffs fire? Because folding doesn't actually take place due to another piece of broken code:

const var_types tempTyp = temp->TypeGet();
const bool useExactSize = varTypeIsStruct(tempTyp) || (tempTyp == TYP_BLK) || (tempTyp == TYP_LCLBLK);
const unsigned varSize = useExactSize ? varDsc->lvExactSize : genTypeSize(temp);

This tries to use the indir type to determine the size of the lclvar and thus wrongly concludes that the var size is 1 (= sizeof(byte)) and wrongly concludes that the access is out of bounds.

@mikedn
Copy link
Contributor Author

mikedn commented Dec 14, 2019

Another case:

[MethodImpl(MethodImplOptions.NoInlining)]
static unsafe int Test(short a, int b)
{
    short c = (short)(a * 2);
    Unsafe.As<short, Bytes>(ref c).hi = (byte)b;
    Console.WriteLine(c);
    return c;
}

Prints -65026 which again is out of short's range.

The original tree is:

fgMorphTree BB01, STMT00002 (before)
[000013] -ACXG-------              *  ASG       ubyte 
[000012] --CXG--N----              +--*  FIELD     ubyte  hi
[000018] ------------              |  \--*  ADDR      byref 
[000019] ------------              |     \--*  LCL_VAR   int    V02 loc0         
[000011] ------------              \--*  CAST      int <- ubyte <- int
[000010] ------------                 \--*  LCL_VAR   int    V01 arg1         

and it becomes:

fgMorphTree BB01, STMT00002 (after)
[000013] -A--G+------              *  ASG       ubyte 
[000019] U----+-N----              +--*  LCL_FLD   ubyte  V02 loc0         [+1] Fseq[hi]
[000011] -----+------              \--*  CAST      int <- ubyte <- int
[000010] -----+------                 \--*  LCL_VAR   int    V01 arg1         

The problem is that the variable is "normalize on store" but no normalization is done when such a partial update occurs. We'll probably need to mark the variable "address exposed" just to force it to be "normalize on load". Not ideal but then this is such a corner case that it's unlikely to matter.

@briansull
Copy link
Contributor

Fixed by #40535

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants