WebAssembly’de çalışan Fortran
(gws.phd)- Eski bilimsel ve mühendislik hesaplama varlıkları olan Fortran sayısal kodlarını tarayıcıya taşımak için bir WebAssembly derleme yolu gerekir; webR bunun için yamalanmış LLVM
flang-newkullanır f2c, LFortran, Dragonegg, Classic Flang ve LLVM Flang’in her birinin modern Fortran desteği, hedef mimariler ve bakım yapılabilirlik açısından sınırlamaları vardır; bu yüzden henüz basit ve standart bir çözüm yoktur- LLVM Flang,
wasm32-unknown-emscriptenhedefini doğrudan desteklemediğindenTargetWasm32eklenmesi gerekir; çalışma zamanı bağlama aşamasında ise host ve hedeftekilongboyutlarının farklı olması sorun çıkarır - Yamalanmış
flang-new, Emscripten ve Fortran çalışma zamanı statik kitaplıkları birleştirildiğinde Fortran alt programları C veya JavaScript’ten çağrılabilir;PRINT,ALLOCATEveCHARACTERargümanları da işlenebilir - BLAS 3.12.0 ve LAPACK 3.12.0 referans uygulamaları WebAssembly statik kitaplıkları olarak derlenip el yazısı rakam sınıflandırma ve polinom interpolasyonu demoları tarayıcıda çalıştırılmıştır
Mevcut Fortran sayısal kodlarını tarayıcıya taşımak
- Fortran 1957’de ortaya çıkmış eski bir dil olsa da bilimsel ve mühendislik hesaplamalarında uzun süredir kullanılır; modern Fortran, Fortran 77’nin sabit biçim kısıtlarının çoğundan kurtulmuştur
- Amaç, modern Fortran rutinlerini WebAssembly’ye derleyip tarayıcıda çalıştırmak; sayısal argümanları alıp BLAS ve LAPACK rutinleriyle hesaplama yaptıktan sonra sonucu döndürmek veya konsola yazdırmaktır
- Bu yaklaşım, SciPy veya R gibi BLAS ve LAPACK’e bağımlı üst seviye programlama ortamlarının web’e taşınmasını mümkün kılar
- Sayısal rutinleri JavaScript veya Rust ile yeniden yazmak yerine, zaten doğrulanmış Fortran tabanlı araçlar ve kitaplıklar kullanılabilir
- webR projesi, Fortran kodunu WebAssembly’ye derlemek için yamalanmış LLVM
flang-newderleyicisini kullanır - Mevcut yöntem hack’lere dayanır; daha deneyimli derleyici geliştiricilerinden yardım almadan değişiklikleri LLVM’e katkı olarak sunmak zordur
Fortran→WebAssembly araçlarının durumu
- 2024 itibarıyla çeşitli araçlar ve toolchain’ler olsa da tam özellikli basit bir çözüm henüz yoktur
-
f2cf2c, Fortran 77’yi C koduna dönüştürür ve Emscripten’ın bunu WebAssembly’ye derlemesini sağlar- Pyodide, Fortran kodu içeren Python paketlerini derlerken bu yöntemi kullanır
- Pyodide yol haritasında da bu yöntem “iyi çalışmıyor” diye değerlendirilir
- Modern Fortran kodları için uygun değildir; dönüşümden sonra da kritik hatalar ve kapsamlı yamalar gerekir
-
LFortran
- LFortran’ın özellikleri son yıllarda önemli ölçüde arttı ve doğrudan WebAssembly’ye derleme yapabilir
- Geliştiricileri, hâlâ alfa aşamasında olduğu için gerçek kodları derlerken sorunların beklenebileceğini belirtir
- MINPACK gibi bazı projeleri derleyebilir, ancak Fortran belirtiminin tamamını desteklemediğinden daha büyük projeler başarısız olabilir
- Geliştirme hedefi Fortran 2018’in tamamını desteklemektir; Jupyter’a benzer etkileşimli Fortran REPL öne çıkan özelliklerinden biridir
-
Dragonegg
- Dragonegg, GCC frontend’ini kullanarak LLVM IR üreten bir GCC eklentisidir
- LLVM backend’iyle WebAssembly çıktısı üretebilir; webR’nin Fortran kaynaklarını WebAssembly’ye ilk kez derlerken kullandığı yöntem buydu
- Desteklenen en yeni sürümler
gcc-4.8vellvm-3.3olduğundan çok eski GCC ve LLVM gerekir - Çoğu kullanıcının VM veya Docker container’ına ihtiyacı olur; Dragonegg’in ürettiği LLVM IR de WebAssembly çıktısı için ek sonradan işlemden geçirilmelidir
- 2020’de Fortran kodunu WebAssembly’ye derlemenin pratikteki tek uygulanabilir yolu neredeyse buydu
-
Classic Flang
- Classic Flang, açık kaynak yapılan PGI/NVIDIA
pgfortrantabanlı, LLVM hedefli bir Fortran derleyicisidir - 32 bit çıktı desteklemediğinden
wasm32hedefi için kullanılamaz - Yazı yazıldığı sırada Firefox, Chrome ve Node
wasm64destekler, ancak bu destek özellik bayraklarının arkasındadır - Proje belgeleri de yeni projelerde Classic Flang’i seçmenin iyi bir fikir olmayabileceğini belirtir
- Classic Flang, açık kaynak yapılan PGI/NVIDIA
-
LLVM Flang
- LLVM Flang, LLVM için Fortran frontend’ini sıfırdan yeniden uygulayan bir projedir ve LLVM 11’den beri LLVM projesine dahildir
- Henüz üretime hazır kabul edilmese de
flang-new’un üretim öncesi sürümü gerçek Fortran kodlarını derlemek için oldukça kullanılabilir hâle gelmiştir - Varsayılan durumda WebAssembly çıktısı üretemez
- LLVM’in modüler tasarımı sayesinde Flang frontend’i ile LLVM WebAssembly backend’i birlikte kullanılabilir
- Bu 2020’de de mümkündü, ancak daha büyük LLVM yamaları, özel matematik rutinleri enjekte edilmesi ve çok aşamalı bir derleme süreci gerektiriyordu
- Bugün
flang-newfrontend geliştirmeleri sayesinde LLVM kaynaklarında küçük değişikliklerle Fortran→WebAssembly derleyicisi oluşturmak mümkündür
LLVM Flang’i WebAssembly için derlemek
- Paket yöneticisiyle kurulan LLVM,
flang-newikilisini içermeyebilir- Örneğin macOS Homebrew’deki LLVM v17.0.6’da
flang-newkomutu bulunamaz
- Örneğin macOS Homebrew’deki LLVM v17.0.6’da
- LLVM Flang kaynaklarının değiştirilmesi gerektiğinden LLVM v18.1.1 kaynakları doğrudan derlenmiştir
- CMake yapılandırmasında varsayılan hedef triple
wasm32-unknown-emscriptenolarak belirlenir;WebAssemblyhedefi veclang;flang;mlirprojeleri etkinleştirilir - Derleme tamamlandığında
build/bin/flang-new --versionile şu bilgiler doğrulanabilir- Sürüm:
flang-new version 18.1.1 - Hedef:
wasm32-unknown-emscripten - Thread modeli:
posix
- Sürüm:
İlk sorun: wasm32 hedefinin uygulanmamış olması
- Basit Fortran alt programı
foo.f08,flang-newile derlendiğinde şu hata oluşurnot yet implemented: target not implemented
- Bunun nedeni
flang-newiçindewasm32-unknown-emscriptenhedef triple’ının henüz uygulanmamış olmasıdır - Çözüm,
flang/lib/Optimizer/CodeGen/Target.cppdosyasınaTargetWasm32hedef özelliklerini ekleyen bir yamadır- Varsayılan genişlik 32 olarak ayarlanır
- Karmaşık sayı argümanlarını ve dönüş tiplerini WebAssembly tarafındaki LLVM tiplerine dönüştüren marshalling tanımlanır
llvm::Triple::ArchType::wasm32dalıTargetWasm32’ye bağlanır
- Yamadan sonra yeniden derlendiğinde Fortran kaynağı WebAssembly nesnesi olarak derlenir
file foo.osonucu:WebAssembly (wasm) binary module version 0x1 (MVP)llvm-nm foo.oçıktısındafoo_sembolü görülebilir
C ve JavaScript’ten Fortran alt programı çağırmak
- Fortran rutinleri genellikle argümanları referansla geçirir;
INTENT()ile argümanın nasıl kullanılacağı bildirilebilir - Örnek Fortran alt programı
foo,x,y,ztamsayı argümanlarını alır vez = x + yişlemini yapar - Native derlemede
gfortran -c foo.f08 -o foo.oçalıştırıldığında sembol adının sonunafoo_gibi alt çizgi eklenebilir - C’den çağırırken dış sembol
extern void foo_(int*, int*, int*);biçiminde bildirilir ve argümanlar adres olarak geçirilir - WebAssembly’de
flang-newile oluşturulanfoo.o, Emscripten ile C koduna bağlanabiliremcc main.c foo.o -o main.jsnode main.jsçıktısı:1 + 1 = 2
-
Doğrudan JavaScript çağrısı
- Fortran alt programını C kodu olmadan doğrudan JavaScript’ten çağırmak da mümkündür
- Emscripten bağlama aşamasında
_foo_,_malloc,_freedışa aktarılır emcc foo.o -sEXPORTED_FUNCTIONS=_foo_,_malloc,_free -o foo.js- JavaScript,
Module._malloc()ile tamsayı saklama alanı ayırır, değerleriModule.HEAPU32içine yazar veModule._foo_(x, y, z)çağrısını yapar - Çalıştırma sonucu şöyledir
x = 123y = 456x + y = 579- Tarayıcıda
foo.jsvestandalone.jsHTML’den yüklenerek aynı sonuç JavaScript konsolunda görülebilir
İkinci sorun: Fortran çalışma zamanı kitaplığı
PRINT *, "Hello, World!"içeren bir Fortran alt programı derlendiğinde bağlama aşamasında çalışma zamanı sembollerinin eksik olduğuna dair hata oluşur_FortranAioBeginExternalListOutput_FortranAioOutputAscii_FortranAioEndIoStatement
- Bunun nedeni LLVM Fortran çalışma zamanı kitaplığının henüz WebAssembly için derlenmemiş olmasıdır
- Çalışma zamanı kitaplığı LLVM kaynak ağacındaki
llvm-project/flang/runtimeiçinde C++ ile yazılmıştır - Çalışma zamanı kaynakları Emscripten’ın
em++veemararaçlarıyla derlenirse statik kitaplıkbuild/flang/runtime/libFortranRuntime.aoluşturulabilir - Bu kitaplık bağlandığında
Hello, World!derlemesi ilerler, ancak başlangıçta fonksiyon imzası uyumsuzluğu uyarısı oluşur
Üçüncü sorun: host ve hedefteki long boyutu farkı
hello.oile Fortran çalışma zamanı kitaplığı bağlandığında_FortranAioOutputAsciiimza uyumsuzluğu uyarısı çıkar- Fortran nesnesi tarafı
(i32, i32, i64) -> i32bekler - Emscripten ile derlenen çalışma zamanı tarafı
(i32, i32, i32) -> i32olarak tanımlanır
- Fortran nesnesi tarafı
- WebAssembly’de birden çok derleme birimi genelinde tanımlanan sembollerin argüman ve dönüş tipleri tutarlı olmalıdır
- Bu sorun yalnızca uyarıda kalmaz; Node’da çalıştırıldığında
RuntimeError: unreachableile başarısız olur - LLVM Flang’in
RTBuilder.hdosyasındasizeofkullanımınınbuild == host == targetvarsaydığını belirten bir TODO yorumu vardır - Modern 64 bit Unix türevi host’larda
sizeof(long)8 bayttır, ancakwasm32-unknown-emscriptenhedefinde 4 bayt olmalıdır - Fortran’daki
CHARACTERtipi argümanlar fonksiyon veya alt programa geçirilirken dizge uzunluğunu ileten gizli argümanlar eklenebilir - Fortran çalışma zamanı kitaplığında bu uzunluk argümanı
size_tolarak bildirilir vetypedefzinciri üzerindenunsigned longile aynı hâle gelir - Bu gizli argümanın boyut farkı
i64ilei32uyumsuzluğuna yol açar
Geçici yama: 4 baytlık değeri zorunlu kılmak
- İdeal çözüm, çapraz derleme sırasında
flang-new’un host’tan bağımsız olarak hedef mimariye ve veri modeline uyguni32veyai64üretmesidir - Şu anda
wasm32ve Emscripten’a uygun şekildelongboyutunu 4 bayt olarak hard-code eden bir yama kullanılır - Yama iki parçadan oluşur
RTBuilder.hiçindelongveunsigned longmodel tipleri8 * sizeof(...)yerine8 * 4olarak zorlanırCodeGen.cppiçindemalloc()çağrısı argümanı 64 bit yerine 32 bit tamsayı olarak üretilir ve ayırma boyutui32ye cast edilir
- Bu değişiklik, Fortran 90’da gelen
ALLOCATE()tabanlı dinamik ayırmayı da düzeltir - Yeniden derlemeden sonra
hello.f08vehello.c, çalışma zamanı kitaplığıyla bağlandığında uyarısız derlenir ve Node’da şu çıktı alınırHello, World!
BLAS’ı WebAssembly’ye derlemek
- BLAS, matris-vektör çarpımı gibi lineer cebirin ortak işlemlerini gerçekleştiren düşük seviyeli rutinler kümesidir
- Orijinal BLAS rutinleri 1979’da yayımlanmıştır ve sayısal hesaplama alanında fiili standarttır
- Referans uygulama BLAS 3.12.0, Fortran 90 ile yazılmıştır ve netlib’den alınabilir
make.inciçinde şu araçlar belirtilerek derlenirFC = ../build/bin/flang-newFFLAGS = -O2AR = emarRANLIB = emranlib
- Derleme sonucu
blas_LINUX.astatik kitaplığı oluşturulur - Örnek Fortran rutini
bar, BLAS level 2 rutiniZGEMV()çağırır ZGEMV(), karmaşık sayı matris-vektör işlemi yapar; örnekCOMPLEX(KIND=8)argümanları veCHARACTERayar argümanı'N'kullanır- C programında karmaşık sayı dizileri oluşturulup Fortran rutinine geçirilir ve sonuç yazdırılır
- Node çalıştırma sonucu şöyledir
Y[0]: 23.000000 + 6.000000iY[1]: 18.000000 + 10.000000iY[2]: 6.000000 + 16.000000i
- Bu sonuç, Fortran 90 kaynağından derlenen BLAS’ın WebAssembly altında çalıştığını doğrular
Tarayıcı örneği: el yazısı rakam sınıflandırıcı
- Demo, çok katmanlı algılayıcı (MLP) yapay sinir ağıyla elle çizilen 0–9 rakamlarını sınıflandırır
- Kullanıcı fare veya dokunmatik ekranla rakam çizebilir; ağın tahmin ettiği göreli olasılıklar sağdaki grafikte gösterilir
- Model ağırlıkları Python ile önceden eğitilmiştir, ancak sınıflandırma çalışma zamanında JavaScript ve WebAssembly ile tarayıcıda yapılır
- MLP sınıflandırma süreci özünde matris-vektör toplama ve çarpma işlemlerinin tekrarından oluşur
- Ağır hesaplama, tek bir Fortran alt programının BLAS level 2 rutini
DGEMV()kullanmasıyla yapılır
LAPACK’i WebAssembly’ye derlemek
- LAPACK, lineer cebir problemlerini sayısal olarak çözen bir yazılım kitaplığıdır ve BLAS üzerine inşa edilmiştir
- Referans uygulama LAPACK 3.12.0 netlib tarafından sağlanır ve değiştirilmiş BSD lisansıyla dağıtılır
make.inc.example,make.incolarak kopyalandıktan sonra şu ayarlar değiştirilirFC, derlenenflang-new’un tam yolu olarak ayarlanırFFLAGS = -O2AR = emarRANLIB = emranlibTIMER = INT_CPU_TIME
make libkomutuyla WebAssembly statik kitaplığıliblapack.aoluşturulur- Daha sonra LAPACK rutinleri BLAS örneğine benzer şekilde çağrılabilir
Tarayıcı örneği: lineer cebirle polinom interpolasyonu
- Demo, nokta kümesi için interpolasyon polinomunu bulur ve LAPACK rutinlerinin tarayıcıda çalıştığını gösterir
- Kullanıcı grafiğe tıklayıp yeni nokta eklediğinde, tüm noktalardan geçen interpolasyon polinomu bulunur
- Yöntem Vandermonde yöntemi kullanır
- Bu yöntemle elde edilen lineer cebir denklemleri LAPACK’in
DGELS()rutiniyle sayısal olarak çözülür nveri noktasını tam olarak içerenn-1dereceli bir polinom her zaman bulunabilirnbüyüdükçe polinomun ardışık veri noktaları arasında büyük salınımlar yaptığı Runge olgusu ortaya çıkabilir; bu, spline interpolasyonuyla önlenebilir
Kalan işler ve sağlanan araçlar
- Yamalanmış LLVM Flang kullanıldığında modern Fortran kodları WebAssembly nesnelerine derlenebilir
- Bu yöntemin avantajı, web için sayısal algoritmaları JavaScript ile yeniden yazmadan, mevcut Fortran araçları ve kitaplıklarını kullanabilmektir
flang-newWebAssembly’yi resmî olarak desteklerse webR ve R paketleri için LLVM fork’u sürdürme yükü azalır- Şu anda LLVM Flang ve çapraz derleme sorunlarını tüm hedeflerde doğru şekilde düzeltmek için daha iyi bir yola ihtiyaç vardır
- LLVM Flang’i doğrudan derlemek istemeyen veya derlemekte zorlanan kullanıcılar için GitHub Container Registry’de yamalanmış LLVM Flang ikililerini içeren Docker container’ı sunulmaktadır
1 yorum
Hacker News yorumları
Biraz arka plan eklemek gerekirse, bu Fortran keşfi, George’un R’yi tarayıcıda çalıştırmak için sürdürdüğü harika WebR çalışmalarının bir parçası
R kaynaklarında epey Fortran kodu var ve bildiğim kadarıyla WebR başlangıçta Fortran’ı önce f2c ile C’ye derliyor, sonra o C’yi wasm’a derliyordu
LLVM Flang yaması sayesinde WebR’yi gerçek bir Fortran derleyicisiyle derlemek mümkün hâle geldi
George blog yazısında bunu doğrudan söylemedi ama Flang’in bu yamayı kabul etmesini ya da daha iyi bir şekilde uygulamasını umduğunu daha önce söylemişti
Böyle olursa ayrı bir yama bakımı gerekmeyecek ve değiştirilmemiş Flang wasm’a derleyebileceği için Fortran kullanan başka projeler de bundan yararlanacak
https://docs.r-wasm.org/webr/latest/
Ancak ben Nvidia’nın Fortran ürün geliştirmesini tamamlamak için gereken işlere odaklandığımdan, bu tür işlere ayıracak zamanım kalmıyor
20 yıl önce Xilinx’te FORTRAN derleme üzerinde çalışmıştım; hatırladığım tek şey f2c.h başlık dosyasında
barftanımının olduğuydu/* f2c.h -- Standard Fortran to C header file //* barf [ba:rf] 2. "He suggested using FORTRAN, and everybody barfed."(https://www.netlib.org/clapack/f2c.h)
Anlatım biçimi olarak en basit, ama önemsiz olmayan örneği kullanması gerçekten iyi
Yazı “JavaScript’ten BLAS fonksiyonları çağırmak” gibi somut bir probleme dayandığı için çok şey öğrenmiş gibi hissettirdi; harika bir yazı
[1] https://en.m.wikipedia.org/wiki/The_Theoretical_Minimum
Hayran mı kalmalıyım, dehşete mi düşmeliyim bilmiyorum; muhtemelen ikisi de
f18’i derlerken llvm-project/main’in en güncel kaynaklarını kullanmanızı öneririm
Proje hızlı ilerliyor; zaten düzeltilmiş sorunları ayıklamak ya da zaten uygulanmış özellikleri kaçırmak zaman kaybı olur
WebAssembly portu üzerinde çalışıp ara kodun Fortran’a kadar çalıştığı noktaya mı getirmeye çalışıyor?
WebAssembly geliştirmeyi pek bilmiyorum
Tüketici açısından WebAssembly şu anda hemen bir şey sunuyor mu? Yoksa programların gerçekten taşınabilir hâle geleceği bir gelecek için yapılan altyapı çalışmasına mı daha yakın?
WebAssembly aygıtının ağ veya dosya gibi erişim kısıtlamalarını kolaylaştırdığını duydum ama bunun teoride mi kaldığını yoksa zaten uygulanmış mı olduğunu bilmiyorum
Temel fark, Wasm’ın kendisinde standart kütüphane olmaması ve giriş/çıkış fonksiyonları da açığa çıkarmaması
Bu yüzden host’u, yani VM’yi kendiniz oluşturup Wasm ikilisinin kullanacağı fonksiyonları açığa çıkarabilirsiniz; Wasm ikilisi de dış dünyaya yalnızca bu fonksiyonlar üzerinden erişebilir
Bir diğer avantajı, ikili biçiminin tescilli olmaması ve bir spesifikasyonu bulunması; bu sayede herkes bir Wasm VM’i uygulayabilir
Ancak şu anda bunun iyi bir durumda olduğunu söylemek zor; hâlâ çok erken, W3C benzeri gruplar tarafından standartlaştırılan birçok yeni özellik var ve süreç de çok yavaş
Çoğu hedefe dağıtmanın veya çapraz derlemenin de bir yolu var
Bilgisayarın CPU’sunun ARM mı x86 mı olduğunu genelde bilmememiz ve pek umursamamamız gibi
Bu yüzden, native kod olarak mı yoksa JVM·.NET·WASM gibi bir VM üzerinde mi çalıştığı gibi ayrıntılarla ilgilenmiyorsanız, başka çözümlere göre neyi daha fazla sunduğunu söylemek zor
Genelde yalnızca kötü örnekler göze çarpar; bu da “tüm Electron programları şişkin ve kaynak yiyen canavarlar, tüm native uygulamalar ise otomatik olarak verimli yazılım mühendisliği harikalarıdır” gibi mem’lere dönüşür
1981/82’de yazdığım Fortran 78 kodunu saklamış olsaydım burada çalışıp çalışmadığını deneyebilirdim; yazık
Jovial programlama dili için kaynak kod biçimlendiricisiydi; Fortran’la yapılacak bir iş değildi ama o dönemde tek seçenek buydu
JavaScript’te kullanılabilecek, bir ölçüde production-ready doğrusal cebir ekosistemi var mı?
Arayınca çoğunlukla 10 yıl kadar eski tanıdık kütüphanelerin JavaScript portları, örneğin emscripten üzerinden port edilmiş hâlleri çıkıyor; bir şeyi kaçırıyor muyum merak ediyorum
LFortran’ın daha derinlemesine ele alınmaması garip
Çevrimiçi çalışan harika ve şaşırtıcı bir WASM örneği de var
https://dev.lfortran.org/
2020’de birçok özellik eksikti ve Fortran’ın yalnızca çok küçük bir alt kümesini destekliyordu; ancak şimdi çok daha geniş dil özelliklerini destekliyor ve oldukça fazla Fortran kodunu derleyebiliyor
WebAssembly’ye de yerel olarak derleme yapabiliyor
Ancak geliştiriciler, LFortran’ı kullanmak için hâlâ pürüzlü noktalar olduğunu, projenin şu anda alfa aşamasında kabul edildiğini ve gerçek kodu derlerken sorunlar çıkabileceğini söylüyor
MINPACK gibi bazı projeler başarıyla derlenebiliyor; fakat Fortran’ın tüm belirtimini henüz desteklemediği için daha büyük projelerin önemli bir kısmı hâlâ derlenemiyor
LFortran geliştiricileri Fortran 2018’i tam desteklemeyi hedefliyor ve öne çıkan özelliklerden biri Jupyter benzeri etkileşimli bir Fortran REPL’i
Birkaç yıl daha geliştirilirse WebAssembly için Fortran kodu derlemede harika bir seçenek olacağı düşünülüyor
Ayrıca https://dev.lfortran.org adresindeki LFortran demosuna bakılması söyleniyor; çok etkileyici, ancak ilk denediğim
x * 2ifadesinix * 3olarak değiştirme işi mevcut kod üreticisi tarafından desteklenmiyordu.NET ve Java üzerinde Fortran da var
https://www.silverfrost.com/14/ftn95/ftn95_fortran_95_for_microsoft_dotnet_features.aspx
https://dl.acm.org/doi/10.1145/376656.376833
https://medium.com/@tomasreimers/compiling-tensorflow-for-the-browser-f3387b8e1e1c üzerinde çalışırken TensorFlow’un Fortran ile yazılmış popüler matematik kütüphanesi BLAS/Lapack yerine Eigen kullanmasına gerçekten çok sevindiğimi düşündüm
Aksi olsaydı iş çok daha fazla olurdu