Конвертировать IntPtr в Int64: conv.u8 или conv.i8? - PullRequest
8 голосов
/ 27 марта 2020

Я работаю над расширением 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);
}

1 Ответ

3 голосов
/ 27 марта 2020

Я сделал ошибку. Int64 op_Explicit(IntPtr) имеет две версии. 64-разрядная версия находится в "C: \ Program Files \ do tnet ...", и ее реализация:

.method public hidebysig specialname static 
    int64 op_Explicit (
        native int 'value'
    ) cil managed 
{
    .maxstack 8

    IL_0000: ldarga.s  'value'
    IL_0002: ldfld     void* System.IntPtr::_value
    IL_0007: conv.u8
    IL_0008: ret
}

32-разрядная версия находится в "C: \ Program Files (x86) \ do tnet ... ", и его реализация:

.method public hidebysig specialname static 
    int64 op_Explicit (
        native int 'value'
    ) cil managed 
{
    .maxstack 8

    IL_0000: ldarga.s  'value'
    IL_0002: ldfld     void* System.IntPtr::_value
    IL_0007: conv.i4
    IL_0008: conv.i8
    IL_0009: ret
}

Головоломка решена!

Тем не менее, я думаю, что можно использовать одна идентичная реализация в 32-битной и 64-битной сборках. Один conv.i8 сделает всю работу здесь.

Действительно, я мог бы упростить мою задачу генерации IntPtr преобразований, потому что во время выполнения известна длина IntPtr (32 или 64 для моего знания), и большинство излучаемых методов не будут сохранены и использованы повторно. Но я все еще хотел бы независимое от времени выполнения решение, и я думаю, что уже нашел его.

...