Tôi đang làm việc trên một ILGenerator
phần mở rộng để giúp phát ra các đoạn IL bằng cách sử dụng Expression
. Mọi thứ đều ổn, cho đến khi tôi làm việc trên phần chuyển đổi số nguyên. Có một cái gì đó thực sự phản trực giác với tôi, như:
- Sử dụng
conv.i8
để chuyển đổiInt32
sangUInt64
- Sử dụng
conv.u8
để chuyển đổiUInt32
sangInt64
Tất cả đều vì ngăn xếp đánh giá không theo dõi được chữ ký số nguyên. Tôi hoàn toàn hiểu lý do, nó chỉ là một chút khó khăn để xử lý.
Bây giờ tôi muốn hỗ trợ chuyển đổi liên quan IntPtr
. Nó phải phức tạp hơn, vì chiều dài của nó là thay đổi. Tôi quyết định xem xét trình biên dịch C # thực hiện nó như thế nào.
Bây giờ tập trung vào cụ thể IntPtr
để Int64
chuyển đổi. Rõ ràng hành vi mong muốn phải là: không hoạt động trên các hệ thống 64 bit hoặc mở rộng đăng nhập trên các hệ thống 32 bit.
Vì trong C # native int
được bao bọc bởi IntPtr
struct, tôi phải xem xét phần thân của Int64 op_Explicit(IntPtr)
phương thức của nó . Phần sau được phân tách bởi dnSpy từ .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
}
Thật kỳ lạ khi conv.u8
xuất hiện ở đây! Nó sẽ thực hiện mở rộng bằng không trên các hệ thống 32 bit. Tôi xác nhận rằng với đoạn mã sau:
delegate long ConvPtrToInt64(void* ptr);
var f = ILAsm<ConvPtrToInt64>(
Ldarg, 0,
Conv_U8,
Ret
);
Console.WriteLine(f((void*)(-1))); // print 4294967295 on x86
Tuy nhiên, khi xem hướng dẫn x86 của phương pháp C # sau:
static long Convert(IntPtr intp) => (long)intp;
;from SharpLab
C.Convert(IntPtr)
L0000: mov eax, ecx
L0002: cdq
L0003: ret
Nó chỉ ra rằng những gì thực sự xảy ra là một dấu hiệu mở rộng!
Tôi nhận thấy rằng Int64 op_Explicit(IntPtr)
có một Intrinsic
thuộc tính. Có phải là trường hợp cơ thể phương thức bị bỏ qua hoàn toàn bởi JIT thời gian chạy và được thay thế bằng một số thực hiện nội bộ?
Câu hỏi cuối cùng: Tôi có phải tham khảo các phương pháp chuyển đổi IntPtr
để thực hiện chuyển đổi của mình không?
Phụ lục Tôi ILAsm
thực hiện:
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);
}
Int64 op_Explicit(IntPtr)
với trong chế độ x64. Làm thế nào đạt được điều này? Tôi đã nghiên cứu đường dẫn tệp mà từ đó System.Private.CoreLib
lắp ráp được tải (bởi Assembly.Location
), nhưng chúng giống nhau giữa x86 và x64.