C# 为什么会生成文件和指令?

C# 为什么会生成文件和指令?,c#,.net,assembly,C#,.net,Assembly,对于此类代码: int res = 0; for (int i = 0; i < 32; i++) { res += 1 << i; } 现在我可以看到大多数说明的要点了,但是和说明是怎么回事?无论如何,在这段代码中ecx永远不会超过0x1F,但我原谅它没有注意到这一点(也没有注意到结果是一个常量),毕竟它不是一个可以花费大量时间进行分析的提前编译器。但更重要的是,具有32位操作数的SHL已将cl屏蔽0x1F。所以在我看来,这些AND是完全无用的。它们为什么会产生?它

对于此类代码:

int res = 0;
for (int i = 0; i < 32; i++)
{
    res += 1 << i;
}
现在我可以看到大多数说明的要点了,但是和说明是怎么回事?无论如何,在这段代码中ecx永远不会超过0x1F,但我原谅它没有注意到这一点(也没有注意到结果是一个常量),毕竟它不是一个可以花费大量时间进行分析的提前编译器。但更重要的是,具有32位操作数的SHL已将cl屏蔽0x1F。所以在我看来,这些AND是完全无用的。它们为什么会产生?它们是否有我所缺少的某些用途?

C#编译器在生成中间(机器无关)代码时必须插入这些和指令,因为C#左移位运算符只需要使用5个最低有效位


生成x86代码时,优化编译器可能会删除这些不需要的指令。但是,很明显,它跳过了这种优化(可能是因为它无法花费太多时间进行分析)。C编译器发出的CIL代码中已经存在

    IL_0009: ldc.i4.s 31
    IL_000b: and
    IL_000c: shl
CIL
shl
指令的规范规定:

如果shiftAmount大于或等于值的大小,则返回值未指定

然而,C#规范定义了32位移位,以采用移位计数mod 32:

当x的类型为
int
uint时,
移位计数由低阶五位计数给出。换句话说,移位计数是根据
count&0x1F
计算的

在这种情况下,C#编译器只能发出显式的
操作。您最好希望抖动会注意到这一点,并优化掉冗余的
,但这需要时间,JIT的速度非常重要。因此,考虑一下基于JIT的系统所支付的价格。

我想真正的问题是,当C#和x86都指定截断行为时,为什么CIL会这样指定
shl
指令。我不知道这一点,但我推测,对于CIL规范来说,重要的是避免指定一种行为,这种行为可能会在某些指令集上JIT到一些昂贵的东西。同时,对于C#来说,有尽可能少的未定义的行为是很重要的,因为人们总是使用这些未定义的行为,直到下一版本的编译器/框架/操作系统/任何改变它们的东西打破了代码。

x64内核已经对移位量应用了5位掩码。从《英特尔处理器手册》第2B卷第4-362页:

目标操作数可以是寄存器或内存位置。计数操作数可以是立即数或CL寄存器计数被屏蔽为5位(如果在64位模式下使用REG.W,则为6位)。为计数为1提供特殊的操作码编码

这是不必要的机器代码。不幸的是,C#编译器不能对处理器的行为做出任何假设,必须应用C#语言规则。并生成其行为在CLI规范中指定的IL。Ecma-335,第三部分,第3.58章提到了SHL操作码:

shl指令将值(int32、int64或本机int)向左移位移位移位移位移位移位移位量由shiftAmount指定。shiftAmount的类型为int32或native int。如果shiftAmount大于或等于值的宽度,则返回值未指定


未指定是这里的难点。在未指定的实现细节之上栓接指定的行为会产生不必要的代码。从技术上讲,抖动可以优化操作码。虽然这很棘手,但它不知道语言规则。任何指定不屏蔽的语言都很难生成正确的IL。您可以发布到connect.microsoft.com以获取jitter团队对此事的看法。

出于兴趣,您是否已经有了关于SHL的链接?我找了好几个地方,但没有找到任何这样的说法。(点击-刚刚找到。)不是专家-所以请原谅这个愚蠢的问题,但是
cl
寄存器是如何填充的。
操作是否是最快的填充方式?我觉得这整个生成的代码都很可笑。为什么要将一个简单的指令批分解成这么多子批?我现在没有可用的visual studio,但我真的很难相信JIT编译器会生成这种类型的程序集….@Polity它展开了循环,所以看起来比实际情况糟糕得多。最后,一个标记为.net和程序集的问题与.net程序集无关:DOn x64,Shl截断64位的移位值,当移位64位值并从x86移动到x64时会导致一些令人讨厌的意外。如果CIL<代码> ShL<代码>被定义为截断,那么肯定需要做的最昂贵的事情是相同的,这是无论如何都无法避免的吗?“IgorSkochinsky,我确实这样认为,但是我手头没有x64规范,我能找到的唯一参考资料似乎表明
shl eax
将截断为32,就像在x86上一样。“不是吗?”哈罗德你当然是对的,净效果是一样的。不同之处在于抖动不会插入这样的操作,而使用原始IL编码的人可以可靠地避免成本。如果截断行为是强制性的(无论如何,在没有更聪明的抖动的情况下),它们将无法在某些CPU上运行。@drhirsch我喜欢这个,实际上,它为32位移位节省了一个AND。。哦,等等:)
    IL_0009: ldc.i4.s 31
    IL_000b: and
    IL_000c: shl