.NET JIT lỗi tiềm ẩn?


404

Đoạn mã sau cung cấp đầu ra khác nhau khi chạy bản phát hành bên trong Visual Studio và chạy bản phát hành bên ngoài Visual Studio. Tôi đang sử dụng Visual Studio 2008 và nhắm mục tiêu .NET 3.5. Tôi cũng đã thử .NET 3.5 SP1.

Khi chạy bên ngoài Visual Studio, JIT sẽ khởi động. Hoặc (a) có điều gì đó tinh tế đang xảy ra với C # mà tôi bị thiếu hoặc (b) JIT thực sự bị lỗi. Tôi nghi ngờ rằng JIT có thể sai, nhưng tôi đang cạn kiệt khả năng khác ...

Đầu ra khi chạy bên trong Visual Studio:

    0 0,
    0 1,
    1 0,
    1 1,

Đầu ra khi chạy bản phát hành bên ngoài Visual Studio:

    0 2,
    0 2,
    1 2,
    1 2,

Lý do là gì?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    struct IntVec
    {
        public int x;
        public int y;
    }

    interface IDoSomething
    {
        void Do(IntVec o);
    }

    class DoSomething : IDoSomething
    {
        public void Do(IntVec o)
        {
            Console.WriteLine(o.x.ToString() + " " + o.y.ToString()+",");
        }
    }

    class Program
    {
        static void Test(IDoSomething oDoesSomething)
        {
            IntVec oVec = new IntVec();
            for (oVec.x = 0; oVec.x < 2; oVec.x++)
            {
                for (oVec.y = 0; oVec.y < 2; oVec.y++)
                {
                    oDoesSomething.Do(oVec);
                }
            }
        }

        static void Main(string[] args)
        {
            Test(new DoSomething());
            Console.ReadLine();
        }
    }
}

8
Vâng - làm thế nào về điều đó: tìm một lỗi nghiêm trọng trong một cái gì đó rất cần thiết như .Net JIT - xin chúc mừng!
Andras Zoltan

73
Điều này dường như để repro trong bản dựng ngày 9 tháng 12 của tôi về khung 4.0 trên x86. Tôi sẽ chuyển nó cho đội jitter. Cảm ơn!
Eric Lippert

28
Đây là một trong số rất ít câu hỏi thực sự xứng đáng với huy hiệu vàng.
Mehrdad Afshari

28
Thực tế là tất cả chúng ta đều quan tâm đến câu hỏi này cho thấy, chúng tôi không mong đợi các lỗi trong .NET JIT, Microsoft đã làm rất tốt.
Ian Ringrose

2
Tất cả chúng tôi đang chờ đợi Microsoft trả lời một cách lo lắng .....
Talha

Câu trả lời:


211

Đây là một lỗi tối ưu hóa JIT. Đó là hủy kiểm soát vòng lặp bên trong nhưng không cập nhật giá trị oVec.y đúng cách:

      for (oVec.x = 0; oVec.x < 2; oVec.x++) {
0000000a  xor         esi,esi                         ; oVec.x = 0
        for (oVec.y = 0; oVec.y < 2; oVec.y++) {
0000000c  mov         edi,2                           ; oVec.y = 2, WRONG!
          oDoesSomething.Do(oVec);
00000011  push        edi  
00000012  push        esi  
00000013  mov         ecx,ebx 
00000015  call        dword ptr ds:[00170210h]        ; first unrolled call
0000001b  push        edi                             ; WRONG! does not increment oVec.y
0000001c  push        esi  
0000001d  mov         ecx,ebx 
0000001f  call        dword ptr ds:[00170210h]        ; second unrolled call
      for (oVec.x = 0; oVec.x < 2; oVec.x++) {
00000025  inc         esi  
00000026  cmp         esi,2 
00000029  jl          0000000C 

Lỗi sẽ biến mất khi bạn tăng oVec.y lên 4, quá nhiều cuộc gọi để hủy đăng ký.

Một cách giải quyết khác là:

  for (int x = 0; x < 2; x++) {
    for (int y = 0; y < 2; y++) {
      oDoesSomething.Do(new IntVec(x, y));
    }
  }

CẬP NHẬT: kiểm tra lại vào tháng 8 năm 2012, lỗi này đã được sửa trong phiên bản jitter 4.0.30319. Nhưng vẫn còn hiện diện trong jitter v2.0.50727. Có vẻ như họ sẽ không sửa nó trong phiên bản cũ sau một thời gian dài.


3
+1, chắc chắn là một lỗi - Tôi có thể đã xác định các điều kiện cho lỗi (không nói rằng nobugz đã tìm thấy nó vì tôi!), Nhưng điều này (và của bạn, Nick, vì vậy +1 cho bạn cũng vậy) cho thấy JIT là thủ phạm. thú vị là việc tối ưu hóa bị loại bỏ hoặc khác đi khi IntVec được khai báo là một lớp. Ngay cả khi bạn khởi tạo một cách rõ ràng các trường cấu trúc thành 0 trước khi vòng lặp hành vi tương tự được nhìn thấy. Bẩn thỉu!
Andras Zoltan

3
@Hans Passant Bạn đã sử dụng công cụ nào để xuất mã lắp ráp?

3
@Joan - Chỉ cần Visual Studio, sao chép / dán từ cửa sổ Gỡ lỗi của trình gỡ lỗi và nhận xét được thêm bằng tay.
Hans Passant

82

Tôi tin rằng đây là một lỗi biên dịch JIT chính hãng. Tôi sẽ báo cáo với Microsoft và xem họ nói gì. Thật thú vị, tôi thấy rằng JIT x64 không có vấn đề tương tự.

Đây là bài đọc của tôi về JIT x86.

// save context
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  

// put oDoesSomething pointer in ebx
00000006  mov         ebx,ecx 

// zero out edi, this will store oVec.y
00000008  xor         edi,edi 

// zero out esi, this will store oVec.x
0000000a  xor         esi,esi 

// NOTE: the inner loop is unrolled here.
// set oVec.y to 2
0000000c  mov         edi,2 

// call oDoesSomething.Do(oVec) -- y is always 2!?!
00000011  push        edi  
00000012  push        esi  
00000013  mov         ecx,ebx 
00000015  call        dword ptr ds:[002F0010h] 

// call oDoesSomething.Do(oVec) -- y is always 2?!?!
0000001b  push        edi  
0000001c  push        esi  
0000001d  mov         ecx,ebx 
0000001f  call        dword ptr ds:[002F0010h] 

// increment oVec.x
00000025  inc         esi  

// loop back to 0000000C if oVec.x < 2
00000026  cmp         esi,2 
00000029  jl          0000000C 

// restore context and return
0000002b  pop         ebx  
0000002c  pop         esi  
0000002d  pop         edi  
0000002e  pop         ebp  
0000002f  ret     

Điều này có vẻ như một sự tối ưu hóa trở nên tồi tệ đối với tôi ...


23

Tôi đã sao chép mã của bạn vào Ứng dụng Console mới.

  • Xây dựng gỡ lỗi
    • Đầu ra đúng với cả trình gỡ lỗi và không có trình gỡ lỗi
  • Chuyển sang Phát hành bản dựng
    • Một lần nữa, đầu ra chính xác cả hai lần
  • Đã tạo cấu hình x86 mới (Tôi đang chạy X64 Windows 2008 và đang sử dụng 'Any CPU')
  • Xây dựng gỡ lỗi
    • Có đầu ra chính xác cả F5 và CTRL + F5
  • Phát hành bản dựng
    • Đầu ra đúng với Debugger đính kèm
    • Không có trình sửa lỗi - Có đầu ra không chính xác

Vì vậy, đó là J86 x86 tạo mã không chính xác. Đã xóa văn bản gốc của tôi về sắp xếp lại các vòng lặp, v.v ... Một vài câu trả lời khác ở đây đã xác nhận rằng JIT đang tháo gỡ vòng lặp không chính xác khi trên x86.

Để khắc phục sự cố, bạn có thể thay đổi khai báo IntVec thành một lớp và nó hoạt động trong tất cả các hương vị.

Hãy nghĩ rằng điều này cần phải đi trên MS Connect ....

-1 cho Microsoft!


1
Ý tưởng thú vị, nhưng chắc chắn đây không phải là "tối ưu hóa" mà là một lỗi rất lớn trong trình biên dịch nếu đây là trường hợp? Bây giờ sẽ được tìm thấy phải không?
David M

Tôi đồng ý với bạn. Sắp xếp lại các vòng lặp như thế này có thể gây ra các vấn đề chưa được giải quyết. Trên thực tế, điều này dường như thậm chí ít xảy ra hơn, bởi vì các vòng lặp không bao giờ có thể đạt tới 2.
Andras Zoltan

2
Trông giống như một trong những Heisenbugs khó chịu này: P
arul

Bất kỳ CPU nào sẽ không hoạt động nếu OP (hoặc bất kỳ ai sử dụng ứng dụng của anh ấy) có máy x86 32 bit. Vấn đề là JIT x86 với tối ưu hóa được kích hoạt tạo ra mã xấu.
Nick Guerrera
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.