Ruby Internal - Code Ruby của bạn được thực thi như thế nào - Compilation (Phần 3)
Ruby
106
hardcore
17
$10
1
White

Cẩm Huỳnh viết ngày 08/10/2016

Về phần trước

Đây là một bài nằm trong chuỗi #hardcore của nhóm Ruby Vietnam

Xem phần 2 tại đây
Xem phần 1 tại đây

Ruby Compilation

Như mình đã giới thiệu ở phần 1, Ruby compile code Ruby thành bytecode, còn gọi là YARV (Yet Another Ruby VM) instructions, và được thực thi ở YARV. Ở phần này chúng ta sẽ tìm hiểu quá trình compile đó diễn ra như thế nào?

Cấu trúc của YARV instruction

YARV bản chất là một Stack-oriented VM, nói một cách khác YARV nó sử dụng một value stack (chứa các args và giá trị trả về) để thực thi "YARV instruction" (từ đây mình sẽ gọi là instruction cho nhanh), nên các instruction được xây dựng tận dụng stack này (push giá trị tính được vào stack hoặc pop giá trị cần tìm ra khỏi stack).

Ruby là một ngôn ngữ OOP hoàn toàn, tất cả các lệnh gọi đều có dạng receiver.method(arguments), vì thế instructions được sinh ra luôn đi theo nguyên tắc sau.

  1. Đẩy receiver vào stack.
  2. Đẩy arguments vào stack.
  3. Đẩy method vào stack.

Ví dụ với lệnh 5 + 4, YARV instructions được sinh ra như sau:

putobject   5
putobject   4
opt_plus    <callinfo!mid:+, argc:1, ARGS_SKIP>

ARGS_SKIP ở đây nhằm giúp YARV biết được tham số được truyền vào là những giá trị đơn giản (không phải là block hay một array các tham số).

Và với lệnh puts 9, YARV instructions được sinh ra như sau:

putself
putobject           9
opt_send_simple     <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>

NOTE: Như ta đã biết puts là một lệnh từ module Kernel mà bất kì một Ruby class nào cũng include, nên receiver ở đây được hiểu là self, đó là lý đó vì sao lệnh putself được đưa vào vị trí receiver ở trên.

Để xuất YARV instruction như trên bạn có thể dùng lệnh

RubyVM::InstructionSequence.compile("your-ruby-code-goes-here").diasam

Local Table

Khi compiler chạy, thông tin về các biến, tham số được Ruby lưu ở một nơi khác gọi là Local Table.

Giá sử ta có một đoạn code Ruby sau:

a = "Viet"
b = "Nam"
puts a + b

Để sử dụng local table Ruby dùng hai lệnh setlocal (dùng để gán) và getlocal (dùng để tham chiếu)

Với đoạn code trên đầu tiên, áp dụng kiến thức ở phần trên và phần này, ta có instruction như sau.

# YARV Instructions                                 # Local Table

putstring   "Viet"
setlocal    3                                       [3] a
# Tương tự Với b
putstring   "Nam"
setlocal    2                                       [2] b
putself
getlocal    3
getlocal    2
send        :+
send        :puts
leave

Scoping

Ở 2 mục trên ta đã tìm hiểu các YARV instruction đơn giản và local table, ở mục này ta sẽ tìm hiểu compiler sẽ làm gì với các lệnh Ruby bao gồm scope (như block với tham số).

10.times do |n|
  puts n
end

Cách Ruby làm là nó sẽ chia đoạn code trên thành 2 block khác nhau (với 2 local table khác nhau), mình tạm gọi là outer block gồm 10.times { ... }inner block gồm { |n| puts n }

# YARV Instructions                                                     # Local Table
putobject   10
send        <callinfo!mid:times, argc:0, block:block in <compiled>>
# Send đến method :times, không tham số với block (inner block bên dưới)
# YARV Instructions                             # Local Table
#                                               [2] n<Arg>
# putself
# getlocal  2
# send      :puts

<Arg> ở trên để đánh dấu cho YARV biết rằng n là một tham số thông thường.

Ngoài ra còn một số kí hiệu cho các loại tham số khác như:

  • <Arg>: như trên, dành cho tham số thông thường. Ví dụ: def foo(a)
  • <Rest>: dành cho các tham số dạng argument (splat *). Ví dụdef foo(*args)
  • <Post>: dành cho các tham số sau splat arguments. Ví dụ: def foo(*args, a)
  • <Block>: dành cho các tham số block được truyền vào bằng kí hiệu &. Ví dụ def foo(&block)
  • <Opt=i>: dành cho các tham số có giá trị mặc định. Ví dụ def foo(a=1)

Kết luận

Còn khá nhiều điều khá thú vị về các lệnh bytecode của Ruby mà một bài viết như thế này không đủ để cover hết, các bạn có thể tìm hiểu thêm tại trang chủ của YARV nhé.

Các bạn có thể tham gia thảo luận về bài viết ở bên dưới hoặc thông qua chat group của nhóm Ruby Vietnam.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Cẩm Huỳnh

35 bài viết.
279 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
36 9
(Ảnh) Vì sao lại là Bật Đèn? Ai từng đọc qua Tắt Đèn hẳn đã biết tác phẩm được kết thúc bằng tình huống: Buông tay, chị vội choàng dậy, mở cửa...
Cẩm Huỳnh viết 9 tháng trước
36 9
White
32 25
Vừa rồi mình vừa tiết kiệm được $5 mỗi tháng sau khi migrate cái (Link) từ Digital Ocean sang Heroku Free Dyno. (Ảnh) Kết quả thật mĩ mãn vì hầu ...
Cẩm Huỳnh viết 7 tháng trước
32 25
White
31 4
(Ảnh) Bắt đầu một câu chuyện Vài ngày trước một người bạn nhờ tui xem giùm trang web của nó bị người dùng phàn nàn là hack tiền ảo. Tui mò vào ...
Cẩm Huỳnh viết 4 tháng trước
31 4
Bài viết liên quan
White
8 6
Chưa xem phần 2? Xem (Link) Trong bài viết này tôi giới thiệu cho các bạn về khái niệm function arity, một cách gọi mĩ miều của số lượng argument ...
Lơi Rệ viết hơn 2 năm trước
8 6
White
8 1
Tiếp theo (Link) Mình sẽ hướng dẫn cách test căn bản cho API mình tạo. Thật ra mà nói thì mình phải viết test trước khi làm nhưng mà để tránh việc...
My Mai viết hơn 2 năm trước
8 1
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
35 bài viết.
279 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!