HHVM
BLOG DOCS GitHub HACK
The Journey of a Thousand bytecode đăng vào 03 Tháng 10 2014 bởi Sara Golemon Trình biên dịch là vui vẻ. Họ mất đẹp, ngôn ngữ có thể đọc được con người như PHP hoặc Hack và biến chúng thành nạc, trung bình, CPU executin 'Turing máy. Một số trong số này là đơn giản, đủ một sinh viên CS có thể viết lên trong một ngày cuối tuần, một số là những sản phẩm của thập kỷ điều chỉnh và architecting cẩn thận. Một nơi nào đó trong đó truyền thống tự hào đứng HHVM; Trong thực tế, nó là một số trình biên dịch xếp chồng lên nhau trong một chuỗi ngày càng tăng của các thao tác logic và trừu tượng. Bài viết này sẽ cố gắng để đưa người đọc thông qua quá trình biên dịch HHVM từ PHP-script để mã máy x86, một bước tại một thời điểm. Bước Một: Lexing Các bước đầu tiên của cuộc hành trình của chúng tôi nên quen thuộc với nhiều người sử dụng PHP vì nó thực sự tiếp xúc với không gian sử dụng như các token_get_all () chức năng. Trong giai đoạn này, chúng tôi giảm một PHP script của con người có thể đọc được một loạt các thẻ; Nhận dạng số mà các trình biên dịch sẽ có thể nhận ra là có một số ý nghĩa nội tại. Đối với điều này, HHVM sử dụng một công cụ gọi là Flex để tạo ra một chương trình từ một định nghĩa meta tại hphp / phân tích cú pháp / hphp.ll. Việc một số quy định trong tập tin này mô tả làm thế nào để nhận token-từ. Ví dụ, gặp phải "tiếng vang" bên trong của một echo $ a + $ b; Nếu chúng ta chạy này qua token_get_all (), chúng tôi nhận được các trình tự sau của thẻ: // T_OPEN_TAG này đại diện cho " T_WHITESPACE (" n") T_ECHO T_WHITESPACE ("") T_VARIABLE ("USD") T_WHITESPACE ("") '+' T_WHITESPACE ("") T_VARIABLE ("$ b") ';' Chú ý rằng đơn giản, thẻ nhân vật duy nhất không được đặt một cái tên, họ chỉ đơn giản là đi qua như các nhân vật mà họ đang Bây giờ chúng ta có một tập hợp các thẻ máy có thể đọc được, chúng ta có thể bắt đầu xây dựng biểu thức từ họ.. Bước Hai: Phân tích cú pháp Các quy tắc tìm thấy trong hphp / phân tích cú pháp / hphp.y là . được sử dụng bởi Bison để tạo ra một công cụ phân tích cú pháp này phần của các trình biên dịch hiện đọc thẻ, thức ăn cho nó bằng các lexer, để xây dựng một Abstract Syntax Tree (AST); Một đại diện của các biểu thức chứa trong kịch bản PHP của bạn. Xem xét một chút nhiều hơn đoạn phức tạp của kịch bản: echo "? Có 1 + 2 bằng 3"; if ((1 + 2) == 3) { echo "! Có nó"; } else { echo "Không có nó không ..." ; } Chúng tôi tạo ra một AST mà trông giống như sau: EchoStatement ConstantExpression ("Có 1 + 2 bằng nó! ") EchoStatement ConstantExpression ("Không có nó không ...") Ở đây chúng ta có thể thấy rằng thẻ vô tổ chức của chúng tôi đã được nhóm lại với nhau như vậy mà các biểu thức logic được lồng nhau thành các nhóm theo cách chúng ta, những con người, sẽ mong đợi để làm . cảm giác của họ này là trung tâm của các định nghĩa của ngôn ngữ và đó là những gì sẽ quyết định tất cả mọi thứ từ đơn đặt hàng của hoạt động (được ưu tiên) sự kết hợp của một tuyên bố thành khác. Bước Two and a Half: Tối ưu hóa tôi gọi hai này và một nửa , bởi vì chúng ta không thực sự thay đổi đại diện của mã kịch bản gốc tại thời điểm này. Thay vào đó, chúng tôi đang giảm hiện nào có thể được đơn giản hóa trước khi mã là bao giờ chạy (cũng được, một số điều này xảy ra ở bước 3). Ví dụ , chúng ta có thể đánh giá "1 + 2" đến 3, và AST của chúng tôi trở thành: EchoStatement ConstantExpression ("Có 1 + 2 bằng nó ")! EchoStatement ConstantExpression ("Không có nó không ...") Sau đó đánh giá "3 == 3" thành "true": EchoStatement ConstantExpression ("? Có 1 + 2 bằng 3") IfBranchStatement ConstantExpression (true) EchoStatement ConstantExpression ("Có nó!") EchoStatement ConstantExpression ("Không có nó không ...") Bây giờ chúng ta biết rằng chúng ta sẽ luôn có những chi nhánh thật và không bao giờ chi nhánh giả, vì vậy: EchoStatement ConstantExpression ("Liệu 1 + 2 bằng 3? ") EchoStatement ConstantExpression ("Có nó!") Trong đó tất nhiên có thể được giảm đến một EchoStatement duy nhất. Kịch bản này rõ ràng là một chút giả tạo để thể hiện giai đoạn tối ưu này, nhưng nó có giá trị nhìn thấy những gì có thể. Câu hỏi: Sự khác nhau giữa một Tuyên bố và một biểu thức là gì? Trả lời:. báo cáo là thiết bị đầu cuối, họ không có giá trị sản lượng được tiêu thụ bởi các nút sau trong AST, và do phải xuất hiện chỉ như là anh chị em ruột cấp cao nhất trong một HHVM AST Expressions sản xuất một giá trị như vậy, và có thể, như vậy, được sử dụng như là đầu vào cho các biểu thức khác hoặc báo cáo. Bước 3: Lập để bytecode Các giai đoạn cuối cùng của "mặt trước" trình biên dịch của chúng tôi mang đến cho chúng ta qua hphp / biên dịch / phân tích / emitter.cpp nơi chúng tôi quay AST đó vào bytecode mà có thể được thực hiện bằng cách thông dịch viên của HHVM. Chúng tôi làm điều này bằng cách "thăm" mỗi biểu từ các cơ sở của các cây trở lên, với mỗi biểu thức mô tả chính nó như là một loạt các bytecode. Ví dụ, một echo tuyên bố có một hoặc nhiều trẻ em trong đó, nhờ được biểu hiện bản thân, có thể trong lần lượt được tạo thành từ các biểu thức khác. Đối với mỗi biểu thức con của một EchoStatement chúng tôi lần đầu đến biểu thức, trong đó sẽ sản xuất một số bytecode dẫn đến một giá trị duy nhất đã được đẩy lên "stack". Chúng tôi sau đó phát ra một "Print" bytecode sẽ bật giá trị đó, xuất ra nội dung của nó, sau đó đẩy nó giá trị kết quả riêng của int (1). Bởi vì Echo là một tuyên bố, tuy nhiên, và do đó không có giá trị sản lượng, chúng tôi ngay lập tức bật nó trở lại bằng cách sử dụng PopC. Câu hỏi: Tại sao đẩy một giá trị vào stack nếu Echo là một tuyên bố, và không phải là một biểu thức trả lời: Bởi vì Echo lận bằng cách dùng lại các bytecode cùng ý nghĩa cho các print () biểu hiện, mà nếu bạn sẽ nhớ lại, không có một giá trị trả về. Các hướng dẫn PopC ném mà đi như không cần thiết. Câu hỏi: Không phải là không hiệu quả? trả lời:. Có, nhưng chỉ trong chế độ interp, điều này sẽ biến mất khi chúng ta đạt đến JIT Chúng ta hãy nhìn vào mà trong thực tế: echo "Hello" , $ name, " n"; Tạo một AST cho một EchoStatement với ba biểu thức con: EchoStatement ConstantExpression ("Hello") VariableExpression ($ name) ConstantExpression (" n") Mà thăm EchoStatement, người trong lượt thăm mỗi con : String "Hello" // Push "Hello" vào stack Print // Tiêu thụ "Hello" từ stack, và push int (1) Tiêu thụ PopC // int (1) và ném nó đi CGetL 0 // Lấy địa phương # 0 ($ name) từ bảng ký hiệu và đẩy nó vào stack Print // vv ... vv ... vv ... PopC String " n" In PopC Tạo Bytecode Dumps Để xem mã số byte cho một kịch bản nhất định, tương tự như cách PHP cho phép bạn xem Opcodes qua VLD, bạn có hai lựa chọn. Đầu tiên là để chạy nó thông qua HHVM như thường lệ với -vEval.DumpBytecode = 1 tùy chọn. Định dạng này được thiết kế cho khả năng đọc của con người và có ý nghĩa chính cho sự phát triển thời gian chạy và gỡ lỗi. Ví dụ, một chương trình Hello World đơn giản: echo "Hello World n"; trở thành: $ hhvm -vEval.DumpBytecode = 1 /tmp/hello-world.php Pseudo-chính tại 0 maxStackCells: 1 numLocals: 0 numIterators: 0 / / line 2 0: String "Hello World n" 5: In 6: PopC 7: Int 1 16: RetC Pseudo-chính tại 0 maxStackCells: 1 numLocals: 0 numIterators: 0 Nếu bạn đang tự hỏi về Int 1 / RetC lúc kết thúc, đó là tiềm ẩn "return 1;" mà đi kèm ở cuối của mỗi tập tin (mà không trả lại một cái gì đó một cách rõ ràng) để bao gồm biểu có giá trị trả. Hãy nhớ rằng, các "phạm vi toàn cầu" hoạt động như một hàm không tên mà được gọi ngầm khi khởi động, và chia sẻ phạm vi biến của nó với tất cả các chức năng psuedo-chính cấp cao khác. Lựa chọn thứ hai là gần như giống hệt nhau trong invocation, nhưng sản lượng sản xuất đáng chú ý khác: $ hhvm -vEval.DumpHhas = 1 /tmp/hello.php .filepath "/tmp/hello.php"; .main { String "Hello World n" In PopC Int 1 RetC } Điều này thực sự là ngôn ngữ thứ ba của HHVM (PHP và Hack là lần đầu tiên hai), HipHop hội. Ngôn ngữ này ẩn, mà thường chỉ được công nhận trong các tập tin Systemlib, cho phép các nhà phát triển để vượt qua hai bước đầu tiên của việc biên dịch và tạo ra bytecode liệu. Bạn có thể tìm thấy một ví dụ hiện điều này (hphp / hệ thống / php / array_map.hhas) trong việc thực hiện array_map () nơi HHVM sử dụng một bytecode tùy chỉnh thiết kế rõ ràng để thực hiện các chức năng hiệu quả hơn. Để biết thêm về ý nghĩa và mục đích sử dụng của tất cả các của HHVM bytecode, xem hphp / doc / bytecode.specification. Lưu ý: Bạn không nên phát triển các ứng dụng hoặc các thư viện trong HHAS. Cú pháp của HHAS và hành vi của bytecode cụ thể không được đảm bảo giữa các phiên bản của HHVM và hầu như không bao giờ bất kỳ lợi ích để có được qua viết kịch bản trong PHP bình thường. Phạm vi bảo hiểm trong bài viết này là có nghĩa là cho mục đích thông tin. Bước 3.5: HHBBC Optimization Các tối ưu, chúng tôi thực hiện trong bước 2.5 đã nhanh chóng, mọi thứ rõ ràng tốt đẹp chúng tôi có thể lắc ra ngoài trong quá trình bình thường của on-the-fly đang biên soạn, nhưng chúng lại không phải là cải tiến duy nhất chúng ta có thể làm. Khi xây dựng trong chế độ RepoAuthoritative, nó có thể nhìn vào chương trình một chút sâu hơn và cắt ra, ngay cả việc không cần thiết nữa. Hãy xem xét kịch bản này: define ('FOO "," Hello World n "); vang FOO; $ x = 1 + 2; if ($ x == 3) { echo "X là ba n"; } else { vang "X không phải là ba n"; } Một lần nữa, một con người có thể nhìn vào điều này và biết rằng một phiên bản đơn giản hóa của kịch bản này có thể giảm rất nhiều, nhưng theo dõi các trạng thái đó là quá phức tạp cho các trình biên dịch đầu tiên vượt qua để làm. Vì vậy, chúng ta thấy bytecode tìm kiếm như thế này: $ hhvm -vEval.DumpBytecode = 1 /tmp/three.php // dòng 3 0: String "Hello World n" 5: DefCns "FOO" 10: PopC // dòng 4 11: thần kinh trung ương "FOO" 16: In 17: PopC // dòng 6 18: Int 3 27: SETL 0 29: PopC // dòng 7 30: Int 3 39: CGetL2 0 41: Eq 42: JmpZ 17 (59) // dòng 8 47: String "X là ba n" 52:
đang được dịch, vui lòng đợi..