我在考虑写一个JIT编译器,想知道是否有可能完全用托管代码来实现。特别是,一旦你把汇编生成到一个字节数组中,如何跳转到它以开始执行?
这是一个完整的概念证明,这里有一个 Rasmus 的 JIT 方法的 完全可行 的 F# 翻译。
open System
open System.Runtime.InteropServices
type AllocationType =
| COMMIT=0x1000u
type MemoryProtection =
| EXECUTE_READWRITE=0x40u
type FreeType =
| DECOMMIT = 0x4000u
[<DllImport("kernel32.dll", SetLastError=true)>]
extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[<DllImport("kernel32.dll", SetLastError=true)>]
extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, FreeType freeType);
let JITcode: byte[] = [|0x55uy;0x8Buy;0xECuy;0x8Buy;0x45uy;0x08uy;0xD1uy;0xC8uy;0x5Duy;0xC3uy|]
[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
type Ret1ArgDelegate = delegate of (uint32) -> uint32
[<EntryPointAttribute>]
let main (args: string[]) =
let executableMemory = VirtualAlloc(IntPtr.Zero, UIntPtr(uint32(JITcode.Length)), AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE)
Marshal.Copy(JITcode, 0, executableMemory, JITcode.Length)
let jitedFun = Marshal.GetDelegateForFunctionPointer(executableMemory, typeof<Ret1ArgDelegate>) :?> Ret1ArgDelegate
let mutable test = 0xFFFFFFFCu
printfn "Value before: %X" test
test <- jitedFun.Invoke test
printfn "Value after: %X" test
VirtualFree(executableMemory, UIntPtr.Zero, FreeType.DECOMMIT) |> ignore
0
愉快地执行yield操作
Value before: FFFFFFFC
Value after: 7FFFFFFE
是的,你可以。实际上这是我的工作 :)
我完全使用F#编写了GPU.NET(除了我们的单元测试)--它实际上会在运行时反汇编和JITs IL,就像.NET CLR一样。我们为您想要使用的任何底层加速设备发出本机代码;目前我们仅支持Nvidia GPU,但我已经设计了我们的系统以便在最少的工作量下进行重新定向,因此很可能我们将来会支持其他平台。
至于性能,我要感谢F#--当以优化模式编译(包括尾递归)时,我们的JIT编译器本身可能与CLR内部的编译器(用C++编写)大致同速。
对于执行,我们有一个好处,即可以将控制权传递给硬件驱动程序来运行JIT编译的代码;但是,在CPU上做到这点不会更难,因为.NET支持指向非托管/本机代码的函数指针(尽管您会失去.NET通常提供的任何安全性/安全保障)。
这个技巧应该使用带有EXECUTE_READWRITE
标志的VirtualAlloc函数(需要P/Invoke)和Marshal.GetDelegateForFunctionPointer方法。
以下是旋转整数示例的修改版本(请注意,在此处不需要使用不安全代码):
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint Ret1ArgDelegate(uint arg1);
public static void Main(string[] args){
// Bitwise rotate input and return it.
// The rest is just to handle CDECL calling convention.
byte[] asmBytes = new byte[]
{
0x55, // push ebp
0x8B, 0xEC, // mov ebp, esp
0x8B, 0x45, 0x08, // mov eax, [ebp+8]
0xD1, 0xC8, // ror eax, 1
0x5D, // pop ebp
0xC3 // ret
};
// Allocate memory with EXECUTE_READWRITE permissions
IntPtr executableMemory =
VirtualAlloc(
IntPtr.Zero,
(UIntPtr) asmBytes.Length,
AllocationType.COMMIT,
MemoryProtection.EXECUTE_READWRITE
);
// Copy the machine code into the allocated memory
Marshal.Copy(asmBytes, 0, executableMemory, asmBytes.Length);
// Create a delegate to the machine code.
Ret1ArgDelegate del =
(Ret1ArgDelegate) Marshal.GetDelegateForFunctionPointer(
executableMemory,
typeof(Ret1ArgDelegate)
);
// Call it
uint n = (uint)0xFFFFFFFC;
n = del(n);
Console.WriteLine("{0:x}", n);
// Free the memory
VirtualFree(executableMemory, UIntPtr.Zero, FreeType.DECOMMIT);
}
完整示例(现在适用于X86和X64)。
_methodPtr
字段,可以使用反射设置。以下是一些示例代码:
当然,这是一个肮脏的黑客技巧,在.NET运行时更改时可能随时停止工作。
calli
操作码来调用编译好的代码。 - Jack P.