386 lines
15 KiB
Markdown
386 lines
15 KiB
Markdown
# saQut Derleyici — Mimari Fikirler ve Karar Kaydı (ADR)
|
||
|
||
> Bu belge, saQut derleyicisinin backend stratejisi, mimari kararları ve
|
||
> gelecek yol haritası hakkında kapsamlı analizleri içerir.
|
||
> Her kararın **neden** alındığı, alternatiflerin neden elendiği ve
|
||
> gelecekte hangi koşullarda tekrar değerlendirileceği belirtilmiştir.
|
||
|
||
---
|
||
|
||
## ADR-001: Backend Stratejisi
|
||
|
||
### Bağlam
|
||
|
||
saQut derleyicisi şu anda:
|
||
- **Lexer**: Karakter seviyesinde tarama (src/lexer/lexer.hpp)
|
||
- **Tokenizer**: Token üretimi, 6 token tipi, yorum satırı desteği (src/tokenizer/tokenizer.hpp)
|
||
- **Parser**: Pratt parser ile ifade ayrıştırma + recursive descent ile statement ayrıştırma (src/parser/parser.hpp)
|
||
- **AST**: Program, FunctionDecl, Block, değişken tanımlama, if/for/while/do-while/return, expression node'ları (src/parser/ast.hpp)
|
||
- **IR**: Sadece temel matematik opcode'ları (mathadd/sub/mul/div) ve declare (src/ir/ir.hpp)
|
||
|
||
Henüz çalışan bir backend yok. Kod üretimi (code generation) aşaması boş.
|
||
|
||
### Değerlendirilen Seçenekler
|
||
|
||
#### 1. LLVM (Low Level Virtual Machine)
|
||
|
||
**Nedir**: Derleyici altyapısı. C/C++/Rust/Swift gibi dillerin kullandığı endüstri standardı.
|
||
|
||
**Artıları**:
|
||
- Agresif optimizasyonlar (loop unrolling, inlining, vectorization, LTO)
|
||
- Çok platformlu kod üretimi (x86, ARM, RISC-V, WebAssembly, GPU)
|
||
- JIT ve AOT (Ahead-of-Time) derleme desteği
|
||
- Olgun hata ayıklama bilgisi üretimi (DWARF, PDB)
|
||
- Geniş araç zinciri (llc, opt, lld, clang)
|
||
- GC (Garbage Collection) desteği için statepoint mekanizması
|
||
|
||
**Eksileri**:
|
||
- **Bağımlılık boyutu**: LLVM kütüphaneleri ~1GB+ disk alanı kaplar
|
||
- **Derleme hızı**: LLVM'nin kendi derlenmesi dakikalar alır, link zamanı yavaştır
|
||
- **Öğrenme eğrisi**: LLVM IR karmaşıktır, C++ API'si ağırdır
|
||
- **Hata ayıklama zorluğu**: LLVM IR seviyesinde hata bulmak zordur
|
||
- **Hafif projeler için aşırı**: saQut gibi deneysel bir derleyici için "sineği top ile vurmak" olur
|
||
- **Build sistemi karmaşası**: LLVM'nin kendi build sistemi CMake ile entegre olur, proje yapısını domine eder
|
||
|
||
**Karar**: ❌ Şimdilik kullanılmamalı. Deneysel aşamada çok ağır. Dil olgunlaştığında ve optimizasyon ihtiyacı somutlaştığında tekrar değerlendirilebilir.
|
||
|
||
---
|
||
|
||
#### 2. GNU Lightning (JIT)
|
||
|
||
**Nedir**: Anında makine kodu üreten hafif kütüphane. Register tabanlı, hedef mimariye göre kod üretir.
|
||
|
||
**Artıları**:
|
||
- Çok hafif (birkaç yüz KB)
|
||
- Anında kod üretimi ve çalıştırma (JIT)
|
||
- x86, ARM, MIPS, PowerPC gibi mimarilere kod üretebilir
|
||
- Kod üretimi hızlıdır (optimizasyon yapmaz, direkt çeviri)
|
||
- C API'si basit ve temiz
|
||
|
||
**Eksileri**:
|
||
- **Optimizasyon yok**: Constant folding, dead code elimination gibi temel optimizasyonlar bile yok
|
||
- **Bakım durumu belirsiz**: Proje uzun süredir aktif geliştirilmiyor
|
||
- **Sınırlı tip desteği**: Karmaşık veri tipleri ve struct'lar için manuel işlem gerekir
|
||
- **Hata toleransı düşük**: Yanlış register kullanımı sessizce yanlış kod üretir
|
||
- **Portability sorunları**: Her platformda aynı performansı vermez
|
||
- **GC ve exception handling desteği yok**
|
||
|
||
**Karar**: ⚠️ Prototip aşamasında kullanılabilir ancak üretim için uygun değil.
|
||
|
||
---
|
||
|
||
#### 3. Sıfırdan Custom Backend (Go yaklaşımı)
|
||
|
||
**Nedir**: Go dilinin yaptığı gibi, kendi kod üreticini yazmak.
|
||
|
||
**Go'nun yaklaşımı**:
|
||
- Go başlangıçta Plan 9 assembler'dan kendi assembler'ına geçti
|
||
- Kendi register allocator, instruction selector ve optimizer'ını yazdı
|
||
- Sonuç: LLVM bağımlılığı yok, hızlı derleme, tam kontrol
|
||
- Go 1.21+ ile PGO (Profile-Guided Optimization) bile eklendi
|
||
|
||
**Artıları**:
|
||
- **Tam kontrol**: Her şeyi istediğin gibi tasarlayabilirsin
|
||
- **Bağımlılık yok**: Dış kütüphane gerektirmez
|
||
- **Hızlı derleme**: Optimizasyon seviyesini sen belirlersin
|
||
- **Dil ile entegrasyon**: saQut diline özel optimizasyonlar yapabilirsin
|
||
- **Öğrenme değeri**: Derleyicinin her katmanını anlarsın
|
||
|
||
**Eksileri**:
|
||
- **Çok iş**: Register allocation, instruction selection, calling convention, stack frame yönetimi, peephole optimization... hepsini sıfırdan yazmak aylar sürer
|
||
- **Platform bağımlılığı**: Her hedef mimari için ayrı kod üretici gerekir
|
||
- **Optimizasyon kalitesi**: LLVM seviyesinde optimizasyon yapmak yıllar alır
|
||
- **Bakım yükü**: Tüm backend hataları senin sorumluluğunda
|
||
|
||
**Karar**: ✅ **Önerilen uzun vadeli strateji**. Aşamalı olarak inşa edilmeli:
|
||
1. Aşama: C koduna transpile et (hızlı prototip, hemen çalışır)
|
||
2. Aşama: Basit bir register allocator + x86-64 kod üretici
|
||
3. Aşama: Orta seviye optimizasyonlar ekle
|
||
4. Aşama: ARM64 desteği ekle
|
||
|
||
---
|
||
|
||
#### 4. QBE (Quick Backend)
|
||
|
||
**Nedir**: LLVM'den 10 kat daha hızlı, hafif bir derleyici backend'i. cproc, harecc gibi C derleyicileri tarafından kullanılır.
|
||
|
||
**Artıları**:
|
||
- LLVM'den çok daha hafif (birkaç MB)
|
||
- Hızlı kod üretimi (LLVM'den ~10x)
|
||
- Makul optimizasyonlar (register allocation, copy propagation, memory folding)
|
||
- x86-64 ve ARM64 desteği
|
||
- Basit SSA-tabanlı IR
|
||
|
||
**Eksileri**:
|
||
- C'de yazılmış, FFI gerektirir
|
||
- Optimizasyonlar LLVM kadar agresif değil
|
||
- Dokümantasyon İngilizce, küçük topluluk
|
||
- 32-bit ve RISC-V desteği deneysel
|
||
- Hata ayıklama bilgisi (DWARF) desteği yok
|
||
|
||
**Karar**: ✅ **Orta vadede en iyi seçenek**. Custom backend yazılana kadar QBE ideal bir ara çözüm.
|
||
|
||
---
|
||
|
||
#### 5. Cranelift (WebAssembly odaklı)
|
||
|
||
**Nedir**: Bytecode Alliance tarafından geliştirilen, Rust'ta yazılmış JIT/AOT derleyici backend'i. Wasmtime'ın JIT motoru.
|
||
|
||
**Artıları**:
|
||
- Hızlı JIT derlemesi
|
||
- x86-64, ARM64, RISC-V64 desteği
|
||
- Güvenlik odaklı (memory safety, sandboxing)
|
||
- Modern mimari (SSA, e-graphs)
|
||
|
||
**Eksileri**:
|
||
- Rust'ta yazılmış, C++ projesine entegrasyon zor
|
||
- WebAssembly odaklı, native diller için ikincil öncelik
|
||
- Dokümantasyon sınırlı, hızlı değişiyor
|
||
- Optimizasyonlar LLVM kadar agresif değil
|
||
|
||
**Karar**: ❌ saQut gibi C++ tabanlı bir proje için uygun değil.
|
||
|
||
---
|
||
|
||
#### 6. C Koduna Transpile Etme
|
||
|
||
**Nedir**: AST'yi doğrudan C kaynak koduna çevirip GCC/Clang ile derlemek.
|
||
|
||
**Artıları**:
|
||
- **En hızlı prototip yolu**: Hemen çalışan bir sistem
|
||
- GCC/Clang optimizasyonlarından bedava faydalanma
|
||
- Hata ayıklama kolay (üretilen C kodunu okuyabilirsin)
|
||
- Her platformda çalışır (C derleyicisi olan her yerde)
|
||
|
||
**Eksileri**:
|
||
- İki aşamalı derleme (yavaş)
|
||
- saQut'a özgü optimizasyonlar kaybolabilir
|
||
- Debug bilgisi orijinal kaynak koda değil, üretilen C koduna işaret eder
|
||
- Dil özellikleri C'nin sınırları içinde kalır
|
||
- Hata mesajları C derleyicisinden gelir, anlaşılması zor
|
||
|
||
**Karar**: ✅ **Birinci aşama için ideal**. Hemen çalışan bir sistem kurup, sonra native backend'e geçiş yapılabilir.
|
||
|
||
---
|
||
|
||
### Nihai Karar ve Yol Haritası
|
||
|
||
```
|
||
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||
│ Aşama 1 │────▶│ Aşama 2 │────▶│ Aşama 3 │
|
||
│ C Transpile│ │ QBE Backend │ │ Custom Backend │
|
||
│ (hemen) │ │ (orta vade) │ │ (uzun vade) │
|
||
└─────────────┘ └──────────────────┘ └─────────────────┘
|
||
1-2 hafta 2-4 hafta 2-6 ay
|
||
```
|
||
|
||
**Aşama 1 — C Transpile**: Hemen başlanabilir. Mevcut AST ve IR'yi C koduna çevirip GCC ile derlemek. Bu sayede:
|
||
- Dilin semantiği test edilebilir
|
||
- Gerçek programlar çalıştırılabilir
|
||
- Backend baskısı olmadan dil geliştirmeye devam edilebilir
|
||
|
||
**Aşama 2 — QBE**: Dil yeterince olgunlaştığında, QBE ile native kod üretimi:
|
||
- C derleyicisi bağımlılığı kalkar
|
||
- Derleme hızı artar
|
||
- Temel optimizasyonlar QBE tarafından yapılır
|
||
|
||
**Aşama 3 — Custom Backend**: Dil tamamen stabilize olduğunda:
|
||
- Tam kontrol
|
||
- saQut'a özgü optimizasyonlar
|
||
- Minimum bağımlılık
|
||
|
||
---
|
||
|
||
## ADR-002: Parser Mimarisi — Neden Pratt?
|
||
|
||
### Bağlam
|
||
|
||
C/C++/Java gibi diller genellikle **recursive descent** veya **LALR(1) parser** (yacc/bison) ile ifade ayrıştırması yapar. saQut için hangi yaklaşım seçilmeli?
|
||
|
||
### Değerlendirilen Seçenekler
|
||
|
||
#### Recursive Descent (elle yazılmış)
|
||
- **+** Basit, okunabilir, hata mesajları kontrol edilebilir
|
||
- **+** Java/C# benzeri dillerde yaygın
|
||
- **−** Operatör önceliğini yönetmek için çok sayıda fonksiyon gerekir (parseAddExpr, parseMulExpr, parseUnaryExpr...)
|
||
- **−** Yeni operatör eklemek zor
|
||
|
||
#### Pratt Parser (Top-Down Operator Precedence)
|
||
- **+** Operatör önceliğini merkezi bir tabloda yönetir
|
||
- **+** Yeni operatör eklemek tek satır (tabloya ekle + NUD/LED yaz)
|
||
- **+** Kod tekrarı yok, single source of truth
|
||
- **+** Hem prefix hem infix hem postfix operatörleri aynı çerçevede işler
|
||
- **−** Recursive descent kadar yaygın bilinmez
|
||
- **−** İlk bakışta anlaşılması zor gelebilir
|
||
|
||
#### LALR(1) / Parser Generator
|
||
- **+** Gramer tanımı net
|
||
- **−** Hata mesajları anlaşılmaz
|
||
- **−** Shift/reduce conflict'leri ile uğraşmak zaman kaybı
|
||
- **−** Generated code okunamaz, debug zor
|
||
|
||
### Karar
|
||
|
||
✅ **Pratt Parser** seçildi çünkü:
|
||
1. saQut'un operatör seti geniş ve büyüyebilir (ileride `|>`, `?.`, `??` gibi özel operatörler eklenebilir)
|
||
2. Operatör önceliğini merkezi bir tabloda (TokenPrecedence) yönetmek, kod tekrarını önler
|
||
3. Hem ifadeler hem prefix/postfix operatörler aynı çerçevede işlenir
|
||
4. Recursive descent'in statement tarafı için kullanılması, Pratt'in ifade tarafı için kullanılması hibrit bir yaklaşım sunar (en iyi iki dünya)
|
||
|
||
---
|
||
|
||
## ADR-003: Neden Header-Only?
|
||
|
||
### Bağlam
|
||
|
||
saQut derleyicisi tüm `.hpp` dosyalarında hem tanım (declaration) hem gerçekleme (implementation) içerir. Geleneksel C++ projelerinde `.hpp` + `.cpp` ayrımı yapılır.
|
||
|
||
### Değerlendirme
|
||
|
||
**Header-only avantajları**:
|
||
- Tek dosya = tek gerçeklik. Tanım ve gerçekleme arasında senkronizasyon sorunu olmaz
|
||
- `inline` anahtar kelimesi ile ODR (One Definition Rule) ihlali önlenir
|
||
- Derleme süreci basit: tek bir `.cpp` dosyası (main.cpp) her şeyi include eder
|
||
- Dağıtım kolay: Tüm derleyici tek bir header koleksiyonu
|
||
|
||
**Header-only dezavantajları**:
|
||
- Tüm kod her yerde görünür (ama zaten açık kaynak)
|
||
- Büyük projelerde derleme süresi uzayabilir
|
||
- Circular dependency riski (ama include guard'lar ile yönetiliyor)
|
||
|
||
### Karar
|
||
|
||
✅ **Header-only** devam ediyor. saQut şu anda küçük bir proje ve bu yaklaşım:
|
||
1. Kodun anlaşılmasını kolaylaştırır (dosyalar arası atlama yok)
|
||
2. Build sistemini basitleştirir
|
||
3. Hızlı iterasyon sağlar
|
||
|
||
Gelecekte proje çok büyürse (100K+ satır), `.hpp` + `.cpp` ayrımına geçilebilir.
|
||
|
||
---
|
||
|
||
## ADR-004: Token Sistemi — Neden Polymorphic Token Sınıfları?
|
||
|
||
### Bağlam
|
||
|
||
Tokenizer farklı token tipleri için farklı veri alanlarına ihtiyaç duyar:
|
||
- NumberToken: `isFloat`, `hasEpsilon`, `base`
|
||
- StringToken: `context`, `size`
|
||
- IdentifierToken: `context`, `size`
|
||
|
||
İki yaklaşım var:
|
||
1. **Tagged union**: Tek bir Token struct'ı, içinde `union` veya `std::variant`
|
||
2. **Class hierarchy**: Base Token sınıfı, her tip için alt sınıf
|
||
|
||
### Karar
|
||
|
||
✅ **Class hierarchy** seçildi çünkü:
|
||
1. C++'ta doğal ve yaygın bir pattern
|
||
2. Yeni token tipi eklemek kolay (yeni sınıf türet)
|
||
3. Tip güvenliği: `dynamic_cast` veya `gettype()` string karşılaştırması ile tip kontrolü
|
||
4. Bellek yönetimi açık: heap'te `new` ile oluşturulup pointer olarak saklanıyor
|
||
|
||
⚠️ **Bilinen sorun**: `ParserToken` yapısı eskiden `Token token` (değer kopyası) tutuyordu, bu object slicing'e neden oluyordu (alt sınıf verileri kayboluyordu). `commit 40579ca` ile `Token* token` pointer'a geçildi.
|
||
|
||
---
|
||
|
||
## ADR-005: IR Tasarımı
|
||
|
||
### Bağlam
|
||
|
||
Mevcut IR (src/ir/ir.hpp) sadece 5 opcode içeriyor: `declare`, `mathadd`, `mathsub`, `mathmul`, `mathdiv`. Bu bir "virtual register" IR'si — her işlem yeni bir sanal register'a yazılır.
|
||
|
||
### Mevcut Durum
|
||
|
||
```
|
||
OPCode: declare, mathadd, mathsub, mathmul, mathdiv
|
||
Param: {isRegister: bool, value: variant<int,float>}
|
||
IROpData: {op: OPCode, targetReg: int, arg1-3: Param}
|
||
```
|
||
|
||
### Eksikler (TODO)
|
||
|
||
- [ ] Kontrol akışı: `branch`, `jump`, `compare`
|
||
- [ ] Fonksiyon çağrısı: `call`, `ret`
|
||
- [ ] Bellek: `load`, `store`, `alloc`
|
||
- [ ] Tip bilgisi: IR opcode'ları tipleri taşımıyor
|
||
- [ ] Debug bilgisi: Kaynak satır eşlemesi yok
|
||
|
||
### Gelecek Yön
|
||
|
||
IR'nin iki katmanlı olması planlanıyor:
|
||
- **HeavyIR**: Debug bilgisi, tip bilgisi, değişken isimleri içeren zengin IR (interpreter/debug için)
|
||
- **LightIR**: Sadece çalıştırma için gerekli minimum IR (JIT/compiler için)
|
||
|
||
---
|
||
|
||
## Performans Karşılaştırması: JIT vs AOT
|
||
|
||
| Kriter | JIT (Lightning/Custom) | AOT (LLVM/QBE/Custom) | Transpile (C) |
|
||
|---|---|---|---|
|
||
| İlk derleme hızı | ⚡ Çok hızlı (mikrosaniye) | 🐢 Yavaş (saniye) | 🐢 Orta |
|
||
| Çalışma hızı | 🐢 Optimizasyonsuz | ⚡ Yüksek optimizasyon | ⚡ GCC/Clang seviyesi |
|
||
| Bellek kullanımı | ✅ Düşük | ⚠️ Yüksek (LLVM) | ✅ Derleme anında yok |
|
||
| Debug kolaylığı | ⚠️ Makine kodu seviyesi | ✅ Kaynak eşlemesi var | ⚠️ C kodu üzerinden |
|
||
| Platform bağımsızlığı | ⚠️ Her mimariye özel | ✅ LLVM her yerde | ✅ C her yerde |
|
||
| Geliştirme süresi | ⚡ Kısa (Lightning ile) | 🐢 Uzun (LLVM öğrenme) | ⚡ En kısa |
|
||
|
||
### Sonuç
|
||
|
||
**Prototip için**: C transpile > QBE > JIT
|
||
**Üretim için**: Custom backend > QBE > LLVM
|
||
**Dinamik kod (REPL) için**: JIT (Lightning veya custom)
|
||
|
||
---
|
||
|
||
## Gelecek Özellikler (Roadmap)
|
||
|
||
### Kısa Vade (1-4 hafta)
|
||
- [ ] C koduna transpile (Aşama 1 backend)
|
||
- [ ] Tip kontrolü (symbol table)
|
||
- [ ] Fonksiyon parametreleri
|
||
- [ ] else-if zincirleri
|
||
- [ ] Mantıksal operatörler (&&, ||) kısa devre değerlendirmesi
|
||
|
||
### Orta Vade (1-3 ay)
|
||
- [ ] QBE backend entegrasyonu
|
||
- [ ] Array/dizi desteği
|
||
- [ ] Struct/record tipleri
|
||
- [ ] Import/include sistemi
|
||
- [ ] Hata mesajlarında kaynak satır gösterimi
|
||
- [ ] Basit optimizasyonlar (constant folding, dead code elimination)
|
||
|
||
### Uzun Vade (3-12 ay)
|
||
- [ ] Custom native backend
|
||
- [ ] Interpreter modu (REPL)
|
||
- [ ] Debugger desteği (DWARF)
|
||
- [ ] Package yöneticisi
|
||
- [ ] LSP sunucusu (IDE desteği)
|
||
- [ ] Kendi kendini derleyebilme (self-hosting)
|
||
|
||
---
|
||
|
||
## Mimari Prensipler
|
||
|
||
1. **Tek sorumluluk**: Her dosya/class tek bir iş yapar
|
||
- Lexer: Karakter → sayı/konum
|
||
- Tokenizer: Lexer → Token
|
||
- Parser: Token → AST
|
||
- IR Generator: AST → IR
|
||
- (Gelecek) Code Generator: IR → Makine kodu / C kodu
|
||
|
||
2. **Bağımlılık yönü**: Tek yönlü
|
||
```
|
||
Lexer ← Tokenizer ← ParserToken ← AST ← Parser ← IR
|
||
```
|
||
|
||
3. **Test edilebilirlik**: Her katman bağımsız test edilebilir
|
||
- Lexer: `scan("42")` → `INumber{42, base=10}`
|
||
- Tokenizer: `scan("1+2")` → `[NumberToken, OperatorToken, NumberToken]`
|
||
- Parser: `parse(tokens)` → `ASTNode*`
|
||
- IR: `parse(ast)` → `vector<IROpData>`
|
||
|
||
4. **Hata toleransı**: Parser mümkün olduğunca ilerlemeye çalışır, ilk hatada durmaz (ileride panic mode eklenecek)
|
||
|
||
5. **Kademeli geliştirme**: Her aşamada çalışan bir sistem. "Big bang" entegrasyon yok.
|