refactor(opt): klon sadece ast komutunda — run/ir yerinde optimize eder

ADR-007 düzeltmesi: klon yalnızca ast komutunun öncesi/sonrası karşılaştırması
için gerekli; diğer komutlar tek versiyon üretiyor, klona gerek yok.

- OptimizationManager: runPassesInPlace() (klon yok) + optimize() (klon, sadece ast)
- run.hpp + ir.hpp: optimize() → runPassesInPlace(); activeAst/optimizedAst dansı kalktı
- ast.hpp: değişmedi — hâlâ optimize() (klon) kullanıyor
- ADR-007: "sembol tablosu remap edilir" yanlış kararı düzeltildi;
  Symbol::references konum listesi, refcount değil — paylaşım güvenli
11/11 ctest geçti
This commit is contained in:
abdussamedulutas 2026-06-18 23:15:27 +03:00
parent ab47e42954
commit 98bf8385d2
4 changed files with 63 additions and 60 deletions

View File

@ -105,46 +105,45 @@ sembol tablosunun optimizasyondan önceki ve sonraki halini ayrı ayrı görebil
symbol bağı, erişilebilirlik, constness ekler). Ağacı **bozmaz**, zenginleştirir.
Orijinal AST hâlâ kaynak kodun tam izdüşümüdür.
2. **Optimizasyon dönüşümü, ağacın bir KOPYASI (klon) üzerinde yapılır.**
Orijinal analizli AST = "öncesi"; klon + dönüştürülmüş AST = "sonrası".
Ağaç klonlamak ucuz ve basittir, yalnızca `--optimized` istendiğinde yapılır.
2. **Optimizasyon dönüşümü iki yolla yapılabilir:**
**Sonuç:** Hem "bellek canavarı" felsefesi korunur (orijinal AST her şeyi tutar),
hem optimizasyon yapılır, hem de öncesi/sonrası ayrı ayrı incelenebilir.
a. **`ast` komutu için klon:** orijinal AST dokunulmadan kalır, klon üstünde
pass'ler çalışır. Kullanıcı "öncesi" ve "sonrası" AST'yi ayrı ayrı
görebilir. `OptimizationManager::optimize()` bu yolu kullanır.
b. **Diğer tüm komutlar için yerinde (in-place):** `run --optimized`,
`ir --optimized` vb. tek versiyon üretiyor — orijinali saklamaya gerek yok.
`OptimizationManager::runPassesInPlace()` bu yolu kullanır, klon maliyeti yok.
**Sonuç:** "bellek canavarı" felsefesi `ast` komutunda korunur; diğer komutlar
gereksiz klon maliyeti taşımaz.
```
saqut ast file.sqt → ham + annotate edilmiş AST (1+2 burada durur)
saqut ast file.sqt --optimized → klon, folding uygulanmış (3 var)
saqut run file.sqt --optimized → yerinde optimize → IR → VM (klon yok)
saqut ir file.sqt --optimized → yerinde optimize → IR dump (klon yok)
```
### Güncelleme — Klon maliyeti yük taşır (load-bearing)
### Güncelleme — Klon ve sembol tablosu paylaşımı
İlk metin "ağaç klonlamak ucuz ve basittir" diyordu; bu **klon maliyetini hafife
alıyor** ve bir **tutarlılık (coherence) problemini** atlıyordu. Düzeltme:
`deepClone` sembol tablosunu yeniden eşlemez (remap etmez) — klondaki
`IdentifierNode::resolvedSymbol` orijinal `Symbol` nesnelerini gösterir. Bu
**güvenlidir**, çünkü:
`ASTNode::clone()` "belki gerekir" değil, **merkezi ve spesifiye edilmesi
zorunlu** bir bileşendir; tüm öncesi/sonrası hikâyesi ona dayanır (bkz. roadmap
Faz 4'te clone() yükseltildi).
- `Symbol::references` bir **konum listesi** (`std::vector<SourceLocation>`),
referans sayacı değildir. Klonda bir `IdentifierNode` silindiğinde bu liste
değişmez.
- `IdentifierNode` destructor'ı yoktur; `resolvedSymbol`'e dokunan hiçbir yıkıcı
kodu çalışmaz.
- Klondaki pass'ler Symbol nesnelerini **okur** (slot numarası, tip vb.),
**yazmaz** — paylaşım salt-okunur (read-only) kullanımdır.
**Klonlanırken karar verilmesi gereken iki nokta (açıkça belgele):**
**Parent pointer'lar** ise yeniden bağlanır — klon node'larının `parent`'ı
orijinali değil, klonu gösterir (deepClone bunu zaten yapar).
1. **Parent pointer'lar yeniden bağlanmalı.** Klon node'larının `parent`'ı
orijinali değil, klonu göstermeli; yoksa yapısal doğrulama ve dönüşümler
yanlış ağaçta gezinir.
2. **`IdentifierNode → Symbol` bağları: paylaş mı, yeniden eşle mi?**
- **Paylaş** (klon ve orijinal aynı sembol tablosuna işaret eder): ucuz, ama
klonu optimize etmek orijinalin **referans sayımlarını bozar** (DCE klonda
bir kullanımı silince orijinalin Symbol ref-count'u da düşer).
- **Yeniden eşle** (klona ait bir sembol tablosu kopyası): doğru, ama ucuz
değil.
- **Karar:** `--optimized` istendiğinde sembol tablosu da **klonlanır ve
yeniden eşlenir** (remap). Doğruluk, ucuzluğa tercih edilir; klon zaten
yalnızca optimizasyon istendiğinde üretilir, sıcak yol değildir. "Ucuz"
iddiası kaldırıldı.
Bu, ADR-013'teki "ref-count Symbol'da yaşar" kararıyla tutarlıdır: ref-count
Symbol'da olduğu için, klonun kendi Symbol'larına sahip olması şarttır.
Önceki versiyon "sembol tablosu klonlanır ve remap edilir" diyordu; bu hem hiç
implement edilmedi hem de gerekli değildi. Düzeltildi.
---
@ -667,7 +666,7 @@ modelini birlikte zorlar — ikisi de bu yüzden ertelendi.
| ADR | Konu | Karar |
|---|---|---|
| 006 | Frontend mimarisi | Çok-aşamalı; frontend/middle-end/backend katmanları |
| 007 | Analiz vs optimizasyon | Analiz yerinde işaretler; optimizasyon klonda dönüştürür; `clone()` merkezi, sembol tablosu remap edilir |
| 007 | Analiz vs optimizasyon | Analiz yerinde; `ast` komutu klon üstünde dönüştürür (öncesi/sonrası karşılaştırması); `run`/`ir` yerinde optimize eder (klon yok); sembol bağları salt-okunur paylaşım (remap gerekmez) |
| 008 | Optimizasyon konumu | Basitler AST'de, dataflow gerektirenler IR'de |
| 009 | Pass yönetimi | Fixpoint döngüsü, toggle'lı; monotonluk/iterasyon-tavanı değişmezi; akışa-bağlı analiz tur başına tazelenir |
| 010 | Tip sistemi | Minimal+genişletilebilir Type; gizli dönüşüm yok; Error tipi; tamsayı literali bağlama-göre tiplenir |

View File

@ -43,24 +43,20 @@ inline int cmdIr(const CliArgs& args) {
return 1;
}
// --optimized: optimize edilmiş AST klonu üzerinden IR üret
ASTNode* activeAst = ast;
ASTNode* optimizedAst = nullptr;
// --optimized: constant folding + DCE yerinde uygulanır, klon yok.
// IR dump için tek versiyon yeterli — ast komutu gibi karşılaştırma yok.
if (args.optimized) {
CompilerConfig cfg;
DiagnosticEngine optDiag;
OptimizationManager mgr(cfg, optDiag);
optimizedAst = mgr.optimize(ast, &symbolTable);
activeAst = optimizedAst;
OptimizationManager(cfg, optDiag).runPassesInPlace(ast, &symbolTable);
if (optDiag.errorCount() + optDiag.warningCount() > 0)
optDiag.printAll(std::cerr); // W002 vb. uyarılar stderr'e
}
IRGenerator irGenerator;
IRProgram program = irGenerator.generate(activeAst, symbolTable);
IRProgram program = irGenerator.generate(ast, symbolTable);
program.dump();
delete optimizedAst; // nullptr ise no-op
delete ast;
for (auto* t : tokens) delete t;
return 0;

View File

@ -4,9 +4,9 @@
// Tam derleme + çalıştırma pipeline'ı:
// tokenize → parse → sembol topla → [opsiyonel: optimize] → IR üret → VM çalıştır
//
// --optimized bayrağı: orijinal AST klonlanır, constant folding + DCE uygulanır,
// optimize edilmiş klon IR generator'a verilir. Orijinal AST dokunulmadan kalır.
// Aynı pattern ir.hpp'de de kullanılıyor — paralel değişikliklerde ikisine bak.
// --optimized bayrağı: AST yerinde optimize edilir (klon yok — sadece tek versiyon
// gerekiyor). ast komutu orijinali saklaması gerektiği için klon kullanır; run/ir
// kullanmaz. Aynı pattern ir.hpp'de de var — paralel değişikliklerde ikisine bak.
// ============================================================================
#ifndef SAQUT_CLI_RUN
@ -61,24 +61,19 @@ inline int cmdRun(const CliArgs& args) {
}
// ── Aşama 4 (opsiyonel): Optimizasyon ────────────────────────────────
// --optimized bayrağı verilmişse: orijinal AST'yi kopyala, klon üstünde
// constant folding + DCE uygula. IR generator klonu kullanır; orijinal
// bu scope'ta silinir. Bayrak yoksa sıfır maliyet — klonlama olmaz.
ASTNode* activeAst = ast;
ASTNode* optimizedAst = nullptr;
// --optimized: constant folding + DCE yerinde uygulanır, klon yok.
// Tek versiyon (optimize edilmiş) yeterli — ast komutu gibi karşılaştırma yok.
if (args.optimized) {
CompilerConfig cfg;
DiagnosticEngine optDiag;
OptimizationManager mgr(cfg, optDiag);
optimizedAst = mgr.optimize(ast, &symbolTable);
activeAst = optimizedAst;
OptimizationManager(cfg, optDiag).runPassesInPlace(ast, &symbolTable);
if (optDiag.errorCount() + optDiag.warningCount() > 0)
optDiag.printAll(std::cerr); // W002 (derleme zamanı sıfıra bölme) vb.
}
// ── Aşama 5: IR üretimi ───────────────────────────────────────────────
IRGenerator irGenerator;
IRProgram program = irGenerator.generate(activeAst, symbolTable);
IRProgram program = irGenerator.generate(ast, symbolTable);
// ── Aşama 6: VM çalıştırma ────────────────────────────────────────────
int exitCode = 0;
@ -90,7 +85,6 @@ inline int cmdRun(const CliArgs& args) {
exitCode = 1;
}
delete optimizedAst; // nullptr ise no-op; orijinal ast her durumda aşağıda silinir
delete ast;
for (auto* t : tokens) delete t;
return exitCode;

View File

@ -1,9 +1,18 @@
// ============================================================================
// saQut — Optimizasyon Yöneticisi (ADR-007, ADR-009)
//
// 1. AST'yi klonlar (orijinal dokunulmaz).
// 2. Etkin pass'leri fixpoint döngüsüyle çalıştırır.
// 3. Optimize edilmiş klon sahipliğini döndürür (caller delete eder).
// İKİ KULLANIM YOLU:
//
// 1. runPassesInPlace(root, table)
// Pass'leri verilen AST üstünde doğrudan çalıştırır — klon yok.
// run / ir / transpile gibi "tek seferlik" komutlar bu yolu kullanır:
// AST'nin tek versiyonu gerekiyor, orijinali saklamaya gerek yok.
//
// 2. optimize(root, table) [sadece ast komutu için]
// Önce deepClone, sonra runPassesInPlace. Orijinali dokunulmaz bırakır.
// Çıktı: optimize edilmiş klon (caller delete eder).
// ast komutunun "öncesi / sonrası" karşılaştırması için zorunlu.
// Diğer komutlar bu yolu ÇAĞIRMAMALI — gereksiz klon maliyeti.
//
// Fixpoint garantisi: her pass yalnızca küçülten dönüşümler yapar
// (katlama: n düğüm → 1 düğüm; DCE: düğüm siler). Büyüten pass
@ -33,18 +42,23 @@ public:
maxRounds_ = cfg.maxFixpointRounds;
}
// optimize: AST'yi klonlar ve optimize edilmiş kopyayı döndürür.
// Dönen pointer caller'a aittir (delete edilmeli).
ASTNode* optimize(ASTNode* root, SymbolTable* table) {
ASTNode* clone = deepClone(root);
// Pass'leri verilen AST üstünde yerinde çalıştırır — klon yok.
// run / ir ve diğer tek-versiyon komutları bu yolu kullanır.
void runPassesInPlace(ASTNode* root, SymbolTable* table) {
for (int round = 0; round < maxRounds_; ++round) {
bool anyChange = false;
for (auto& pass : passes_)
if (pass->run(clone, table)) anyChange = true;
if (pass->run(root, table)) anyChange = true;
if (!anyChange) break;
}
}
// Önce deepClone, sonra runPassesInPlace. Orijinal dokunulmaz.
// SADECE ast komutu kullanır — öncesi/sonrası karşılaştırması için.
// Dönen pointer caller'a aittir (delete edilmeli).
ASTNode* optimize(ASTNode* root, SymbolTable* table) {
ASTNode* clone = deepClone(root);
runPassesInPlace(clone, table);
return clone;
}