83 votes

Est-il possible d'écrire un compilateur JIT (en code natif) entièrement dans un langage .NET géré

Je pense à l’écriture d’un compilateur JIT et je me demande s’il est même théoriquement possible d’écrire le tout en code managé. En particulier, une fois que vous avez généré l'assembleur dans un tableau d'octets, comment pouvez-vous y accéder pour commencer l'exécution?

71voto

Gene Belitski Points 5671

Et pour la preuve de concept complète, voici une traduction tout à fait capable de l'approche de Rasmus à l'égard de JIT en 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
 

qui exécute heureusement cédant

 Value before: FFFFFFFC
Value after: 7FFFFFFE
 

70voto

Jack P. Points 8767

Oui, vous le pouvez. En fait, c'est mon travail :)

J'ai écrit GPU.NET entièrement en F# (modulo nos tests), mais se démonte et Ece IL au moment de l'exécution, tout comme l' .NET CLR. Nous émettons code natif pour tout sous-jacente de l'accélération de l'appareil que vous souhaitez utiliser; actuellement, nous ne prenons en charge les GPU Nvidia, mais je l'ai conçu de notre système de reciblage avec un minimum de travail, de sorte qu'il est probable que nous allons soutenir d'autres plates-formes dans le futur.

Comme pour les performances, j'ai un F# remercier-lorsqu'il est compilé en mode optimisé (avec tailcalls), notre compilateur JIT lui-même est probablement à peu près aussi vite que le compilateur dans le CLR (ce qui est écrit en C++, IIRC).

Pour l'exécution, nous avons l'avantage d'être en mesure de passer le contrôle à des pilotes de matériel pour exécuter le jitted code; toutefois, ce ne serait pas plus difficile à faire sur le CPU depuis .NET prend en charge des pointeurs de fonction non géré/code natif (si vous perdez toute sécurité normalement fournis par .NET).

51voto

Rasmus Faber Points 24195

Le truc doit être VirtualAlloc avec l' EXECUTE_READWRITE-drapeau (besoins de P/Invoke) et le Maréchal.GetDelegateForFunctionPointer.

Voici une version modifiée de la rotation entier exemple (notez qu'aucun code unsafe est nécessaire ici):

[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);
 }

Exemple complet (fonctionne maintenant avec les versions X86 et X64).

30voto

Tomas Petricek Points 118959

En utilisant le code unsafe, vous pouvez "pirater" un délégué et d'en faire le point de l'arbitraire d'un assemblage de code que vous avez généré et stocké dans un tableau. L'idée est que le délégué a un _methodPtr domaine, qui peut être défini à l'aide de la Réflexion. Voici un exemple de code:

C'est, bien sûr, un sale hack qui peut cesser de fonctionner à tout moment lorsque l' .NET runtime changements.

Je suppose que, en principe, entièrement géré code de sécurité ne peuvent pas être autorisés à mettre en œuvre JIT, parce que ce serait briser toute sécurité des hypothèses que l'exécution s'appuie sur. (Sauf si le code assembleur généré est venu avec une machine-checkable la preuve qu'il ne viole pas les hypothèses...)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X