c# - msil - ¿Es posible escribir un compilador JIT(en código nativo) completamente en un lenguaje.NET administrado?
common language runtime (4)
Estoy jugando con la idea de escribir un compilador JIT y me pregunto si incluso teóricamente es posible escribir todo en código administrado. En particular, una vez que hayas generado el ensamblador en una matriz de bytes, ¿cómo saltas para comenzar la ejecución?
Al usar código inseguro, puede "piratear" un delegado y hacer que apunte a un código de ensamblaje arbitrario que usted generó y almacenó en una matriz. La idea es que el delegado tenga un campo _methodPtr
, que se puede establecer utilizando Reflection. Aquí hay un código de muestra:
Esto es, por supuesto, un truco sucio que puede dejar de funcionar en cualquier momento cuando el tiempo de ejecución de .NET cambia.
Supongo que, en principio, no se puede permitir que el código seguro totalmente administrado implemente JIT, porque eso rompería cualquier suposición de seguridad en la que se base el tiempo de ejecución. (A menos que el código ensamblado generado venga con una prueba verificable por máquina que no viole las suposiciones ...)
El truco debe ser VirtualAlloc con EXECUTE_READWRITE
-flag (necesita P / Invoke) y Marshal.GetDelegateForFunctionPointer .
Aquí hay una versión modificada del ejemplo entero de rotación (tenga en cuenta que aquí no se necesita ningún código inseguro):
[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);
}
Ejemplo completo (ahora funciona tanto con X86 como con X64).
Sí tu puedes. De hecho, es mi trabajo :)
He escrito GPU.NET por completo en F # (módulo de nuestras pruebas unitarias): realmente desmonta y JITs IL en tiempo de ejecución, al igual que lo hace .NET CLR. Emitimos código nativo para cualquier dispositivo de aceleración subyacente que desee usar; actualmente solo admitimos las GPU de Nvidia, pero he diseñado nuestro sistema para que sea reorientado con un mínimo de trabajo, por lo que es probable que respaldemos otras plataformas en el futuro.
En cuanto al rendimiento, tengo F # para agradecer: cuando se compila en modo optimizado (con tailcalls), nuestro compilador JIT es probablemente tan rápido como el compilador dentro del CLR (que está escrito en C ++, IIRC).
Para la ejecución, tenemos la ventaja de poder pasar el control a los controladores de hardware para ejecutar el código jitted; sin embargo, esto no sería más difícil de hacer en la CPU ya que .NET admite punteros a código no administrado / nativo (aunque usted perdería la seguridad normalmente proporcionada por .NET).
Y para la prueba de concepto completa aquí hay una traducción totalmente capaz del enfoque de Rasmus para 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
que felizmente ejecuta el rendimiento
Value before: FFFFFFFC
Value after: 7FFFFFFE