From 77c5dbabc6c4b65580629cd7064a13d74ddf5a63 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 4 Mar 2024 10:15:29 +0100 Subject: [PATCH] JIT: Support subtraction in scalar evolution (#99154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Represent `x - y` as `x + y * -1` (this works even for MinValue). For example: ```csharp public static int Foo(int[] arr, int k) { int sum = 0; for (int i = arr.Length - 1; i >= 0; i -= k) { sum += arr[i]; } return sum; } ``` analyzes to ``` STMT00007 ( ??? ... ??? ) N004 ( 0, 0) [000044] DA--------- ▌ STORE_LCL_VAR int V03 loc1 d:3 $VN.Void N003 ( 0, 0) [000043] ----------- └──▌ PHI int $241 N001 ( 0, 0) [000053] ----------- pred BB03 ├──▌ PHI_ARG int V03 loc1 u:4 N002 ( 0, 0) [000051] ----------- pred BB02 └──▌ PHI_ARG int V03 loc1 u:2 $201 => ``` ```csharp public static int Bar(int n) { int sum = n * n; for (int i = 0; i < n; i++) { sum -= i; } return sum; } ``` analyzes to ``` N004 ( 0, 0) [000029] DA--------- ▌ STORE_LCL_VAR int V01 loc0 d:3 $VN.Void N003 ( 0, 0) [000028] ----------- └──▌ PHI int $140 N001 ( 0, 0) [000033] ----------- pred BB03 ├──▌ PHI_ARG int V01 loc0 u:4 N002 ( 0, 0) [000031] ----------- pred BB02 └──▌ PHI_ARG int V01 loc0 u:2 $100 => > ``` for `sum`. It would be `>` if we resolved SSA defs during simplification -- I'll make that change in a future PR. Also add some more documentation around the symbolic way we resolve addrecs. --- src/coreclr/jit/scev.cpp | 49 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/scev.cpp b/src/coreclr/jit/scev.cpp index 911cb1ff05026..5819b56bdfd3a 100644 --- a/src/coreclr/jit/scev.cpp +++ b/src/coreclr/jit/scev.cpp @@ -469,12 +469,53 @@ Scev* ScalarEvolutionContext::AnalyzeNew(BasicBlock* block, GenTree* tree, int d assert(ssaDsc->GetBlock() != nullptr); + // Try simple but most common case first, where we have a direct + // add recurrence like i = i + 1. Scev* simpleAddRec = CreateSimpleAddRec(store, enterScev, ssaDsc->GetBlock(), ssaDsc->GetDefNode()->Data()); if (simpleAddRec != nullptr) { return simpleAddRec; } + // Otherwise try a more powerful approach; we create a symbolic + // node representing the recurrence and then invoke the analysis + // recursively. This handles for example cases like + // + // int i = start; + // while (i < n) + // { + // int j = i + 1; + // ... + // i = j; + // } + // => + // + // where we need to follow SSA defs. In this case the analysis will result in + // + 1. The symbolic node represents a recurrence, + // so this corresponds to the infinite sequence [start, start + 1, + // start + 1 + 1, ...] which can be represented by . + // + // This approach also generalizes to handle chains of recurrences. + // For example: + // + // int i = 0; + // int j = 0; + // while (i < n) + // { + // j++; + // i += j; + // } + // => > + // + // Here `i` will analyze to + . + // Like before this corresponds to an infinite sequence + // [start, start + , start + 2 * , ...] + // which again can be represented as >. + // + // More generally, as long as we have only additions and only a + // single operand is the recurrence, we can represent it as an add + // recurrence. See MakeAddRecFromRecursiveScev for the details. + // ScevConstant* symbolicAddRec = NewConstant(data->TypeGet(), 0xdeadbeef); m_ephemeralCache.Emplace(store, symbolicAddRec); @@ -515,6 +556,7 @@ Scev* ScalarEvolutionContext::AnalyzeNew(BasicBlock* block, GenTree* tree, int d return NewExtension(cast->IsUnsigned() ? ScevOper::ZeroExtend : ScevOper::SignExtend, TYP_LONG, op); } case GT_ADD: + case GT_SUB: case GT_MUL: case GT_LSH: { @@ -532,6 +574,10 @@ Scev* ScalarEvolutionContext::AnalyzeNew(BasicBlock* block, GenTree* tree, int d case GT_ADD: oper = ScevOper::Add; break; + case GT_SUB: + oper = ScevOper::Add; + op2 = NewBinop(ScevOper::Mul, op2, NewConstant(op2->Type, -1)); + break; case GT_MUL: oper = ScevOper::Mul; break; @@ -651,7 +697,8 @@ void ScalarEvolutionContext::ExtractAddOperands(ScevBinop* binop, ArrayStack