是否可以完全用托管.NET 语言编写 JIT 编译器(到本机代码)

我正在考虑编写一个 JIT 编译器的想法,我只是想知道在理论上是否有可能用托管代码编写整个过程。特别是,一旦你生成了一个字节数组的汇编程序,你如何跳转到它开始执行?

6291 次浏览

使用不安全的代码,您可以“黑客”一个委托,并使其指向您生成并存储在数组中的任意汇编代码。其思想是,委托有一个 _methodPtr字段,可以使用反射设置该字段。下面是一些示例代码:

当然,这是一个肮脏的黑客行为,当.NET 运行时发生变化时,它可能随时停止工作。

我猜,原则上不允许完全托管的安全代码实现 JIT,因为这会打破运行时所依赖的任何安全假设。(除非,生成的汇编代码附带了一个机器可检查的证据,证明它没有违反假设... ...)

是的,你可以。事实上,这是我的工作:)

我完全用 F # (模块化我们的单元测试)编写了 GPU.NET ——它实际上在运行时反汇编和 JITs IL,就像。NET CLR 做。我们为任何你想要使用的底层加速设备发布本地代码; 目前我们只支持 Nvidia GPU,但是我已经设计了我们的系统,以最小的工作量重定向,所以我们很可能在未来支持其他平台。

至于性能,我要感谢 F # ——当以优化模式编译时(使用尾调用) ,我们的 JIT 编译器本身可能与 CLR 中的编译器(用 C + + ,IIRC 编写)一样快。

对于执行来说,我们可以将控制权传递给硬件驱动程序来运行抖动的代码; 然而,在 CPU 上这样做并不难。NET 支持指向非托管/本机代码的函数指针(尽管您会失去通常由。NET).

诀窍应该是 VirtualAllocEXECUTE_READWRITE-标志(需要 P/调用)和 函数指针

下面是旋转整数示例的修改版本(注意,这里不需要不安全的代码) :

[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)。

为了充分证明这个概念,这里有一个将 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