Eval of Julia code
Julia Dilinin kodu nasıl çalıştırdığını öğrenmenin en zor kısımlarından biri, bir kod bloğunu yürütmek için tüm parçaların nasıl bir araya geldiğini öğrenmektir.
Her bir kod parçası genellikle, potansiyel olarak tanıdık olmayan isimlerle birçok adımda bir yolculuk yapar, bunlar arasında (herhangi bir sırayla): flisp, AST, C++, LLVM, eval
, typeinf
, macroexpand
, sysimg (veya sistem görüntüsü), bootstrapping, derleme, ayrıştırma, yürütme, JIT, yorumlama, kutulama, kutudan çıkarma, içsel fonksiyon ve ilkel fonksiyon bulunur; bunların hepsi istenen sonuca (umarım) dönüşmeden önce.
Julia Execution
Tüm sürecin 10,000 fitlik görünümü aşağıdaki gibidir:
- Kullanıcı
julia
başlatır. cli/loader_exe.c
dosyasındakimain()
C fonksiyonu çağrılır. Bu fonksiyon, komut satırı argümanlarını işler,jl_options
yapısını doldurur veARGS
değişkenini ayarlar. Ardından Julia'yı başlatır (şu çağrıyı yaparak:julia_init
ininit.c
), bu da daha önce derlenmiş sysimg dosyasını yükleyebilir. Son olarak, kontrolü Julia'ya devretmek içinBase._start()
çağrısını yapar._start()
kontrolü devraldığında, sonraki komut dizisi verilen komut satırı argümanlarına bağlıdır. Örneğin, bir dosya adı sağlandıysa, o dosyayı çalıştırmaya devam edecektir. Aksi takdirde, etkileşimli bir REPL başlatacaktır.- Kullanıcının REPL ile etkileşimi hakkında detayları atlayarak, programın çalıştırmak istediği bir kod bloğuna ulaştığını söyleyelim.
- Eğer çalıştırılacak kod bloğu bir dosyada ise,
jl_load(char *filename)
dosyayı yüklemek için çağrılır ve parse ile yüklenir. Her kod parçası daha sonraeval
ile çalıştırılması için geçirilir. - Her bir kod parçası (veya AST), sonuçlara dönüştürmek için
eval()
'ya iletilir. eval()
her bir kod parçasını çalıştırmaya çalışırjl_toplevel_eval_flex()
.jl_toplevel_eval_flex()
kodun "toplevel" bir eylem olup olmadığını (örneğinusing
veyamodule
gibi) belirler; bu, bir fonksiyonun içinde geçersiz olur. Eğer öyleyse, kodu toplevel yorumlayıcısına iletir.jl_toplevel_eval_flex()
ardından expands makroları ortadan kaldırmak ve AST'yi daha basit bir şekilde çalıştırmak için "aşağı indirmek" amacıyla kullanılan kod.jl_toplevel_eval_flex()
daha sonra AST'yi JIT derleyip derlemeyeceğine veya doğrudan yorumlayacağına karar vermek için bazı basit sezgiler kullanır.- Çalışmanın büyük kısmı kodu yorumlamak için
eval
ininterpreter.c
tarafından yönetilmektedir. - Eğer bunun yerine kod derlenirse, işin büyük kısmı
codegen.cpp
tarafından halledilir. Bir Julia fonksiyonu, belirli bir argüman türü seti ile ilk kez çağrıldığında, type inference o fonksiyon üzerinde çalıştırılacaktır. Bu bilgi, daha hızlı kod üretmek için codegen adımında kullanılır. - Sonunda, kullanıcı REPL'i kapatır veya programın sonuna ulaşılır ve
_start()
metodu döner. - Çıkmadan hemen önce,
main()
jl_atexit_hook(exit_code)
çağrısını yapar. Bu, Julia içindeatexit()
ile kayıtlı olan herhangi bir fonksiyonu çağıranBase._atexit()
'i çağırır. Ardındanjl_gc_run_all_finalizers()
çağrısını yapar. Son olarak, tümlibuv
handle'larını nazikçe temizler ve bunların boşalmasını ve kapanmasını bekler.
Parsing
Julia ayrıştırıcısı, femtolisp'te yazılmış küçük bir lisp programıdır; kaynak kodu, Julia içinde src/flisp içinde dağıtılmaktadır.
Arayüz işlevleri esasen jlfrontend.scm
içinde tanımlanmıştır. ast.c
içindeki kod, bu geçişi Julia tarafında yönetmektedir.
Bu aşamada ilgili diğer dosyalar julia-parser.scm
, Julia kodunu tokenleştiren ve bir AST'ye dönüştüren, ve julia-syntax.scm
, karmaşık AST temsillerini daha basit, "aşağıya indirilmiş" AST temsillerine dönüştüren dosyalardır; bu temsiller analiz ve yürütme için daha uygundur.
Eğer Julia'yı tamamen yeniden inşa etmeden ayrıştırıcıyı test etmek istiyorsanız, ön yüzü kendi başına aşağıdaki gibi çalıştırabilirsiniz:
$ cd src
$ flisp/flisp
> (load "jlfrontend.scm")
> (jl-parse-file "<filename>")
Macro Expansion
eval()
bir makro ile karşılaştığında, ifadenin değerlendirilmeden önce o AST düğümünü genişletir. Makro genişletme, 4d61726b646f776e2e436f64652822222c20226576616c28292229_40726566
(Julia'da) ile jl_macroexpand()
(flisp'te yazılmış) parser fonksiyonu ve Julia makrosu (başka ne ile yazılabilir ki - Julia) aracılığıyla fl_invoke_julia_macro()
ile elden ele geçişi içerir ve geri döner.
Tipik olarak, makro genişletme, Meta.lower()
/jl_expand()
çağrısı sırasında birinci adım olarak çağrılır, ancak doğrudan macroexpand()
/jl_macroexpand()
çağrısıyla da tetiklenebilir.
Type Inference
Tip çıkarımı, Julia'da typeinf()
in compiler/typeinfer.jl
ile uygulanır. Tip çıkarımı, bir Julia fonksiyonunu inceleme ve her bir değişkeninin türleri için sınırlar belirleme sürecidir; ayrıca fonksiyonun dönüş değerinin türü için de sınırlar belirler. Bu, bilinen değiştirilemez değerlerin kutusunun açılması ve alan ofsetleri ile fonksiyon işaretçileri gibi çeşitli çalışma zamanı işlemlerinin derleme zamanı yükseltilmesi gibi birçok gelecekteki optimizasyonu mümkün kılar. Tip çıkarımı, sabit yayılımı ve iç içe alma gibi diğer adımları da içerebilir.
JIT Code Generation
Codegen, bir Julia AST'sini yerel makine koduna dönüştürme sürecidir.
JIT ortamı, jl_init_codegen
in codegen.cpp
ile erken bir çağrı ile başlatılır.
Talep üzerine, bir Julia yöntemi emit_function(jl_method_instance_t*)
fonksiyonu tarafından yerel bir işleve dönüştürülür. (Not: MCJIT kullanıldığında (LLVM v3.4+), her işlev yeni bir modüle JIT edilmelidir.) Bu fonksiyon, tüm işlev yayımlanana kadar emit_expr()
fonksiyonunu özyinelemeli olarak çağırır.
Bu dosyanın geri kalan kısmı, belirli kod desenlerinin çeşitli manuel optimizasyonlarına ayrılmıştır. Örneğin, emit_known_call()
birçok ilkel fonksiyonu (tanımlı olan builtins.c
) argüman türlerinin çeşitli kombinasyonları için inline yapmayı bilir.
Kod oluşturmanın diğer kısımları çeşitli yardımcı dosyalar tarafından yönetilmektedir:
JIT fonksiyonları için geri izleme işlemlerini yönetir
abi_*.cpp
dosyaları ile birlikte ccall ve llvmcall FFI'yi yönetir.Çeşitli düşük seviyeli içsel işlevlerin yayılımını yönetir
System Image
Sistem görüntüsü, bir dizi Julia dosyasının önceden derlenmiş bir arşividir. Julia ile dağıtılan sys.ji
dosyası, sysimg.jl
dosyasını çalıştırarak oluşturulan bir sistem görüntüsüdür ve sonuçta elde edilen ortamı (Türler, Fonksiyonlar, Modüller ve tanımlı tüm diğer değerler dahil) bir dosyaya serileştirir. Bu nedenle, Main
, Core
ve Base
modüllerinin (ve başlatma işleminin sonunda ortamda bulunan diğer her şeyin) dondurulmuş bir versiyonunu içerir. Bu serileştirici/serileştirici çözümleyici, jl_save_system_image
/jl_restore_system_image
in staticdata.c
tarafından uygulanmaktadır.
Eğer bir sysimg dosyası yoksa (jl_options.image_file == NULL
), bu aynı zamanda komut satırında --build
seçeneğinin verildiğini de gösterir, bu nedenle nihai sonuç yeni bir sysimg dosyası olmalıdır. Julia başlatılırken, minimal Core
ve Main
modülleri oluşturulur. Ardından, mevcut dizinden boot.jl
adlı bir dosya değerlendirilir. Julia daha sonra komut satırı argümanı olarak verilen herhangi bir dosyayı değerlendirir ve sona ulaşana kadar devam eder. Son olarak, elde edilen ortamı gelecekteki bir Julia çalışması için başlangıç noktası olarak kullanılmak üzere bir "sysimg" dosyasına kaydeder.