MSIL中Cpblk操作码的完整语义

时间:2014-12-03 19:05:42

标签: .net clr

cpblk的{​​{3}}有点稀疏:

  

cpblk指令从源地址(类型unsigned int32*native int)复制一个数字(类型&)到目标地址(类型*native int&)。如果源和目标区域重叠,则cpblk的行为未指定。

     

cpblk假设所寻址的源和目标都与机器的自然大小对齐。 cpblk指令可以紧跟在unaligned.指令之前,以指示源或目标是未对​​齐的。

好的,与Array.CopyMarshal.CopyBuffer.BlockCopy等其他批量复制操作相比,我们知道:

  • 大小以字节为单位
  • 指针应该对齐

这给我留下了一些问题:

  • 缓冲区应该先固定吗?操作数类型是native int,"非托管指针"是否重要?或"托管指针(&)"?
  • 这种类型有限制吗? (例如,Buffer.BlockCopy仅适用于基本类型,而不是结构,即使它们只包含基本类型)

根据The MSDN documentation钉扎是不必要的,但支持说明是错误的。 (我怀疑大型物体堆不会被压缩,这是一种过度概括)

ECMA-335也不是很有帮助。那里的指令描述包含相同的措辞并添加

  

[基本原理: cpblk用于复制结构(而不​​是任意字节运行)。由CLI分配的所有这些结构自然地与当前平台对齐。因此,生成cpblk指令的编译器不需要知道代码最终是在32位还是64位平台上执行。 结束理由]

好的,这听起来应该接受比Buffer.BlockCopy更多的类型。但仍然不是任意类型。

也许新发布的.NET核心源代码会有一些答案。

1 个答案:

答案 0 :(得分:8)

cpblk及其随附的initblk直接映射到任何本地语言编译器依赖于初始化和复制结构的内在函数。无需等待.NETCore源,您可以从SSCLI20,clr / src / fjit / fjitdef.h中看到它们的语义。一个简单的抖动,它将cpblk直接转换为memcpy()initblkmemset()的调用。与C编译器使用的内在函数相同。

当然不考虑GC,C#和VB.NET编译器根本不使用这些操作码。但是C ++ / CLI编译器就是一个简单的例子:

using namespace System;

struct s { int a; int b;  };

int main(array<System::String ^> ^args)
{
    s var = {};        // initblk
    s cpy = var;       // cpblk
    return 0;
}

优化的MSIL:

.method assembly static int32  main(string[] args) cil managed
{
  // Code size       34 (0x22)
  .maxstack  3
  .locals ([0] valuetype s cpy,
           [1] valuetype s var)
  IL_0000:  ldloca.s   var
  IL_0002:  ldc.i4.0
  IL_0003:  ldc.i4.8
  IL_0004:  initblk
  IL_0006:  ldloca.s   cpy
  IL_0008:  ldloca.s   var
  IL_000a:  ldc.i4.8
  IL_000b:  cpblk
  ...
}

当前的.NET抖动生成内联代码,其中包含针对小型结构的简单寄存器移动,针对大型结构的REP STOS / MOVS。与Buffer.Memcpy()的作用非常相似。