【问题标题】:Is the JIT generating the wrong codeJIT 是否生成了错误的代码
【发布时间】:2014-03-21 18:09:00
【问题描述】:

我一直在寻找您的一些代码不起作用。除了以下行,一切看起来都很好。

Transport = Transport?? MockITransportUtil.GetMock(true);

在执行该行之前,Transport 为空。我看到 GetMock 已执行并且它返回一个非空对象。在该行之后 Transport 仍然为空;

我查看了生成的 IL,我觉得它看起来不错。

 IL_0002:  ldarg.0
  IL_0003:  ldfld      class [Moq]Moq.Mock`1<class [CommLibNet]CommLibNET.ITransport> Curex.Services.Common.UnitTests.Messaging.TestIGuaranteedSubscriptionBase::Transport
  IL_0008:  dup
  IL_0009:  brtrue.s   IL_0012
  IL_000b:  pop
  IL_000c:  ldc.i4.1
  IL_000d:  call       class [Moq]Moq.Mock`1<class [CommLibNet]CommLibNET.ITransport> Curex.Services.Common.UnitTests.Mocking.MockITransportUtil::GetMock(bool)
  IL_0012:  stfld      class [Moq]Moq.Mock`1<class [CommLibNet]CommLibNET.ITransport> Curex.Services.Common.UnitTests.Messaging.TestIGuaranteedSubscriptionBase::Transport

我们看到函数被调用,stfld 应该获取返回值并设置字段。

然后我查看了程序集,我看到调用已完成,但看起来 RAX 中的返回被下一次调用吹走并且丢失了。

            Transport = Transport?? MockITransportUtil.GetMock(true);
000007FE9236F776  mov         rax,qword ptr [rbp+0B0h]  
000007FE9236F77D  mov         rax,qword ptr [rax+20h]  
000007FE9236F781  mov         qword ptr [rbp+20h],rax  
000007FE9236F785  mov         rcx,qword ptr [rbp+20h]  
000007FE9236F789  mov         rax,qword ptr [rbp+0B0h]  
000007FE9236F790  mov         qword ptr [rbp+28h],rax  
000007FE9236F794  test        rcx,rcx  
000007FE9236F797  jne         000007FE9236F7AC  
000007FE9236F799  mov         cl,1  
000007FE9236F79B  call        000007FE92290608  

            //var x = ReferenceEquals(null, Transport) ? MockITransportUtil.GetMock(true) : Transport;
            ListerFactory = ListerFactory ?? MockIListenerUtil.GetMockSetupWithAction((a) => invokingAction = a);
000007FE9236F7A0  mov         qword ptr [rbp+30h],rax  
000007FE9236F7A4  mov         rax,qword ptr [rbp+30h]  
000007FE9236F7A8  mov         qword ptr [rbp+20h],rax  
000007FE9236F7AC  mov         rcx,qword ptr [rbp+28h]  

如果我使用 if 语句或 ?: 运算符,一切都可以正常工作。

Visual Studio 2013

编辑

我已经创建了一个伪最小复制。

class simple
{
    public A MyA = null;
    public B MyB = null;

    public void SetUp()
    {
        MyA = MyA ?? new A();
        MyB = new B();// Put breakpoint here
    }
}

如果您在指示的行上设置断点并在调试器中查看 MyA 的值,它仍然为空(仅在 x64 中构建时)。如果您执行下一行,它将设置该值。我无法重现根本没有发生的评估。在反汇编中非常清楚,下一行的执行在分配发生之前就已经开始了。

编辑 2

这里是一个link 到 ms 连接站点

【问题讨论】:

  • 更多代码请或我们可以下载代码的某个位置。我希望能够重现该问题。
  • 您能否展示更多的反汇编,至少通过000007FE9236F7ACjne 所在的位置)?
  • @rerun 那我没疯。 :) 寻呼 Eric Lippert...
  • 注意64位调试器有问题see here
  • @Guvante 这是不同形式的相同问题,但不是调试器。根据程序集,它在错误的位置生成 fld 分配。看起来它可能正在执行不应该的优化。

标签: c# jit


【解决方案1】:
    MyB = new B();// Put breakpoint here

问题在于断点,而不是代码生成。 x64 抖动使这一点失效,它会生成不准确的调试信息。它使用仍然是前一条语句的一部分的代码地址错误地为语句发出行号信息。

从您发布的反汇编中可以看出,地址 F7A0 到 F7A8 的代码仍然是 ??陈述。 F7AC 的分支是真正的分支,这是下一条语句开始的地方。所以它应该说 F7AC 是下一个语句的开始,而不是 F7A0。

这个错误的后果是调试器可能永远不会在断点处停止。您可以通过更改您的复制代码并编写public A MyA = new A(); 亲自查看这一点,如果它确实停止,则尚未执行分配。因此,在您的情况下,您仍然会看到具有先前值 null 的变量。一个步骤就可以解决它,尽管它取决于下一个语句的样子。

请放心,这只会在您调试时出错,程序仍然可以正常运行。请记住这个怪癖,afaik它只会出错??操作员。你可以看出它并没有被太多使用 :) 尽管大多数程序员只调试过他们的程序的 32 位版本,但默认的项目设置非常鼓励它。

问题正在解决中,不要指望您的 Connect 报告会产生影响,Microsoft 很清楚这个错误。微软的jitter团队有rewritten the x64 jitter completely,目前在CTP2。在它发布之前,我估计还要再过一年左右。

【讨论】:

  • 这不是问题。我之前遇到过与提问者相同的问题,因此我得到了不正确的程序行为,而不仅仅是调试器问题。此外,根据我使用的机器,行为不一致。 ?? 会在一台机器上失败而在另一台机器上正常工作,这表明 JIT 中存在错误。
  • 请一次一个错误,您可以报告自己的。
  • 我只看到了这个问题,因为它在发布的函数中显示为空。一旦我完全改变了程序,它就会发生分配。
  • @HansPassant 在 rerun 链接的 MS Connect 错误报告中,他写道“在生产代码中我能够看到分配从未发生过。 在最小的复制中我无法获得这会发生。” (强调)这与我遇到的完全相同的问题。在我的生产代码中,null 合并运算符被彻底破坏,我不得不切换到替代语法。当我试图制作一个重现问题的玩具程序时,我做不到。同样的事情正在重新运行。所以看起来我们遇到了同样的错误。
  • 嗯,这个错误的另一个方面是在启用优化器的发布版本中会发生什么。我可以看到优化器可能会消除 ?? 的右侧表达式操作员认为它没有被使用时。很大程度上取决于表达式的外观,例如,它不会优化构造函数调用。很难确定。
【解决方案2】:

我从 MS 那里得到了更新,这确实是一个真正的问题,并且已在即将发布的 x64 jiter 中得到修复。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-19
    • 1970-01-01
    • 1970-01-01
    • 2016-10-27
    相关资源
    最近更新 更多