Skip to content

Commit c9173f2

Browse files
authored
JIT: fix bug in cloning conditions for jagged array (#83414)
When checking that an inner array access is in bounds, we must ensure any outer access is fully in bounds too. We were checking that `idx < array.Len` but not that `idx >= 0`. Use an unsigned compare for this check so we can do both sides with a single instruction. Fixes #83242.
1 parent a42f39b commit c9173f2

File tree

4 files changed

+83
-7
lines changed

4 files changed

+83
-7
lines changed

src/coreclr/jit/loopcloning.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,15 @@ GenTree* LC_Condition::ToGenTree(Compiler* comp, BasicBlock* bb, bool invert)
213213
GenTree* op1Tree = op1.ToGenTree(comp, bb);
214214
GenTree* op2Tree = op2.ToGenTree(comp, bb);
215215
assert(genTypeSize(genActualType(op1Tree->TypeGet())) == genTypeSize(genActualType(op2Tree->TypeGet())));
216-
return comp->gtNewOperNode(invert ? GenTree::ReverseRelop(oper) : oper, TYP_INT, op1Tree, op2Tree);
216+
217+
GenTree* result = comp->gtNewOperNode(invert ? GenTree::ReverseRelop(oper) : oper, TYP_INT, op1Tree, op2Tree);
218+
219+
if (compareUnsigned)
220+
{
221+
result->gtFlags |= GTF_UNSIGNED;
222+
}
223+
224+
return result;
217225
}
218226

219227
//--------------------------------------------------------------------------------------------------
@@ -963,12 +971,14 @@ void LC_ArrayDeref::DeriveLevelConditions(JitExpandArrayStack<JitExpandArrayStac
963971
}
964972
else
965973
{
966-
// Adjust for level0 having just 1 condition and push condition (i < a.len).
974+
// Adjust for level0 having just 1 condition and push conditions (i >= 0) && (i < a.len).
975+
// We fold the two compares into one using unsigned compare, since we know a.len is non-negative.
976+
//
967977
LC_Array arrLen = array;
968978
arrLen.oper = LC_Array::ArrLen;
969979
arrLen.dim = level - 1;
970-
(*conds)[level * 2 - 1]->Push(
971-
LC_Condition(GT_LT, LC_Expr(LC_Ident::CreateVar(Lcl())), LC_Expr(LC_Ident::CreateArrAccess(arrLen))));
980+
(*conds)[level * 2 - 1]->Push(LC_Condition(GT_LT, LC_Expr(LC_Ident::CreateVar(Lcl())),
981+
LC_Expr(LC_Ident::CreateArrAccess(arrLen)), /*unsigned*/ true));
972982

973983
// Push condition (a[i] != null)
974984
LC_Array arrTmp = array;
@@ -1524,7 +1534,7 @@ bool Compiler::optComputeDerefConditions(unsigned loopNum, LoopCloneContext* con
15241534
assert(maxRank != -1);
15251535

15261536
// First level will always yield the null-check, since it is made of the array base variables.
1527-
// All other levels (dimensions) will yield two conditions ex: (i < a.length && a[i] != null)
1537+
// All other levels (dimensions) will yield two conditions ex: ((unsigned) i < a.length && a[i] != null)
15281538
// So add 1 after rank * 2.
15291539
const unsigned condBlocks = (unsigned)maxRank * 2 + 1;
15301540

src/coreclr/jit/loopcloning.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -712,12 +712,13 @@ struct LC_Condition
712712
LC_Expr op1;
713713
LC_Expr op2;
714714
genTreeOps oper;
715+
bool compareUnsigned;
715716

716717
#ifdef DEBUG
717718
void Print()
718719
{
719720
op1.Print();
720-
printf(" %s ", GenTree::OpName(oper));
721+
printf(" %s%s ", GenTree::OpName(oper), compareUnsigned ? "U" : "");
721722
op2.Print();
722723
}
723724
#endif
@@ -733,7 +734,8 @@ struct LC_Condition
733734
LC_Condition()
734735
{
735736
}
736-
LC_Condition(genTreeOps oper, const LC_Expr& op1, const LC_Expr& op2) : op1(op1), op2(op2), oper(oper)
737+
LC_Condition(genTreeOps oper, const LC_Expr& op1, const LC_Expr& op2, bool asUnsigned = false)
738+
: op1(op1), op2(op2), oper(oper), compareUnsigned(asUnsigned)
737739
{
738740
}
739741

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
7+
class Runtime_83242
8+
{
9+
[MethodImpl(MethodImplOptions.NoInlining)]
10+
static int Map(int i)
11+
{
12+
if (i == 5) return -1;
13+
return i;
14+
}
15+
16+
[MethodImpl(MethodImplOptions.NoInlining)]
17+
static void Setup(int[][] a)
18+
{
19+
for (int i = 0; i < a.Length; i++)
20+
{
21+
a[i] = new int[5];
22+
23+
for (int j = 0; j < 5; j++)
24+
{
25+
a[i][j] = j;
26+
}
27+
}
28+
}
29+
30+
public static int Main()
31+
{
32+
int[][] a = new int[11][];
33+
int sum = 0;
34+
Setup(a);
35+
36+
for (int i = 0; i < a.Length; i++)
37+
{
38+
int ii = Map(i);
39+
40+
// Need to ensure ii >= 0 is in the cloning
41+
// conditions for the following loop
42+
43+
for (int j = 0; j < 5; j++)
44+
{
45+
if (ii >= 0)
46+
{
47+
sum += a[ii][j];
48+
}
49+
}
50+
}
51+
52+
Console.WriteLine($"sum is {sum}\n");
53+
return sum;
54+
}
55+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<Optimize>True</Optimize>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="$(MSBuildProjectName).cs" />
8+
</ItemGroup>
9+
</Project>

0 commit comments

Comments
 (0)