是否有可能完全用托pipe的.NET语言编写JIT编译器(本地代码)

我正在写一个JIT编译器的想法,只是想知道是否在理论上可以写在托pipe代码的整个事情。 特别是,一旦你把汇编器生成一个字节数组,你怎么跳到它开始执行?

而对于这个概念的充分certificate,这里是一个完全有能力的翻译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 

高兴地执行屈服

 Value before: FFFFFFFC Value after: 7FFFFFFE 

是的你可以。 其实这是我的工作:)

我已经完全用F#编写了GPU.NET(模块化unit testing) – 它实际上是在运行时反汇编和JIT IL,就像.NET CLR一样。 我们为您想要使用的任何底层加速设备发出本机代码; 目前我们只支持Nvidia的GPU,但是我已经devise了我们的系统,以最less的工作重新定位,所以我们将来可能会支持其他平台。

至于性能方面,我有F#来感谢 – 在编译优化模式(带tailcalls)时,我们的JIT编译器本身可能与CLR(用C ++,IIRC编写)中的编译器一样快。

为了执行,我们有能力将控制权交给硬件驱动程序来运行jitted代码; 然而,这对CPU来说并不难,因为.NET支持函数指针指向非托pipe/本地代码(尽pipe你会失去.NET提供的任何安全/安全性)。

诀窍应该是带有EXECUTE_READWRITE flag(需要P / Invoke)和Marshal.GetDelegateForFunctionPointer的 VirtualAlloc 。

这里是一个修改版本的旋转整数的例子(请注意,这里不需要不安全的代码):

 [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字段,可以使用reflection设置。 这是一些示例代码:

  • C#中的内联x86 ASM

当然,这是一个肮脏的黑客,可能在.NET运行时改变的任何时候停止工作。

我想,原则上,完全托pipe的安全代码不能被允许实现JIT,因为这会破坏运行时依赖的任何安全假设。 (除非生成的汇编代码附带一个机器可检验的certificate,certificate它不违反假设…)