-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSubjects.cs
More file actions
154 lines (137 loc) · 7.01 KB
/
Copy pathSubjects.cs
File metadata and controls
154 lines (137 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
using System.Runtime.CompilerServices;
namespace BoundsCheckProof;
/// <summary>
/// Подопытные циклы для проверки границ. Один JIT научился убирать
/// в .NET 9, ещё один - в .NET 10, обратные циклы дожаты в .NET 11,
/// а цикл с i != Length таскает проверку до сих пор. Плюс два контроля.
///
/// Пруфы (код взят дословно из первоисточников):
/// PR #100777 (jakobbotsch, JIT-команда) - цикл (uint)i, убит в .NET 9
/// https://github.com/dotnet/runtime/pull/100777
/// PR #115980 (EgorBo, JIT-команда) - доступ s[p.Length], убит в .NET 10
/// https://github.com/dotnet/runtime/pull/115980
/// Issue #84697 (xtqqczze) - цикл i != Length, там же в треде hughbe и
/// xtqqczze фиксируют обратные циклы, EgorBo сс-ит BruceForstall
/// https://github.com/dotnet/runtime/issues/84697
/// Issue #9505 - обратная итерация, открыт с 2017, дожат в .NET 11
/// https://github.com/dotnet/runtime/issues/9505
/// Сводный трекер пропусков у команды JIT - issue #109677 (EgorBo)
/// https://github.com/dotnet/runtime/issues/109677
///
/// NoInlining на методах - только чтобы каждый печатался в листинге
/// отдельно и под своим именем. На сам цикл и проверку инлайн не влияет.
/// </summary>
public static class Subjects
{
// ------------------------------------------------------------------
// ЭТАЛОН: классический цикл, проверка убрана с незапамятных времен
// на всех рантаймах. Baseline для всего дальше.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int LessLoop(byte[] src)
{
int sum = 0;
for (int i = 0; i < src.Length; i++)
{
sum += src[i];
}
return sum;
}
// ------------------------------------------------------------------
// ВЫЖИВШИЙ: тот же перебор, но i != Length (issue #84697).
// Делает то же самое, что LessLoop, а проверка на каждой итерации -
// жива на всех рантаймах, включая .NET 11.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int NotEqualLoop(byte[] src)
{
int sum = 0;
for (int i = 0; i != src.Length; i++)
{
sum += src[i];
}
return sum;
}
// ------------------------------------------------------------------
// Обратный цикл (issue #9505, открыт с 2017). Повседневный код -
// обход с хвоста, удаление с конца; hughbe в треде #84697: "This could
// be a common pattern". Индекс идёт от Length-1 вниз до нуля - глазами
// видно, что выход за границы невозможен, а JIT доказать не мог.
// До .NET 10 включительно проверка живёт в запасной копии цикла
// (loop cloning) и времени не отнимает, в .NET 11 копию выкинули.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int ReverseLoop(byte[] src)
{
int sum = 0;
for (int i = src.Length - 1; i >= 0; i--)
{
sum += src[i];
}
return sum;
}
// ------------------------------------------------------------------
// Родственный обратный while из того же треда #84697 (xtqqczze):
// декремент до нуля. Дожат в .NET 11 вместе с ReverseLoop, а в .NET 9
// стал быстрее за счёт IV widening (PR #97865).
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int WhileDecLoop(byte[] src)
{
int sum = 0;
int i = src.Length;
while (i != 0)
{
sum += src[--i];
}
return sum;
}
// ------------------------------------------------------------------
// УБИТ В .NET 9 (PR #100777, jakobbotsch). Беззнаковое сравнение
// само доказывает 0 <= i < len, но до фикса анализ диапазонов это
// доказать не мог - мешало слишком осторожное "а вдруг переполнение".
// net8: cmp/jae в цикле есть, net9/10/11: нет.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int UnsignedWhile(int i, int[] indices)
{
while ((uint)i < (uint)indices.Length)
{
i = indices[i];
}
return i;
}
// ------------------------------------------------------------------
// УБИТ В .NET 10 (PR #115980, EgorBo). Сравнение длин уже доказывает
// валидность индекса. net8/9: проверка есть, net10/11: нет.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool PrefixSlash(string prefix, string path)
{
if (prefix.Length < path.Length)
{
return path[prefix.Length] == '/';
}
return false;
}
// ------------------------------------------------------------------
// КОНТРОЛЬ-ПЛЮС: одна проверка вместо двух на любом рантайме.
// Показывает: устранение проверок реально работает, инструменту
// можно верить.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int FirstPlusLast(int[] arr) => arr[0] + arr[arr.Length - 1];
// ------------------------------------------------------------------
// КОНТРОЛЬ-МИНУС: проверку убрать нельзя, обязана остаться везде.
// Показывает: отсутствие проверки в других методах - не случайность.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int AtUnknownIndex(int[] arr, int index)
{
if (arr.Length == 0)
{
return -1;
}
return arr[index];
}
}