私はJITコンパイラーを書くという考えをいじっていて、すべてをマネージコードで書くことさえ理論的に可能かどうか疑問に思っています。特に、アセンブラをバイト配列に生成したら、どのようにアセンブラにジャンプして実行を開始しますか?
そして、ここでの概念の完全な証明は、完全に機能する 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
はい、できます。実際、それは私の仕事です:)
GPU.NETを完全にF#で記述しました(単体テストを法として)。実際には、.NET CLRと同様に、実行時に分解してILをJITします。使用したい基盤となるアクセラレーションデバイスのネイティブコードを発行します。現在、Nvidia GPUのみをサポートしていますが、最小限の作業で再ターゲット可能になるようにシステムを設計したため、将来的には他のプラットフォームもサポートする可能性があります。
パフォーマンスに関しては、F#に感謝します-最適化モード(テールコール付き)でコンパイルすると、JITコンパイラ自体はおそらくCLR内のコンパイラ(C++、IIRCで記述されている)とほぼ同じくらい高速です。
実行する場合、ハードウェアドライバーに制御を渡して、jittedコードを実行できるという利点があります。ただし、.NETはアンマネージコードまたはネイティブコードへの関数ポインターをサポートしているため(通常、.NETによって提供される安全性/セキュリティは失われますが)、CPUでこれを行うことは難しくありません。
トリックは VirtualAlloc with EXECUTE_READWRITE
-フラグ(P/Invokeが必要)および Marshal.GetDelegateForFunctionPointer 。
次に、回転整数の例の修正バージョンを示します(安全でないコードは必要ありません)。
[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の両方で動作するようになりました)。
安全でないコードを使用すると、デリゲートを「ハッキング」して、生成して配列に格納した任意のアセンブリコードを指すようにすることができます。デリゲートには_methodPtr
フィールド。Reflectionを使用して設定できます。ここにいくつかのサンプルコードがあります:
もちろん、これは.NETランタイムが変更されたときにいつでも動作を停止する可能性があるダーティーハックです。
原則として、フルマネージドの安全なコードでJITを実装することはできないと思います。これは、ランタイムが依存するセキュリティの前提条件を破るからです。 (生成されたアセンブリコードに、前提条件に違反していないことを示すマシンチェック可能な証明が付属している場合を除きます...)