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?
Réponses
Trop de publicités?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
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).
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).
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...)