yomikomu(0.4.1)のコードリーディングをしました。
コードリーディングの前にRubyVM::InstructionSequence.load_iseqの説明をします。
Rubyでloadやrequireをすると
- RubyVM::InstructionSequence.load_iseqが定義されている場合、load_iseqを実行
- 戻り値がnilであればファイルから読み込んでパース・コンパイルして命令列に変換して実行
- 戻り値がnilでなければ、それを命令列として実行
- load_iseqが定義されていない場合は、ファイルを読みこんでパース・コンパイルして命令列に変換して実行
というフローでファイルがロードされます。
例えば以下のようなコードで
1 2 3 4 5 6 7 8 9 10 |
#!/usr/bin/env ruby class RubyVM::InstructionSequence def self.load_iseq(path) puts '*****' puts path end end require 'csv' |
これを実行すると、requireの度にRubyVM::InstructionSequence.load_iseqが呼ばれ、以下のように出力されます。
1 2 3 4 5 6 7 8 9 10 |
***** /Users/mtajitsu/.rbenv/versions/2.5.0/lib/ruby/2.5.0/csv.rb ***** /Users/mtajitsu/.rbenv/versions/2.5.0/lib/ruby/2.5.0/forwardable.rb ***** /Users/mtajitsu/.rbenv/versions/2.5.0/lib/ruby/2.5.0/forwardable/impl.rb ***** /Users/mtajitsu/.rbenv/versions/2.5.0/lib/ruby/2.5.0/English.rb ***** /Users/mtajitsu/.rbenv/versions/2.5.0/lib/ruby/2.5.0/date.rb |
上の例だとputsの戻り値=nilなので通常通りファイルがコンパイルされますが、ここでiseqを返してあげることでRubyに命令列を直接読み込ませることが可能です。
それではコードを読んでいきます。yomikomuはlib/yomikomu.rbのワンファイルになります。
環境変数が設定されている場合、RubyVM::InstructionSequence.load_iseqが呼ばれます。load_iseqはYomikomu::STORAGE.load_iseqを呼びます。
1 2 3 4 5 6 7 |
class RubyVM::InstructionSequence if ENV['YOMIKOMU_YOMIKOMANAI'] != 'true' def self.load_iseq fname ::Yomikomu::STORAGE.load_iseq(fname) end end end |
Yomikomu::STORAGEはストレージのクラスで環境変数によって変わります。
1 2 3 4 5 6 7 8 9 10 |
STORAGE = case storage = ENV['YOMIKOMU_STORAGE'] when 'fs' FSStorage.new when 'fsgz' # ... when nil FSStorage.new else raise "Unknown storage type: #{storage}" end |
デフォルトはFSStrogareなので、以下FSStorageに関して見ていきます。
FSStorageはBasicStorageを継承しています。BasicStorage#load_iseqは以下のように定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class BasicStorage def load_iseq fname iseq_key = iseq_key_name(fname) if compiled_iseq_exist?(fname, iseq_key) && compiled_iseq_is_younger?(fname, iseq_key) ::Yomikomu::STATISTICS[:loaded] += 1 ::Yomikomu.debug{ "load #{fname} from #{iseq_key}" } binary = read_compiled_iseq(fname, iseq_key) iseq = RubyVM::InstructionSequence.load_from_binary(binary) # p [extra_data(iseq.path), RubyVM::InstructionSequence.load_from_binary_extra_data(binary)] # raise unless extra_data(iseq.path) == RubyVM::InstructionSequence.load_from_binary_extra_data(binary) iseq elsif YOMIKOMU_AUTO_COMPILE compile_and_store_iseq(fname, iseq_key) else ::Yomikomu::STATISTICS[:ignored] += 1 ::Yomikomu.debug{ "ignored #{fname}" } nil end end |
コンパイルしたiseqが既に存在していて(compiled_iseq_exist?)、直近にコンパイルされたものであれば(compiled_iseq_is_younger?)、コンパイルされたiseqを読み込みRubyVM::InstructionSequenceのインスタンスを返します。そうでなければ、環境変数によって設定されるYOMIKOMU_AUTO_COMPILEがtrueであれば自動的にコンパイルを行い、YOMIKOMU_AUTO_COMPILEが設定されていなければnilを返し通常のファイル読み込み・パース・コンパイルを行います。
compiled_iseq_exist?やcompiled_iseq_is_younger?は以下のように定義されています。
1 2 3 4 5 6 7 8 |
class FSStorage < BasicStorage def compiled_iseq_exist? fname, iseq_key File.exist?(iseq_key) end def compiled_iseq_is_younger? fname, iseq_key File.mtime(iseq_key) >= File.mtime(fname) end |
コンパイルしたファイルが存在し、コンパイル時よりもあとにファイルが更新されていない場合に読み込むようになっています。
read_compiled_iseqはFile.binreadでファイルの読み込みをしています。
1 2 3 |
def read_compiled_iseq fname, iseq_key File.binread(iseq_key) end |
コメントを残す