Чому значення перерахунку з багатовимірного масиву не дорівнює собі?


151

Поміркуйте:

using System;

public class Test
{
    enum State : sbyte { OK = 0, BUG = -1 }

    static void Main(string[] args)
    {
        var s = new State[1, 1];
        s[0, 0] = State.BUG;
        State a = s[0, 0];
        Console.WriteLine(a == s[0, 0]); // False
    }
}

Як це можна пояснити? Він виникає у налагодженнях налагодження у Visual Studio 2015 під час роботи в x86 JIT. Збірка випуску або запуску в x64 JIT друкує True як очікувалося.

Для відтворення з командного рядка:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portableа /debug:fullтакож відтворювати.)


2
ideone.com/li3EzY це правда. додати більше інформації про .net версію, IDE, компілятор
Березня

1
Те ж саме. Але, знайшовшись із налаштуваннями проекту, я зрозумів, що якщо зняти прапорець "Віддати перевагу 32-бітовому" на вкладці "Збірка", це спрацює за призначенням - повернеться правдою. Отже, це виглядає як проблема WoW64 для мене.
Дмитро Ротай

2
Здається, ви вказали на помилку в рамках.
Fabien PERRONNET

1
Цікаво, що запущений код порушено, ildasmа потім ilasm"виправить" його ...
Джон Скіт

2
В /debug=IMPLпрапор листя розтрощений /debug=OPT"виправляє" це.
Джон Скіт

Відповіді:


163

Ви знайшли помилку з генерацією коду в тремтінні .NET 4 x86. Це дуже незвично, воно виходить з ладу лише тоді, коли код не оптимізований. Машинний код виглядає приблизно так:

        State a = s[0, 0];
013F04A9  push        0                            ; index 2 = 0
013F04AB  mov         ecx,dword ptr [ebp-40h]      ; s[] reference
013F04AE  xor         edx,edx                      ; index 1 = 0
013F04B0  call        013F0058                     ; eax = s[0, 0]
013F04B5  mov         dword ptr [ebp-4Ch],eax      ; $temp1 = eax 
013F04B8  movsx       eax,byte ptr [ebp-4Ch]       ; convert sbyte to int
013F04BC  mov         dword ptr [ebp-44h],eax      ; a = s[0, 0]
        Console.WriteLine(a == s[0, 0]); // False
013F04BF  mov         eax,dword ptr [ebp-44h]      ; a
013F04C2  mov         dword ptr [ebp-50h],eax      ; $temp2 = a
013F04C5  push        0                            ; index 2 = 0
013F04C7  mov         ecx,dword ptr [ebp-40h]      ; s[] reference 
013F04CA  xor         edx,edx                      ; index 1 = 0
013F04CC  call        013F0058                     ; eax = s[0, 0]
013F04D1  mov         dword ptr [ebp-54h],eax      ; $temp3 = eax 
                                               ; <=== Bug here!
013F04D4  mov         eax,dword ptr [ebp-50h]      ; a == s[0, 0] 
013F04D7  cmp         eax,dword ptr [ebp-54h]  
013F04DA  sete        cl  
013F04DD  movzx       ecx,cl  
013F04E0  call        731C28F4  

Проблема з великою кількістю часу та дублювання коду - це нормально для неоптимізованого коду. Інструкція на 013F04B8 примітна, саме там відбувається необхідне перетворення з sbate в 32-бітове ціле число. Функція помічника збору масиву повертає 0x0000000FF, що дорівнює State.BUG, і його потрібно перетворити в -1 (0xFFFFFFFF), перш ніж значення можна порівняти. Інструкція MOVSX - це інструкція Sign eXtension.

Те ж саме відбувається знову на 013F04CC, але цього разу немає інструкції MOVSX для того ж перетворення. Ось де чіпи падають, інструкція CMP порівнює 0xFFFFFFFF з 0x000000FF, і це неправда. Таким чином, це помилка упущення, генератор коду не зміг знову випромінювати MOVSX для виконання того ж конвертації sbyte в int.

Що особливо незвично в цій помилці, це те, що вона працює правильно, коли ви вмикаєте оптимізатор, тепер він знає, як використовувати MOVSX в обох випадках.

Ймовірною причиною того, що ця помилка залишалася невиявленою так довго, є використання sbyte як базового типу enum. Досить рідко це робити. Використання багатовимірного масиву також є інструментальним, а комбінація - фатальною.

Інакше я б сказала досить критичну помилку. Наскільки це може бути широко, важко здогадатися, у мене є лише тест дрожання 4.6.1 x86. Джиттер x64 і 3.5 x86 генерують дуже різний код і уникають цієї помилки. Тимчасове рішення, яке потрібно продовжувати, - це видалити sbyte як базовий тип enum, і нехай це буде типовим, int , тому розширення знаків не потрібно.

Ви можете подати помилку на connect.microsoft.com, посилання на цей Q + A має бути достатньо, щоб сказати їм усе, що вони повинні знати. Дайте мені знати, якщо ви не хочете витрачати час, і я подбаю про це.


33
Хороші, ґрунтовні дані з точною причиною такого дивного питання, завжди приємно читати, +1.
Лассе В. Карлсен

11
Будь ласка, опублікуйте посилання на статтю connect.microsoft.com, щоб ми могли за неї проголосувати.
Ганс Пасант

Я припускаю, що використання byteзамість цього також sbyteповинно бути добре і може бути кращим, якщо справжній код використовується з, скажімо, ORM, де ви не хочете, щоб ваші прапори в базі даних займали додаткове місце.
Voo

6
Я б опублікував помилку на dotnet / coreclr, а не підключився, ви потрапите прямо до розробників JIT.
Лукас Трушевський

8
Я дев в команді JIT в Microsoft. Я відтворив помилку і відкрив проблему для неї внутрішньо (доставка x86 JIT ще не відкрита на github). Що стосується термінів, коли це буде виправлено, я передбачаю, що ми будемо включати це виправлення в наступний головний реліз інструментів. Якщо ця помилка впливає на бізнес, і вам потрібно виправити раніше, будь ласка, подайте проблему підключення (connect.microsoft.com), щоб ми могли переглянути вплив і які альтернативи нам потрібно швидше отримати.
Рассел К. Хедлі

8

Розглянемо декларацію ОП:

enum State : sbyte { OK = 0, BUG = -1 }

Оскільки помилка виникає лише тоді, коли вона BUGє негативною (від -128 до -1), а State є перерахунком підписаного байта, я почав припускати, що десь виникає проблема.

Якщо ви запускаєте це:

Console.WriteLine((sbyte)s[0, 0]);
Console.WriteLine((sbyte)State.BUG);
Console.WriteLine(s[0, 0]);
unchecked
{
    Console.WriteLine((byte) State.BUG);
}

він виведе:

255

-1

БУГ

255

З тієї причини, яку я ігнорую (станом на даний момент), s[0, 0] передається байт перед оцінкою, і саме тому вона стверджує, що a == s[0,0]це неправда.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.