2011-02-17 63 views
22

我已經用C語言編寫了一個虛擬機,它對於非JIT虛擬機具有不錯的性能,但是我想學習新的東西並提高性能。我目前的實現只是使用一個開關將VM字節碼轉換爲指令,並將其編譯爲跳轉表。就像我所說的那樣,它表現得不錯,但是我遇到了只能用JIT編譯器克服的障礙。在程序集中編寫JIT編譯器

不久前,我已經問過一個類似的問題,關於自修改代碼,但我意識到我沒有提出正確的問題。

所以我的目標是爲這個C虛擬機編寫一個JIT編譯器,並且我想在x86程序集中完成它。 (我使用NASM作爲我的彙編程序)我不太清楚如何去做這件事。我對彙編很熟悉,並且已經研究了一些自修改代碼示例,但是我還沒有弄清楚如何進行代碼生成。

到目前爲止,我的主要塊是將指令複製到一個可執行的內存塊,我的論點。我知道我可以在NASM中標記某一行,並將該地址的整行與靜態參數一起復制,但這不是很動態,並且不適用於JIT編譯器。我需要能夠解釋來自字節碼的指令,將它複製到可執行內存,解釋第一個參數,將其複製到內存中,然後解釋第二個參數,並將其複製到內存中。

我已經被告知有幾個庫可以讓這個任務更容易,比如GNU閃電,甚至LLVM。但是,在使用外部資源之前,我想先手動編寫它,以瞭解它是如何工作的。

這個社區能提供哪些資源或例子來幫助我開始這項工作?一個顯示兩個或三個指令(如「add」和「mov」)的簡單示例將用於生成可執行代碼,並在內存中動態地生成參數,這將帶來奇蹟。

+10

只是因爲抖動產生的機器代碼並不意味着它本身需要用匯編語言編寫。這樣做沒有意義。 – 2011-02-17 23:28:47

+0

嘗試的一箇中間步驟是使用GCC計算出的goto擴展(使用`void * optable [] = {&& op_add,&& op_subtract,...}`並且每個操作數是`op_add:... goto * optable [* ip ++] ;`)。我已經看到了像你所描述的交換式口譯員的巨大成就。 – 2011-07-28 01:39:25

回答

18

我不會推薦在程序集中編寫JIT。在彙編中編寫解釋器的最常執行的位有很好的參數。作爲LuaJIT作者的例子,看看這個comment from Mike Pall

對於JIT,有許多不同的層面具有不同的複雜性:

  1. 通過簡單地複製翻譯的代碼編譯一個基本塊(的非分支的指令序列)。例如,少數(基於寄存器)字節碼指令的實現可能是這樣的:

    ; ebp points to virtual register 0 on the stack 
    instr_ADD: 
        <decode instruction> 
        mov eax, [ebp + ecx * 4] ; load first operand from stack 
        add eax, [ebp + edx * 4] ; add second operand from stack 
        mov [ebp + ebx * 4], eax ; write back result 
        <dispatch next instruction> 
    instr_SUB: 
        ... ; similar 
    

    所以,給出的指令序列ADD R3, R1, R2SUB R3, R3, R4一個簡單的JIT可以在解釋執行的相關部分複製到新機器代碼塊:

    mov ecx, 1 
        mov edx, 2 
        mov ebx, 3 
        mov eax, [ebp + ecx * 4] ; load first operand from stack 
        add eax, [ebp + edx * 4] ; add second operand from stack 
        mov [ebp + ebx * 4], eax ; write back result 
        mov ecx, 3 
        mov edx, 4 
        mov ebx, 3 
        mov eax, [ebp + ecx * 4] ; load first operand from stack 
        sub eax, [ebp + edx * 4] ; add second operand from stack 
        mov [ebp + ebx * 4], eax ; write back result 
    

    這只是複製相關的代碼,所以我們需要初始化相應的寄存器。更好的解決方案是將其直接轉換爲機器指令mov eax, [ebp + 4],但現在您已經必須手動編碼所請求的指令。

    該技術消除了解釋的開銷,但否則不會提高效率。如果代碼只執行一次或兩次,那麼首先將代碼翻譯成機器代碼(需要刷新至少部分I-cache)可能是不值得的。

  2. 雖然一些JIT使用上述技術而不是解釋器,但它們對頻繁執行的代碼採用更復雜的優化機制。這涉及將執行的字節碼轉換爲執行附加優化的中間表示(IR)。

    根據源語言和JIT的類型,這可能非常複雜(這就是爲什麼許多JIT將此任務委派給LLVM)。基於方法的JIT需要處理連接控制流圖,因此它們使用SSA表單並對其執行各種分析(例如,熱點)。

    跟蹤JIT(如LuaJIT 2)僅編譯直線代碼,使許多事情更容易實現,但您必須非常小心如何選擇跟蹤以及如何有效地將多個跟蹤鏈接在一起。 Gal和Franz在this paper (PDF)中描述了一種方法。有關另一種方法,請參閱LuaJIT源代碼。這兩個JIT都是用C語言編寫的(或者也許是C++)。

7

我建議你看項目http://code.google.com/p/asmjit/。通過使用它提供的框架,您可以節省大量的能源。如果你想手工編寫所有東西,只需閱讀源代碼並自己重寫,我認爲它不是很難。