Ruby Internal - Code Ruby của bạn được thực thi như thế nào - Compilation (Phần 3)
Ruby
118
hardcore
18
$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

47 bài viết.
454 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
66 5
image cover]imgcover] “Make it work, make it right, make it fast.” Bạn vừa viết xong một ứng dụng web :tada:. Mọi thứ chạy ổn. Code cũng đã được...
Cẩm Huỳnh viết 10 tháng trước
66 5
White
47 26
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 2 năm trước
47 26
White
44 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 hơn 2 năm trước
44 9
Bài viết liên quan
White
12 0
Hadoop là cái gì vậy? “Hadoop là một framework nguồn mở viết bằng Java cho phép phát triển các ứng dụng phân tán có cường độ dữ liệu lớn một cách ...
hell nguyen viết gần 3 năm trước
12 0
White
23 3
Tiếp nối phần 1 http://kipalog.com/posts/7concurrencymodelsinsevenweekphan1. Trong phần này chúng ta sẽ tiếp tục tìm hiểu về mô hình ThreadLock th...
huydx viết 3 năm trước
23 3
{{like_count}}

kipalog

{{ comment_count }}

bình luận

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


White
{{userFollowed ? 'Following' : 'Follow'}}
47 bài viết.
454 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á!