2011-02-17 30 views
22

Tôi đã viết một máy ảo trong C có hiệu suất khá cho một máy ảo không phải JIT, nhưng tôi muốn tìm hiểu một cái gì đó mới và cải thiện hiệu suất. Việc triển khai hiện tại của tôi chỉ đơn giản là sử dụng một công tắc để dịch từ bytecode VM sang các chỉ lệnh, được biên dịch thành một bảng nhảy. Như tôi đã nói, hiệu suất tốt cho nó là gì, nhưng tôi đã đánh một rào cản chỉ có thể được khắc phục bằng trình biên dịch JIT.Viết một trình biên dịch JIT trong assembly

Tôi đã hỏi một câu hỏi tương tự cách đây không lâu về mã tự sửa đổi, nhưng tôi đã nhận ra rằng tôi đã không hỏi đúng câu hỏi.

Vì vậy, mục tiêu của tôi là viết trình biên dịch JIT cho máy ảo C này và tôi muốn thực hiện nó trong lắp ráp x86. (Tôi đang sử dụng NASM như assembler của tôi) Tôi không hoàn toàn chắc chắn làm thế nào để đi về việc này. Tôi cảm thấy thoải mái với việc lắp ráp, và tôi đã xem xét một số ví dụ mã tự sửa đổi, nhưng tôi đã không tìm ra cách để tạo ra mã nguồn.

Khối chính của tôi cho đến nay là sao chép hướng dẫn đến một phần bộ nhớ thực thi, với đối số của tôi. Tôi biết rằng tôi có thể gắn nhãn một dòng nhất định trong NASM và sao chép toàn bộ dòng từ địa chỉ đó bằng các đối số tĩnh, nhưng điều đó không phải là rất động và không hoạt động đối với trình biên dịch JIT. Tôi cần để có thể giải thích các lệnh từ bytecode, sao chép nó vào bộ nhớ thực thi, giải thích đối số đầu tiên, sao chép nó vào bộ nhớ, sau đó giải thích đối số thứ hai, và sao chép nó vào bộ nhớ.

Tôi đã được thông báo về một số thư viện có thể làm nhiệm vụ này dễ dàng hơn, chẳng hạn như GNU sét và thậm chí là LLVM. Tuy nhiên, tôi muốn viết điều này bằng tay trước, để hiểu cách nó hoạt động, trước khi sử dụng các tài nguyên bên ngoài.

Có bất kỳ tài nguyên hoặc ví dụ nào mà cộng đồng này có thể cung cấp để giúp tôi bắt đầu nhiệm vụ này không? Một ví dụ đơn giản cho thấy hai hoặc ba hướng dẫn như "thêm" và "mov" được sử dụng để tạo mã thực thi, với các đối số, động, trong bộ nhớ, sẽ làm điều kỳ diệu.

+10

Chỉ vì một jitter tạo ra mã máy không * không * có nghĩa là bản thân nó cần phải được viết trong hội đồng. Nó không có ý nghĩa để làm như vậy. –

+0

Một bước trung gian để thử là gửi luồng bằng cách sử dụng phần mở rộng goto được tính toán của GCC (sử dụng 'void * optable [] = {&& op_add, && op_subtract, ...}' và mỗi toán hạng là 'op_add: ... goto * optable [* ip ++] ; '). Tôi đã thấy những lợi ích to lớn trong các phiên dịch viên chuyển đổi như bạn mô tả. –

Trả lời

18

Tôi sẽ không khuyên bạn nên viết JIT để lắp ráp. Có các đối số tốt để viết các bit được thực hiện thường xuyên nhất của thông dịch viên thông dịch. Để biết ví dụ về hình thức này, hãy xem comment from Mike Pall, tác giả của LuaJIT.

Đối với JIT, có nhiều mức độ khác nhau với thay đổi phức tạp:

  1. Biên dịch một khối cơ bản (một chuỗi các lệnh không phân nhánh) bằng cách đơn giản sao chép mã của thông dịch viên. Ví dụ, việc triển khai của một vài (register-based) hướng dẫn bytecode có thể trông như thế này:

    ; 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 
    

    Vì vậy, được đưa ra trình tự hướng dẫn ADD R3, R1, R2, SUB R3, R3, R4 một JIT đơn giản có thể sao chép các bộ phận liên quan của việc thực hiện dịch viên thành một mã máy mới:

    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 
    

    Điều này chỉ đơn giản là sao chép mã có liên quan, vì vậy chúng tôi cần khởi tạo thanh ghi được sử dụng cho phù hợp. Một giải pháp tốt hơn sẽ là dịch trực tiếp điều này sang hướng dẫn máy mov eax, [ebp + 4], nhưng bây giờ bạn đã phải mã hóa thủ công các hướng dẫn được yêu cầu.

    Kỹ thuật này loại bỏ chi phí giải thích, nhưng không cải thiện hiệu quả nhiều.Nếu mã được thực thi chỉ một hoặc hai lần, thì có thể nó không đáng để dịch mã đầu tiên sang mã máy (yêu cầu phải xả sạch ít nhất một phần của I-cache).

  2. Trong khi một số JIT sử dụng kỹ thuật trên thay vì thông dịch viên, sau đó họ sử dụng cơ chế tối ưu hóa phức tạp hơn cho mã được thực hiện thường xuyên. Điều này liên quan đến việc dịch bytecode được thực thi thành một biểu diễn trung gian (IR) mà trên đó các tối ưu hóa bổ sung được thực hiện.

    Tùy thuộc vào ngôn ngữ nguồn và loại JIT, điều này có thể rất phức tạp (đó là lý do tại sao nhiều JIT ủy nhiệm nhiệm vụ này cho LLVM). JIT dựa trên phương pháp cần phải xử lý việc tham gia biểu đồ luồng kiểm soát, do đó, chúng sử dụng biểu mẫu SSA và chạy các phân tích khác nhau về điều đó (ví dụ: Hotspot).

    Một JIT truy tìm (như LuaJIT 2) chỉ biên dịch mã đường thẳng, giúp bạn dễ dàng thực hiện hơn, nhưng bạn phải cẩn thận cách chọn dấu vết và cách bạn liên kết nhiều dấu vết với nhau một cách hiệu quả. Gal và Franz mô tả một phương pháp trong this paper (PDF). Đối với một phương pháp khác xem mã nguồn LuaJIT. Cả hai JIT được viết bằng C (hoặc có thể là C++).

+0

Điều gì về các khối mã sử dụng phân nhánh? – jakogut

+1

Phải, ở cuối mỗi khối cơ bản, bạn phải quay trở lại một thói quen quyết định vị trí nhánh. Điều này sau đó dẫn đến một địa chỉ bytecode mới phải được ánh xạ tới một địa chỉ của mã máy tương ứng. Có một kỹ thuật được gọi là [bối cảnh luồng (PDF)] (http://www.cs.toronto.edu/syslab/pubs/zaleski_shapes2005.pdf) cho phép tích hợp trình thông dịch và JIT mượt mà hơn. Ý tưởng chính là dịch các nhánh thành các lệnh máy thực tế để người dự đoán nhánh có thể nhìn thấy chúng. – nominolo

7

Tôi đề nghị bạn xem dự án http://code.google.com/p/asmjit/. Bằng cách sử dụng khung mà nó cung cấp, bạn có thể tiết kiệm rất nhiều năng lượng. Nếu bạn muốn viết tất cả mọi thứ bằng tay, chỉ cần đọc nguồn và tự viết lại nó, tôi nghĩ rằng nó không phải là rất khó.

Các vấn đề liên quan