Я работаю над расширением ILGenerator
, чтобы помочь генерировать фрагменты IL, используя Expression
. Все было хорошо, пока я не работал над целочисленной частью преобразования. Есть что-то действительно нелогичное для меня, например:
- Используйте
conv.i8
для преобразования Int32
в UInt64
- Используйте
conv.u8
для преобразования UInt32
в Int64
Они все потому, что стек оценки не отслеживает целочисленную подпись. Я полностью понимаю причину, с этим немного сложно справиться.
Теперь я хочу поддержать преобразование, включающее IntPtr
. Это должно быть сложнее, так как его длина является переменной. Я решил посмотреть, как это реализует C# компилятор.
Теперь сосредоточимся на конкретном преобразовании IntPtr
в Int64
. По-видимому, желаемое поведение должно быть следующим: запрет на 64-разрядные системы или расширение знака в 32-разрядных системах.
Так как в C# * * * * * * * * * * * * * * * * * native int
обернут структурой IntPtr
, Я должен посмотреть на тело метода Int64 op_Explicit(IntPtr)
. Следующее разобрано dnSpy из. NET core 3.1.1:
.method public hidebysig specialname static
int64 op_Explicit (
native int 'value'
) cil managed
{
.custom instance void System.Runtime.CompilerServices.IntrinsicAttribute::.ctor() = (
01 00 00 00
)
.custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = (
01 00 00 00
)
.maxstack 8
IL_0000: ldarga.s 'value'
IL_0002: ldfld void* System.IntPtr::_value
IL_0007: conv.u8
IL_0008: ret
}
Странно, что conv.u8
появляется здесь! Он будет выполнять расширение нуля в 32-битных системах. Я подтвердил это с помощью следующего кода:
delegate long ConvPtrToInt64(void* ptr);
var f = ILAsm<ConvPtrToInt64>(
Ldarg, 0,
Conv_U8,
Ret
);
Console.WriteLine(f((void*)(-1))); // print 4294967295 on x86
Однако, глядя на инструкции x86 следующего C# метода:
static long Convert(IntPtr intp) => (long)intp;
;from SharpLab
C.Convert(IntPtr)
L0000: mov eax, ecx
L0002: cdq
L0003: ret
Оказывается, что на самом деле случается, это расширение знака!
Я заметил, что Int64 op_Explicit(IntPtr)
имеет Intrinsic
Атрибут. Является ли это случаем, что тело метода полностью игнорируется JIT среды выполнения и заменяется некоторой внутренней реализацией?
ЗАКЛЮЧИТЕЛЬНЫЙ вопрос: Должен ли я ссылаться на методы преобразования IntPtr
для реализации моих преобразований?
Приложение Моя ILAsm
реализация:
static T ILAsm<T>(params object[] insts) where T : Delegate =>
ILAsm<T>(Array.Empty<(Type, string)>(), insts);
static T ILAsm<T>((Type type, string name)[] locals, params object[] insts) where T : Delegate
{
var delegateType = typeof(T);
var mi = delegateType.GetMethod("Invoke");
Type[] paramTypes = mi.GetParameters().Select(p => p.ParameterType).ToArray();
Type returnType = mi.ReturnType;
var dm = new DynamicMethod("", returnType, paramTypes);
var ilg = dm.GetILGenerator();
var localDict = locals.Select(tup => (name: tup.name, local: ilg.DeclareLocal(tup.type)))
.ToDictionary(tup => tup.name, tup => tup.local);
var labelDict = new Dictionary<string, Label>();
Label GetLabel(string name)
{
if (!labelDict.TryGetValue(name, out var label))
{
label = ilg.DefineLabel();
labelDict.Add(name, label);
}
return label;
}
for (int i = 0; i < insts.Length; ++i)
{
if (insts[i] is OpCode op)
{
if (op.OperandType == InlineNone)
{
ilg.Emit(op);
continue;
}
var operand = insts[++i];
if (op.OperandType == InlineBrTarget || op.OperandType == ShortInlineBrTarget)
ilg.Emit(op, GetLabel((string)operand));
else if (operand is string && (op.OperandType == InlineVar || op.OperandType == ShortInlineVar))
ilg.Emit(op, localDict[(string)operand]);
else
ilg.Emit(op, (dynamic)operand);
}
else if (insts[i] is string labelName)
ilg.MarkLabel(GetLabel(labelName));
else
throw new ArgumentException();
}
return (T)dm.CreateDelegate(delegateType);
}