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

Jon*_*rop 82 .net c# f# jit

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

Gen*_*ski 71

而对于完整的概念证明,这是一个完全有能力的将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
Run Code Online (Sandbox Code Playgroud)

快乐地执行屈服

Value before: FFFFFFFC
Value after: 7FFFFFFE
Run Code Online (Sandbox Code Playgroud)

  • @rwong:"编译"方面从未涉及原始问题的范围.托管代码实现*IL - >本机代码*转换的能力有点明显. (4认同)

Jac*_* P. 70

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

我完全用F#编写了GPU.NET(模块化我们的单元测试) - 它实际上在运行时反汇编和JITs IL,就像.NET CLR一样.我们为您想要使用的任何底层加速设备发出本机代码; 目前我们只支持Nvidia GPU,但我设计的系统可以通过最少的工作进行重定向,因此我们很可能在将来支持其他平台.

至于性能,感谢F# - 当在优化模式下编译(使用tailcalls)时,我们的JIT编译器本身可能与CLR中的编译器一样快(用C++,IIRC编写).

为了执行,我们的好处是能够将控制权交给硬件驱动程序来运行jitted代码; 但是,这对CPU来说并不困难,因为.NET支持非托管/本机代码的函数指针(尽管你失去了.NET通常提供的任何安全/安全性).

  • NoExecute的重点不在于您不能跳转到自己创建的代码吗?而不是通过函数指针跳转到本机代码:不是**不可能通过函数指针跳转到本机代码? (4认同)

Ras*_*ber 50

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

以下是旋转整数示例的修改版本(请注意,此处不需要不安全的代码):

[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);
 }
Run Code Online (Sandbox Code Playgroud)

完整示例(现在可以与X86和X64一起使用).


Tom*_*cek 30

使用不安全的代码,您可以"破解"委托并使其指向您生成并存储在数组中的任意汇编代码.这个想法是委托有一个_methodPtr字段,可以使用Reflection设置.以下是一些示例代码:

当然,这是一个脏的黑客,可能会在.NET运行时更改时随时停止工作.

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