Full semantics of the Cpblk opcode in MSIL
The MSDN documentation is for a cpblk
bit sparse:
The command
cpblk
copies the number (typeunsigned int32
) of bytes from the source address (type*
,native int
or&
) to the destination address (type*
,native int
or&
). The behavior iscpblk
not specified if the source and destination areas overlap.
cpblk
assumes that both the source and destination addresses match the natural size of the machine. The commandcpblk
can be preceded by an instructionunaligned.
to indicate that the source or destination is not bound.
Okay, compared to other bulk copy operations like Array.Copy
, Marshal.Copy
and Buffer.BlockCopy
, we know that:
- Size is measured in bytes
- Pointers must be aligned
This leaves me with a few questions:
- Do I need to buffer the buffers first? Does the type of the operand matter
native int
, "unmanaged pointer" or "managed pointer (&
)"? - Are there any restrictions on the type? (for example
Buffer.BlockCopy
only works with primitive types, not structures, even if they only contain primitive types)
According to this StackOverflow question , no pinning is needed, but the supporting explanation is simply wrong. (I suspect this is an over-generalization due to the LOB bunch not being compact)
ECMA-335 doesn't help much either. The description of the instruction contains the same wording and adds
[Rationale:
cpblk
intended to copy structures (not arbitrary bytes). All such CLI-allocated structures naturally align for the current platform. Therefore, the compiler does not need to generate instructionscpblk
to know if the code will eventually execute on a 32-bit or 64-bit platform. ultimate justification]
Ok, it looks like it should accept more types than Buffer.BlockCopy
. But still not arbitrary types.
Perhaps the recently released .NET source code will contain some answers.
source to share
cpblk
and its companion,, initblk
maps directly to internals that any native language compiler depends on initializing and copying structures. No need to wait for .NETCore source, you can see their semantics from SSCLI20, clr / src / fjit / fjitdef.h. Simple jitter converts cpblk
directly into a call memcpy()
, initblk
up to memset()
. The same features that the C compiler uses.
Of course, disregarding GC, the C # and VB.NET compilers don't use these opcodes at all. But the C ++ / CLI compiler does, a simple example:
using namespace System;
struct s { int a; int b; };
int main(array<System::String ^> ^args)
{
s var = {}; // initblk
s cpy = var; // cpblk
return 0;
}
Optimized 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 current tags generate inline code with simple register moves for small structures, REP STOS / MOVS for large ones. Very similar to what Buffer.Memcpy () does.
source to share