MSIL中Cpblk操作码的完整语义

Ben*_*igt 3 .net clr

在MSDN文档cpblk是一个有点稀疏:

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

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

好吧,相比其他大容量复制操作,如Array.Copy,Marshal.CopyBuffer.BlockCopy,我们知道:

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

这给我留下了一些问题:

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

根据/sf/answers/1846607381/钉扎是不必要的,但支持说明是错误的.(我怀疑大型对象堆没有被压缩这是一个过度概括)

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

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

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

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

Han*_*ant 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;
}
Run Code Online (Sandbox Code Playgroud)

优化的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
  ...
}
Run Code Online (Sandbox Code Playgroud)

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

  • 在 https://github.com/dotnet/coreclr/issues/20086 中,建议在包含托管引用的块上使用“cpblk”,即使它在小对象堆中工作,也不是一个好主意。这又回到了@HansPassant 一开始所说的内容。 (2认同)