diff --git a/README.tr.md b/README.tr.md new file mode 100644 index 0000000..dc576b3 --- /dev/null +++ b/README.tr.md @@ -0,0 +1,20 @@ +FFMPEG Assembly Dil Okulu'na hoş geldiniz. Programlamadaki en ilginç, zorlayıcı ve ödüllendirici yolculuğa ilk adımı attınız. + +Bu dersler, FFmpeg'de Assembly dilinin nasıl yazıldığına dair temel bilgiler verecek ve bilgisayarınızda gerçekte neler olup bittiğini anlamanıza yardımcı olacaktır. + +**Gerekli Bilgiler** + +* C dili bilgisi, özellikle işaretçiler. C dilini bilmiyorsanız [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) kitabı ile çalışın. +* Lise Matematiği (skaler ve vektörel, toplama, çarpma vb.) + +**Dersler** + +Bu Git deposunda, her dersle ilgili dersler ve ödevler (henüz yüklenmemiş) bulunmaktadır. Derslerin sonunda FFmpeg'e katkıda bulunabileceksiniz. + +Soruları yanıtlamak için bir Discord sunucusu mevcuttur: +https://discord.com/invite/Ks5MhUhqfB + +**Çeviriler** +* [İngilizce](./README.md) +* [Fransızca](./README.fr.md) +* [İspanyolca](./README.es.md) diff --git a/lesson_01/index.tr.md b/lesson_01/index.tr.md new file mode 100644 index 0000000..c98faf0 --- /dev/null +++ b/lesson_01/index.tr.md @@ -0,0 +1,214 @@ +**FFmpeg Assembly Dili Birinci Ders** + +**Giriş** + +FFMPEG Assembly Dil Okulu'na hoş geldiniz. Programlamadaki en ilginç, zorlayıcı ve ödüllendirici yolculuğa ilk adımı attınız. Bu dersler, FFmpeg'de Assembly dilinin nasıl yazıldığına dair temel bilgiler verecek ve bilgisayarınızda gerçekte neler olup bittiğini anlamanıza yardımcı olacaktır. + +**Gerekli Bilgiler** + +* C dili bilgisi, özellikle işaretçiler. C dilini bilmiyorsanız [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) kitabı ile çalışın. +* Lise Matematiği (skaler ve vektörel, toplama, çarpma vb.) + +**Assembly dili nedir?** + +Assembly dili, bir CPU'nun işlediği komutlara doğrudan karşılık gelen kod yazdığınız bir programlama dilidir. İnsan tarafından okunabilir assembly dili -adından da anlaşılacağı gibi- CPU'nun anlayabileceği *makine kodu* olarak bilinen ikili verilere *dönüştürülür* (assemble edilir). Assembly dilinin kısaca “assembly” veya “asm” olarak adlandırıldığını görebilirsiniz. + +FFMPEG'de bulunan Assembly kodlarının büyük bir kısmı *SIMD, Single Instruction Multiple Data* (Tek Talimat Çoklu Veri) olarak bilinir. +SIMD bazen vektör programlama olarak da adlandırılır. Bu, belirli bir komutun aynı anda birden fazla veri öğesi üzerinde işlem yaptığı anlamına gelir. Skaler programlama olarak bilinen çoğu programlama dili bir seferde tek bir veri öğesi üzerinde işlem yapar. + +Anlayacağınız SIMD, bellekte sıralı olarak düzenlenmiş çok sayıda veri içeren görüntü, video ve ses dosyalarının işlenmesine çok uygundur. CPU'da sıralı verileri işlememize yardımcı olan özel komutlar bulunmaktadır. + +FFmpeg'de, “assembly fonksiyonu”, ‘SIMD’ ve “vektör(leştirme)” terimleri birbirinin yerine kullanılmaktadır. Bunların hepsi birden fazla veri öğesini tek seferde işlemek için Assembly dilinde elle bir fonksiyon yazmayı ifade eder. Bazı projeler bunları “Assembly çekirdekleri” olarak da adlandırabilir. + +Tüm bunlar kulağa karmaşık gelebilir, ancak FFmpeg'de lise öğrencilerinin Assembly kodu yazdığını unutmamak önemlidir. Her şeyde olduğu gibi, öğrenmenin %50'si jargon, %50'si ise gerçek öğrenmedir. + +**Neden Assembly dilinde yazıyoruz?** +Multimedya işlemeyi hızlandırmak için. Assembly kodu yazarak 10 kat veya daha fazla hız artışı elde etmek çok yaygındır ve bu, videoları gerçek zamanlı olarak takılmadan oynatmak istediğinizde özellikle önemlidir. Ayrıca enerji tasarrufu sağlar ve pil ömrünü uzatır. Video kodlama ve kod çözme işlevlerinin hem son kullanıcılar hem de veri merkezlerindeki büyük şirketler tarafından en yoğun kullanılan işlevlerden bazıları olduğunu belirtmek gerekir. Bu nedenle, küçük bir iyileştirme bile hızlı bir şekilde büyük bir fark yaratır. + +Çevrimiçi ortamda, insanların daha hızlı geliştirme sağlamak için Assembly komutlarına eşlenen C benzeri işlevler olan *intrinsics* kullandığını sıklıkla görebilirsiniz. FFmpeg'de intrinsics kullanmıyoruz, bunun yerine Assembly kodunu elle yazıyoruz. Bu tartışmalı bir konudur, ancak intrinsics genellikle derleyiciye bağlı olarak elle yazılmış Assembly kodundan yaklaşık %10-15 daha yavaştır ancak intrinsics destekçileri buna katılmayacaktır. FFmpeg için ekstra performansın her bir parçası önemlidir, bu yüzden doğrudan Assembly kodu yazıyoruz. Intrinsics'in “[Macar Notasyonu (İngilizce)](https://en.wikipedia.org/wiki/Hungarian_notation)” kullanımı nedeniyle okunması zor olduğu yönünde bir tartışma da vardır. + +Geçmiş nedenlerden dolayı FFmpeg'de veya Linux çekirdeği gibi projelerdeki çok özel kullanım durumları nedeniyle birkaç yerde *inline assembly*, yani intrinsics kullanmayan yöntem görebilirsiniz. Burada Assembly kodu ayrı bir dosyada değil, C kodu ile birlikte inline olarak yazılmıştır. FFmpeg gibi projelerde hakim görüş, bu kodun okunmasının zor olduğu, derleyiciler tarafından yaygın olarak desteklenmediği ve bakımı zor olduğu yönündedir. + +Son olarak, internette birçok kendini uzman ilan eden kişinin, bunların hiçbirinin gerekli olmadığını ve derleyicinin tüm bu “vektörleştirme” işlemlerini sizin için yapabileceğini söylediğini göreceksiniz. En azından öğrenme amacıyla, bunları görmezden gelin: örneğin [dav1d projesi](https://www.videolan.org/projects/dav1d.html) gibi son testler, bu otomatik vektörleştirme ile yaklaşık 2 kat hız artışı sağlarken, elle yazılmış versiyonlar 8 kata kadar ulaşabildiğini gösterdi. + +**Assembly dilinin özellikleri** + +Bu dersler x86 64-bit Assembly dilini baz alacaktır. Bu ayırca amd64 olarak da bilinir fakat Intel CPU'lar ile de çalışmaktadır. Assembly'nin ARM ve RISC-V gibi farklı CPU'lar için olan türü de vardır ve muhtemelen önümüzdeki dersler bunları da kapsayacaktır. + +İnternette AT&T ve Intel olarak iki tür x86 Assembly sözdizimi göreceksiniz. AT&T sözdizimi Intel sözdizimine kıyasla daha eskidir ve okunması daha zordur. Bu nedenle Intel sözdizimini kullanacağız. + +**Destekleyici materyaller** +Stack Overflow gibi kitapların veya çevrimiçi kaynakların referans olarak pek yardımcı olmadığını duyunca şaşırabilirsiniz. Bunun nedeni nispeten Intel sözdizimini kullanan el yazısı derlemeyi tercih etmemizdir. Ancak bunun yanı sıra, birçok çevrimiçi kaynak işletim sistemi programlama veya donanım programlamaya odaklanmaktadır ve genellikle SIMD olmayan kod kullanmaktadır. FFmpeg Assembly özellikle yüksek performanslı görüntü işlemeye odaklanmıştır ve göreceğiniz gibi, Assembly programlamaya özellikle benzersiz bir yaklaşımdır. Bununla birlikte, bu dersleri tamamladıktan sonra diğer Assembly kullanım örneklerini anlamak kolaydır. + +Birçok kitap, Assembly öğretmeden önce bilgisayar mimarisiyle ilgili birçok ayrıntıya girer. Öğrenmek istediğiniz şey buysa sorun yok ve bizim açımızdan bu araba kullanmayı öğrenmeden önce motorları incelemek gibidir. + +Bununla birlikte “The Art of 64-bit Assembly” kitabının sonraki bölümlerinde SIMD komutlarını ve davranışlarını görsel bir biçimde gösteren diyagramlar yararlı olabilir: [https://artofasm.randallhyde.com/](https://artofasm.randallhyde.com/) + +Soruları yanıtlamak için bir Discord sunucusu mevcuttur: +[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB) + +**Yazmaçlar** +Yazmaçlar, CPU'da verilerin işlenebildiği alanlardır. CPU'lar bellek üzerinde doğrudan işlem yapmazlar, bunun yerine veriler yazmaçlara yüklenir, işlenir ve belleğe geri yazılır. Assembly dilinde genellikle verileri bir yazmaç üzerinden geçirmeden bir bellek konumundan başka bir bellek konumuna doğrudan kopyalayamazsınız. + +**Genel Amaçlı Yazmaçlar** +İlk yazmaç türü, Genel Amaçlı Yazmaç (GPR) olarak bilinir. GPR'lar genel amaçlı olarak adlandırılır çünkü hem veri (bu durumda 64-bit'e kadar bir değer) hem de bir bellek adresi (işaretçi) içerebilirler. Bir GPR'deki değer, toplama, çarpma, kaydırma vb. işlemlerle işlenebilir. + +Çoğu Assembly kitabında, GPR'lerin inceliklerine, tarihsel geçmişine vb. ayrılmış bütün bölümler vardır. Bunun nedeni, GPR'lerin işletim sistemi programlama, tersine mühendislik vb. konularda önemli olmasıdır. FFmpeg'de yazılan Assembly kodunda GPR'ler daha çok bir iskelet görevi görür, çoğu zaman karmaşıklıklarına ihtiyaç duyulmaz ve soyutlanarak ortadan kaldırılır. + +**Vektör yazmaçları** +Vektör (SIMD) yazmaçları, adından da anlaşılacağı gibi, birden fazla veri öğesi içerir. Çeşitli türde vektör yazmaçları vardır: + +* mm yazmaçları - MMX yazmaçları, 64 bit boyutunda, eski ve artık pek kullanılmayan +* xmm yazmaçları - XMM yazmaçları, 128 bit boyutunda, yaygın olarak kullanılabilir +* ymm yazmaçları - YMM yazmaçları, 256 bit boyutunda, kullanırken bazı zorluklar olabilir +* zmm yazmaçları - ZMM yazmaçları, 512 bit boyutunda, sınırlı kullanılabilirlik + +Video sıkıştırma ve açma işlemlerindeki hesaplamaların çoğu tamsayı tabanlıdır, bu yüzden biz de buna uyacağız. İşte xmm yazmacındaki 16 baytlık bir örnek: + +| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +Fakat 8 kelime de olabilir (16 bitlik tamsayılar) + +| a | b | c | d | e | f | g | h | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +ya da 4 çift kelime (doublewords) (32 bitlik tamsayılar) + +| a | b | c | d | +| :---- | :---- | :---- | :---- | + +Veya iki quadwords (64 bitlik tamsayılar): + +| a | b | +| :---- | :---- | + +Özetle: + + +* **b**ytes - 8 bitlik veri +* **w**ords - 16 bitlik veri +* **d**oublewords - 32 bitlik veri +* **q**uadwords - 64 bitlik veri +* **d**ouble **q**uadwords - 128 bitlik veri + +Kalın yazılmış karakterler daha sonra önemli olacaktır. + +**x86inc.asm dahil** +Birçok örnekte x86inc.asm dosyasını dahil ettiğimizi göreceksiniz. X86inc.asm, FFmpeg, x264 ve dav1d'de birleştirme programcılarının işini kolaylaştırmak için kullanılan hafif bir soyutlama katmanıdır. Birçok yönden yardımcı olur ancak başta yaptığı yararlı şeylerden biri GPR'lari, r0, r1, r2 olarak etiketlemektir. Bu herhangi bir kayıt adını hatırlamanız gerekmediği anlamına gelir. Daha önce de belirtildiği gibi, GPR'ler genellikle sadece iskelet görevi görür, bu da işi çok daha kolaylaştırır. +**Basit bir skaler asm parçası** + +Neler olup bittiğini görmek için basit ve oldukça yapay bir skaler asm (her komutta tek tek veri öğeleri üzerinde işlem yapan assembly kodu) parçasına bakalım: + +```assembly +mov r0q, 3 +inc r0q +dec r0q +imul r0q, 5 +``` + +İlk satırda, *anlık değer* 3 (bellekten alınmak yerine doğrudan assembly kodunun içinde saklanan bir değer), r0 yazmacına bir quadword olarak depolanır. Intel sözdiziminde, kaynak işlenen (sağda bulunan, veriyi sağlayan değer veya konum), hedef işlenene (solda bulunan, veriyi alan konum) tıpkı memcpy'nin davranışına benzer şekilde aktarılır. Sıra aynı olduğu için bunu “r0q = 3” olarak da okuyabilirsiniz. r0'ın “q” soneki, yazmacın bir quadword olarak kullanıldığını belirtir. inc değeri artırır, böylece r0q 4 içerir, dec değeri tekrar 3'e düşürür. imul değeri 5 ile çarpar. Yani sonunda r0q 15 içerir. + +mov ve inc gibi insan tarafından okunabilir komutların, assembler tarafından makine koduna dönüştürülen hallerinin *mnemonics* olarak bilindiğini unutmayın. Çevrimiçi kaynaklarda ve kitaplarda MOV ve INC gibi büyük harflerle temsil edilen mnemonics görebilirsiniz, ancak bunlar küçük harfli versiyonlarla aynıdır. FFmpeg'de küçük harfli mnemonics kullanırız ve büyük harfleri makrolar için ayırırız. + +**Basit bir vektör fonksiyonunu anlayalım** + +İşte ilk SIMD fonksiyonumuz: + +```assembly +%include "x86inc.asm" + +SECTION .text + +;static void add_values(uint8_t *src, const uint8_t *src2) +INIT_XMM sse2 +cglobal add_values, 2, 2, 2, src, src2 + movu m0, [srcq] + movu m1, [src2q] + + paddb m0, m1 + + movu [srcq], m0 + + RET +``` + +Satır satır üzerinden geçelim + +```assembly +%include "x86inc.asm" +``` + +Bu, x264, FFmpeg ve dav1d topluluklarında geliştirilen bir “başlıktır" ve derleme yazımını basitleştirmek için yardımcılar, önceden tanımlanmış isimler ve makrolar (aşağıdaki cglobal gibi) sağlar. + +```assembly +SECTION .text +``` + +Bu, yürütmek istediğiniz kodun yerleştirildiği bölümü belirtir. Bu, sabit verileri yerleştirebileceğiniz .data bölümünün aksine bir durumdur. + +```assembly +;static void add_values(uint8_t *src, const uint8_t *src2) +INIT_XMM sse2 +``` + +İlk satır, fonksiyon argümanının C dilinde nasıl göründüğünü gösteren bir yorumdur (asm'deki noktalı virgül “;” C dilindeki “//” gibidir). İkinci satır, sse2 komut setini kullanarak fonksiyonu XMM kayıtlarını kullanacak şekilde nasıl başlattığımızı gösterir. Bunun nedeni, paddb'nin bir sse2 komutu olmasıdır. Bir sonraki derste sse2'yi daha ayrıntılı olarak ele alacağız. + +```assembly +cglobal add_values, 2, 2, 2, src, src2 +``` + +Bu, “add_values” adlı bir C işlevini tanımladığı için önemli bir satırdır. + +Her bir öğeyi tek tek inceleyelim: + +* Bir sonraki parametre, iki işlev argümanı olduğunu gösterir. +* Bundan sonraki parametre, argümanlar için iki GPR kullanacağımızı gösterir. Bazı durumlarda daha fazla GPR kullanmak isteyebiliriz, bu nedenle x86util'e daha fazlasına ihtiyacımız olduğunu belirtmeliyiz. +* Bundan sonraki parametre, x86util'e kaç tane XMM kaydı kullanacağımızı bildirir. +* Sonraki iki parametre, işlev argümanları için etiketlerdir. + +Eski kodlarda işlev argümanları için etiketler olmayabilir, bunun yerine r0, r1 vb. kullanılarak GPR'lere doğrudan adres verilebilir. + +```assembly + movu m0, [srcq] + movu m1, [src2q] +``` + +movu, movdqu (move double quad unaligned) komutunun kısaltmasıdır. Hizalama konusu başka bir derste ele alınacaktır, ancak şimdilik movu, [srcq]'dan 128 bitlik bir taşıma olarak düşünülebilir. mov komutunda, köşeli parantezler [srcq]'daki adresin dereferanslandığını, yani C dilinde **src ile eşdeğer olduğunu gösterir.* Bu, yükleme olarak bilinir. “q” sonekinin işaretçinin boyutunu ifade ettiğini unutmayın *(*yani C'de 64 bit sistemlerde *sizeof(*src) == 8 anlamına gelir ve x86asm, 32 bit sistemlerde 32 bit kullanacak kadar akıllıdır), ancak temel yükleme 128 bittir. + +Vektör yazmaçlarına tam adlarıyla, bu durumda xmm0 ile, değil m0 gibi soyut bir biçimle atıfta bulunmadığımızı unutmayın. İleriki derslerde, bunun bir kez kod yazıp birden fazla SIMD kayıt boyutunda çalışmasını nasıl sağladığını göreceksiniz. + +```assembly +paddb m0, m1 +``` + +paddb (bunu kafanızda *p-add-b* olarak okuyun) aşağıda gösterildiği gibi her bir kayıtta her bir baytı ekler. “p” öneki ‘paketlenmiş’ anlamına gelir ve vektör komutlarını skaler komutlardan ayırmak için kullanılır. “b” soneki ise bunun bayt bazında toplama (baytların toplamı) olduğunu gösterir. + +| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +\+ + +| q | r | s | t | u | v | w | x | y | z | aa | ab | ac | ad | ae | af | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +\= + +| a+q | b+r | c+s | d+t | e+u | f+v | g+w | h+x | i+y | j+z | k+aa | l+ab | m+ac | n+ad | o+ae | p+af | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +```assembly +movu [srcq], m0 +``` + +Bu, depo olarak bilinen şeydir. Veriler srcq işaretçisindeki adrese geri yazılır. + +```assembly +RET +``` + +Bu, fonksiyonun döndürdüğü değeri belirtmek için kullanılan bir makrodur. FFmpeg'deki neredeyse tüm Assembly fonksiyonları, bir değer döndürmek yerine argümanlardaki verileri değiştirir. + +Ödevde göreceğiniz gibi Assembly fonksiyonlarına fonksiyon işaretçileri oluşturuyor ve bunları kullanılabilir oldukları yerlerde kullanıyoruz. + +[Sonraki Ders](../lesson_02/index.tr.md) diff --git a/lesson_02/index.tr.md b/lesson_02/index.tr.md new file mode 100644 index 0000000..f1cd965 --- /dev/null +++ b/lesson_02/index.tr.md @@ -0,0 +1,168 @@ +**FFmpeg Assembly Dili İkinci Ders** + +Assembly dilinde ilk fonksiyonunuzu yazdığınıza göre, şimdi dallanma ve döngüleri tanıtacağız. + +Öncelikle etiketler ve atlamalar kavramını tanıtmamız gerekiyor. Aşağıdaki yapay örnekte, jmp komutu kod komutunu “.loop:” sonrasına taşır. “. loop:" bir *etiket* olarak bilinir ve etiketin önündeki nokta, bunun bir *yerel etiket* olduğunu gösterir, böylece aynı etiket adını birden fazla fonksiyonda yeniden kullanabilirsiniz. Bu örnekte elbette sonsuz bir döngü gösterilmektedir, ancak bunu daha sonra daha gerçekçi bir şeye genişleteceğiz. + +```assembly +mov r0q, 3 +.loop: + dec r0q + jmp .loop +``` + +Gerçekçi bir döngü oluşturmadan önce *FLAGS* kaydını tanıtmamız gerekiyor. *FLAGS*'in karmaşıklığına yine GPR işlemleri büyük ölçüde iskele görevi gördüğü için çok fazla girmeyeceğiz ancak aritmetik işlemler ve kaydırmalar gibi skaler veriler üzerindeki çoğu non-mov komutunun çıktısına göre ayarlanan Zero-Flag, Sign-Flag ve Overflow-Flag gibi birkaç bayrak vardır. + +İşte döngü sayacı sıfıra kadar geri sayarken jg (sıfırdan büyükse atla) döngü koşulu olan bir örnek. dec r0q, komuttan sonra r0q değerine göre FLAG'leri ayarlar ve bunlara göre atlama yapabilirsiniz. + +```assembly +mov r0q, 3 +.loop: + ; do something + dec r0q + jg .loop ; jump if greater than zero +``` + +Bu, aşağıdaki C koduna eşdeğerdir: + +```c +int i = 3; +do +{ + // do something + i--; +} while(i > 0) +``` + +Bu C kodu çok da doğal olmayan bir yapıya sahiptir. Genellikle C dilinde bir döngü şu şekilde yazılır: + +```c +int i; +for(i = 0; i < 3; i++) { + // do something +} +``` + +Bu, kabaca şuna eşdeğerdir (```for``` döngüsünü eşleştirmek için basit bir yol yoktur): + +```assembly +xor r0q, r0q +.loop: + ; do something + inc r0q + cmp r0q, 3 + jl .loop ; jump if (r0q - 3) < 0, i.e (r0q < 3) +``` + +Bu kod parçasında dikkat edilmesi gereken birkaç nokta var. İlki, ```xor r0q, r0q``` komutudur. Bu, bir kaydı sıfıra ayarlamanın yaygın bir yoludur ve bazı sistemlerde ```mov r0q, 0``` komutundan daha hızlıdır, çünkü basitçe söylemek gerekirse, gerçek bir yükleme işlemi gerçekleşmez. Bu komut, SIMD kayıtlarında ```pxor m0, m0``` ile birlikte kullanılarak tüm kaydı sıfırlayabilir. Dikkat edilmesi gereken bir diğer nokta ise cmp kullanımıdır. cmp, ikinci kaydı ilk kayıttan etkili bir şekilde çıkarır (değeri hiçbir yere kaydetmeden) ve *FLAGS*'ı ayarlar, ancak yorumda belirtildiği gibi, ```r0q < 3``` ise atlamak için atlama ile birlikte okunabilir (jl = sıfırdan küçükse atlama). + +Bu kod parçasında fazladan bir komut (cmp) olduğunu unutmayın. Genel olarak, komut sayısı ne kadar azsa kod o kadar hızlıdır, bu nedenle önceki kod parçası tercih edilir. İleriki derslerde göreceğiniz gibi, bu fazladan komutu önlemek ve *FLAGS*'ı aritmetik veya başka bir işlemle ayarlamak için kullanılan başka püf noktaları da vardır. C döngüleriyle tam olarak eşleşecek şekilde assembly yazmadığımızı, döngüleri assembly'de olabildiğince hızlı hale getirmek için yazdığımızı unutmayın. + +İşte kullanacağınız bazı yaygın atlama mnemonikleri (*FLAGS* tamlık için vardır, ancak döngü yazmak için ayrıntıları bilmenize gerek yoktur): + +| Mnemonic | Description | FLAGS | +| :---- | :---- | :---- | +| JE/JZ | Jump if Equal/Zero | ZF = 1 | +| JNE/JNZ | Jump if Not Equal/Not Zero | ZF = 0 | +| JG/JNLE | Jump if Greater/Not Less or Equal (signed) | ZF = 0 and SF = OF | +| JGE/JNL | Jump if Greater or Equal/Not Less (signed) | SF = OF | +| JL/JNGE | Jump if Less/Not Greater or Equal (signed) | SF ≠ OF | +| JLE/JNG | Jump if Less or Equal/Not Greater (signed) | ZF = 1 or SF ≠ OF | + +**Sabitler** + +Sabitleri nasıl kullanacağınıza dair bazı örneklere bakalım: + +```assembly +SECTION_RODATA + +constants_1: db 1,2,3,4 +constants_2: times 2 dw 4,3,2,1 +``` + +* SECTION_RODATA, bunun salt okunur bir veri bölümü olduğunu belirtir. (Bu bir makrodur, çünkü işletim sistemlerinin kullandığı farklı çıktı dosyası biçimleri bunu farklı şekilde bildirir.) +* constants_1: constants_1 etiketi, ```db``` (bayt bildirimi) olarak tanımlanır - yani uint8_t constants_1[4] = {1, 2, 3, 4}; ile eşdeğerdir. +* constants_2: Bu, ```times 2``` makrosunu kullanarak bildirilen kelimeleri tekrarlar - yani uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1}; ile eşdeğerdir. + +Derleyici tarafından bellek adresine dönüştürülen bu etiketler yüklemelerde kullanılabilir ancak salt okunur oldukları için depolamalarda kullanılamazlar. Bazı komutlar, bellek adresini işlenen olarak alırlar, bu nedenle kayıtlara açıkça yüklenmeden kullanılabilirler ki bunun avantajları ve dezavantajları vardır. + +**Ofsetler** + +Ofsetler, bellekteki ardışık öğeler arasındaki mesafedir (bayt cinsinden). Ofset, veri yapısındaki **her bir öğenin boyutu** tarafından belirlenir. + +Artık döngüler yazabildiğimize göre, veri almaya geçebiliriz. Ancak C ile bazı farklılıklar vardır. C'deki aşağıdaki döngüye bakalım: + +```c +uint32_t data[3]; +int i; +for(i = 0; i < 3; i++) { + data[i]; +} +``` + +Veri öğeleri arasındaki 4 baytlık ofset, C derleyicisi tarafından önceden hesaplanır. Ancak, elle assembler yazarken bu ofsetleri kendiniz hesaplamanız gerekir. + +Bellek adresi hesaplamaları için sözdizimine bakalım. Bu, tüm bellek adresi türleri için geçerlidir: + +```assembly +[base + scale*index + disp] +``` + +* base - Bu bir GPR'dır (genellikle bir C fonksiyon argümanından gelen bir işaretçi) +* scale - Bu 1, 2, 4, 8 olabilir. Varsayılan olarak 1'dir. +* index - Bu bir GPR'dır. (genellikle bir döngü sayacı) +* disp - Bu bir tam sayıdır (32 bit'e kadar). Yer değiştirme (displacement), verilerdeki bir kaydırmadır. + +x86asm, çalıştığınız SIMD kaydının boyutunu öğrenmenizi sağlayan mmsize sabitini sağlar. + +Özel ofsetlerden yüklemeyi açıklamak için basit ve anlamsız bir örnek verelim: + +```assembly +;static void simple_loop(const uint8_t *src) +INIT_XMM sse2 +cglobal simple_loop, 1, 2, 2, src + movq r1q, 3 +.loop: + movu m0, [srcq] + movu m1, [srcq+2*r1q+3+mmsize] + + ; do some things + + add srcq, mmsize +dec r1q +jg .loop + +RET +``` + +```movu m1, [srcq+2*r1q+3+mmsize]``` komutunda, assembler kullanılacak doğru yer değiştirme sabitini önceden hesaplayacaktır. Bir sonraki derste döngüde toplama ve çıkarma işlemleri yapmak zorunda kalmamak için bunları tek bir toplama işlemiyle değiştirebileceğiniz bir püf noktası göstereceğiz. + +**LEA** + +Ofsetleri anladıktan sonra, lea (Load Effective Address) komutunu kullanabilirsiniz. Bu komut, tek bir komutla çarpma ve toplama işlemlerini gerçekleştirmenizi sağlar, bu da birden fazla komut kullanmaktan daha hızlıdır. Elbette, çarpma ve toplama işlemlerinde bazı sınırlamalar vardır, ancak bu, lea komutunun güçlü bir komut olmasını engellemez. + +```assembly +lea r0q, [base + scale*index + disp] +``` + +Adının aksine, LEA normal aritmetik işlemlerin yanı sıra adres hesaplamaları için de kullanılabilir. Şu kadar karmaşık bir işlem yapabilirsiniz: + +```assembly +lea r0q, [r1q + 8*r2q + 5] +``` + +Bunun r1q ve r2q'nun içeriğini etkilemediğini unutmayın. Ayrıca *FLAGS*'ı da etkilemez (bu nedenle çıktıya göre atlama yapamazsınız). LEA kullanmak tüm bu komutları ve geçici kayıtları önler (bu kod eşdeğer değildir çünkü add *FLAGS*'ı değiştirir): + +```assembly +movq r0q, r1q +movq r3q, r2q +sal r3q, 3 ; shift arithmetic left 3 = * 8 +add r3q, 5 +add r0q, r3q +``` + +Döngülerden önce adresleri ayarlamak veya yukarıdaki gibi hesaplamalar yapmak için lea'in sıklıkla kullanıldığını göreceksiniz. Elbette her tür çarpma ve toplama işlemini yapamayacağınızı unutmayın, ancak 1, 2, 4, 8 ile çarpma ve sabit bir ofsetin toplama işlemi yaygın olarak kullanılır. + +Ödevde, bir sabiti yüklemeniz ve değerleri bir döngü içinde SIMD vektörüne eklemeniz gerekecektir. + +[Sonraki Ders](../lesson_03/index.tr.md) diff --git a/lesson_03/index.tr.md b/lesson_03/index.tr.md new file mode 100644 index 0000000..931b082 --- /dev/null +++ b/lesson_03/index.tr.md @@ -0,0 +1,203 @@ +**FFmpeg Assembly Dili Üçüncü Ders** + +Bazı jargonları açıklayalım ve size tarihçesini anlatalım. + +**Komut Setleri** + +Modern işlemcilerde SIMD (Single Instruction, Multiple Data / Tek Komut, Çoklu Veri) komut setleri, paralel veri işleme için büyük öneme sahiptir. Her yeni CPU jenerasyonu, daha geniş veri yolları ve gelişmiş komutlarla gelir. x86 mimarisinde komut setlerinin evrimi şu şekildedir: + +* **MMX (1997):** Intel’in ilk SIMD komut seti; 64-bit yazmaçlar ile temel paralel işlemler. +* **SSE (1999):** 128-bit yazmaçlar; daha yüksek bant genişliği ve yeni komutlar. +* **SSE2 (2000):** Daha fazla komut ve veri tipi desteği; video ve grafik işleme için önemli. +* **SSE3 (2004):** Yatay işlemler (aynı yazmaç içindeki veriler arasında) için ilk komutlar. +* **SSSE3 (2006):** Video işleme için kritik pshufb shuffle komutu ve ek fonksiyonlar. +* **SSE4 (2008):** Minimum/maksimum gibi yeni paketlenmiş işlemler; daha fazla optimizasyon. +* **AVX (2011):** 256-bit yazmaçlar (float için); üç operantlı komut formatı ile daha esnek kodlama. +* **AVX2 (2013):** Tam sayı işlemlerinde 256-bit desteği; veri işleme kapasitesi artışı. +* **AVX512 (2017):** 512-bit yazmaçlar; maskeleme ve gelişmiş shuffle (vpermb) komutları. FFmpeg’de başlangıçta sınırlı kullanıldı zira bazı komutlar CPU frekansını düşürüyordu. +* **AVX512ICL (2019):** Intel’in yeni nesil AVX512’si; frekans düşüşü problemi giderildi. +* **AVX10 (Yakında):** Daha da gelişmiş paralel işlem yetenekleriyle gelecek. + +Her komut seti, donanımın sunduğu paralellik ve performans olanaklarını artırır. Uygulama geliştiricileri, kodlarını bu komut setlerine göre optimize ederek donanımın maksimum kapasitesinden faydalanabilir. + +Komut setlerinin CPU’lara sadece eklenmekle kalmayıp kaldırılabildiğini da unutmamak gerekir. Örneğin AVX512, tartışmalı biçimde, 12. Nesil Intel CPU’larında [kaldırıldı/devre dışı bırakıldı](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/). FFmpeg bu nedenle çalışma zamanında CPU tespiti (runtime CPU detection) yapar; yani üzerinde çalıştığı CPU’nun yeteneklerini dinamik olarak algılar. + +Ödevde gördüğünüz gibi, function pointer’lar varsayılan olarak C (genel) sürümü gösterir ve sonra uygun komut seti varyantı ile değiştirilir. Böylece algılama bir kez yapılır ve tekrar gerekmez. Bu, belirli bir komut setini koda gömüp (hardcode ederek) aslında tamamen işlevsel bir bilgisayarı erken “eski” (obsolete) ilan eden birçok kapalı kaynak uygulamanın tersine bir yaklaşımdır. Aynı zamanda optimize edilmiş işlevlerin çalışma zamanında açılıp kapatılmasını sağlar. Bu açık kaynak olmanın büyük avantajlarından biridir. + +FFmpeg gibi programlar dünyada bazıları çok eski olan milyarlarca cihazda kullanılır. FFmpeg teknik olarak yalnızca SSE destekleyen (yaklaşık 25 yıllık) makineleri bile destekler! Neyse ki x86inc.asm, belirli bir komut setinde bulunmayan bir komutu kullanırsanız sizi uyarabilecek kapasitededir. + +Gerçek dünyadaki durum hakkında fikir vermesi için, Kasım 2024 itibarıyle (oyuncu kitlesine doğal olarak taraflı) [Steam Anketi](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) verilerine göre komut seti bulunurluk oranları: + +| Instruction Set | Availability | +| :---- | :---- | +| SSE2 | 100% | +| SSE3 | 100% | +| SSSE3 | 99.86% | +| SSE4.1 | 99.80% | +| AVX | 97.39% | +| AVX2 | 94.44% | +| AVX512 (Steam AVX512 ile AVX512ICL’i ayırmıyor) | 14.09% | + +FFmpeg gibi milyarlarca kullanıcıya sahip bir uygulama için, %0.1’lik bir oran bile çok büyük bir kullanıcı ve hata raporu sayısına denk gelir. Bu nedenle FFmpeg, [FATE test suite](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F) aracılığıyla CPU/OS/Compiler çeşitliliklerini test etmek için kapsamlı bir test altyapısına sahiptir. Her bir commit, hiçbir şeyin bozulmadığından emin olmak için yüzlerce makinede çalıştırılır. + +Intel, [burada](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) ayrıntılı bir komut seti kılavuzu sağlar. + +Bir PDF içinde arama yapmak zahmetli olabileceğinden, [burada](https://www.felixcloutier.com/x86/) resmi olmayan bir web tabanlı alternatif bulunmaktadır. + +Ayrıca SIMD talimatlarının görsel temsili için [burada](https://www.officedaytime.com/simd512e/) bir kaynak mevcuttur. + +x86 assembly dilinin zorluklarından biri, ihtiyaçlarınıza uygun doğru komutu bulmaktır. Bazı durumlarda komutlar ilk olarak tasarlandıkları şekilde kullanılmayabilir. + +**Pointer offset trickery** + +Orijinal (1. Dersteki) fonksiyonumuza dönelim; fakat C fonksiyonuna bir de width (genişlik) argümanı ekleyelim. + +Width değişkeni için int yerine ptrdiff_t kullanıyoruz; böylece 64-bit argümanın üst 32 bitinin sıfır olduğundan emin oluyoruz. Fonksiyon imzasında int width geçirip sonra onu işaretçi aritmetiğinde quad (ör. `widthq`) olarak kullansaydık yazmacın üst 32 bitine rastgele değerler sızabilirdi. Bunu `movsxd` ile (x86inc.asm içindeki `movsxdifnidn` makrosuna da bakınız) işaret genişletmesi yaparak düzeltebilirdik; ancak bu yol daha kolay. + +Aşağıdaki fonksiyon bu işaretçi ofset numarasını içeriyor: + +```assembly +;static void add_values(uint8_t *src, const uint8_t *src2, ptrdiff_t width) +INIT_XMM sse2 +cglobal add_values, 3, 3, 2, src, src2, width + add srcq, widthq + add src2q, widthq + neg widthq + +.loop + movu m0, [srcq+widthq] + movu m1, [src2q+widthq] + + paddb m0, m1 + + movu [srcq+widthq], m0 + add widthq, mmsize + jl .loop + + RET +``` + +Adım adım inceleyelim (ilk bakışta kafa karıştırıcı olabilir): + +```assembly + add srcq, widthq + add src2q, widthq + neg widthq +``` + +width her iki işaretçiye eklenir; böylece işaretçilerin her biri artık işlenecek tamponun (buffer) sonunu gösterir. Ardından width negatiflenir. + +```assembly + movu m0, [srcq+widthq] + movu m1, [src2q+widthq] +``` + +Yüklemeler widthq negatifken yapılır. İlk iterasyonda `[srcq+widthq]` srcq’nun orijinal adresini (tamponun başlangıcı) işaret eder. + +```assembly + add widthq, mmsize + jl .loop +``` + +Negatif widthq’ya mmsize eklenerek sıfıra yaklaşması sağlanır. Artık döngü koşulu jl (sıfırdan küçük ise zıpla). Bu hile widthq’nun hem işaretçi ofseti **hem de** döngü sayacı olarak kullanılmasını sağlar; böylece bir cmp talimatı tasarruf edilir. Ayrıca ofset birden fazla yük/store’da ve gerekirse ofsetin katlarında tekrar kullanılabilir (ödev için hatırlayın). + +**Alignment (Hizalama)** + +Örneklerimizin tümünde hizalama konusuna girmemek için movu kullandık. Birçok CPU, veri hizalıysa (yani bellek adresi SIMD yazmaç boyutuna bölünebiliyorsa) veriyi daha hızlı yükleyip saklayabilir. Mümkün olduğunda FFmpeg’de hizalı yük/store için mova kullanmaya çalışırız. + +FFmpeg’de av_malloc yığında hizalı bellek sağlayabilir; DECLARE_ALIGNED önişlemci makrosu ise yığında hizalı bellek yaratabilir. mova hizalı olmayan bir adreste kullanılırsa segmentation fault oluşur ve uygulama çöker. Ayrıca hizalama değerinin SIMD yazmaç boyutuyla uyuştuğundan emin olmak gerekir: xmm için 16, ymm için 32, zmm için 64. + +RODATA bölümünün başlangıcını 64 bayta hizalamak için: + +```assembly +SECTION_RODATA 64 +``` + +Bu yalnızca RODATA’nın başlangıcını hizalar; bir sonraki etiketin 64 bayt sınırında kalması için dolgu (padding) gerekebilir. + +**Range expansion (Aralık genişletme)** + +Şimdiye dek değinmediğimiz bir konu taşma (overflow). Örneğin bir baytın değeri toplama veya çarpma sonrası 255’i aşarsa olur. Bayttan büyük (ör. word) ara değerlerle çalışmamız gerekebilir ya da veriyi o daha geniş formda tutmak isteyebiliriz. + +İşaretsiz baytlar için punpcklbw (low baytları word’e aç) ve punpckhbw (high baytları word’e aç) devreye girer. + +punpcklbw’nin nasıl çalıştığına bakalım. Intel Kılavuzundaki SSE2 sözdizimi: + +| PUNPCKLBW xmm1, xmm2/m128 | +| :---- | + +Bu; kaynağın (sağ taraf) bir xmm yazmacı veya bellek adresi (m128 = standart [base + scale*index + disp]) olabileceği, hedefin ise bir xmm yazmacı olduğu anlamına gelir. + +Yukarıda link verilen officedaytime.com sitesinde süreci gösteren iyi bir diyagram var: + +![What is this](image1.png) + +Baytların her iki yazmacın alt yarılarından karşılıklı olarak iç içe (interleave) edildiğini görebilirsiniz. Peki bunun aralık genişletmeyle ilgisi ne? Eğer src yazmacı tamamen sıfır ise dst içindeki baytlar sıfırlarla örülür. Bu, baytlar işaretsiz olduğu için *zero extension* (sıfırla genişletme) olarak adlandırılır. Yüksek baytlar için aynı şeyi yapmak üzere punpckhbw kullanılabilir. + +Aşağıda bunun nasıl yapıldığını gösteren bir parça var: + +```assembly +pxor m2, m2 ; m2’yi sıfırla + +movu m0, [srcq] +movu m1, m0 ; m0’u m1’e kopyala +punpcklbw m0, m2 +punpckhbw m1, m2 +``` + +`m0` ve `m1` artık orijinal baytların word’e sıfırla genişletilmiş (zero-extended) hallerini içerir. Bir sonraki derste AVX’teki üç operantlı talimatların ikinci movu’yu nasıl gereksiz kıldığını göreceksiniz. + +**Sign extension (İşaretle genişletme)** + +İşaretli veri biraz daha karmaşıktır. İşaretli bir tamsayıyı daha geniş bir aralığa taşımak (range extend) için [işaretle genişletme](https://en.wikipedia.org/wiki/Sign_extension) ismi verilen işlemi kullanırız. Bu, en anlamlı bitleri (MSB) işaret bitiyle doldurur. Örneğin int8_t’de -2 = 0b11111110; int16_t’ye uzatıldığında 0b1111111111111110 olur. + +`pcmpgtb` (packed compare greater than byte) sign extension için kullanılabilir. Karşılaştırmayı (0 > byte) yaparak bayt negatifse hedef bayttaki tüm bitler 1, değilse 0 olur. Sonra yukarıdaki gibi punpckX ile interleave ederek sign extension elde ederiz. Bayt negatifse karşılık gelen bayt 0b11111111, değilse 0x00000000’dır; pcmpgtb çıktısı ile değerleri iç içe koymak word’e işaret uzatma sonucunu verir. + +```assembly +pxor m2, m2 ; m2’yi sıfırla + +movu m0, [srcq] +movu m1, m0 ; m0’u m1’e kopyala + +pcmpgtb m2, m0 +punpcklbw m0, m2 +punpckhbw m1, m2 +``` + +Gördüğünüz gibi işaretsiz duruma kıyasla ekstra bir komut var. + +**Packing (Paketleme)** + +packuswb (pack unsigned word to byte) ve packsswb word’den byte’a dönüş sağlar. Word içeren iki SIMD yazmacını bayt içeren tek bir SIMD yazmacında iç içe yerleştirir. Değerler byte aralığını aşarsa saturate (doyurularak) edilir (üst sınırda kenetlenir). + +**Shuffles (Karıştırmalar)** + +Karıştırmalar (permute işlemleri) video işleme için muhtemelen en önemli komut sınıfıdır ve SSSE3 ile gelen pshufb (packed shuffle bytes) en kritik varyanttır. + +Her çıktı baytı için karşılık gelen kaynak bayt, hedef yazmaçtaki indeks olarak kullanılır; eğer MSB set ise (0x80) çıktı baytı sıfırlanır. Aşağıdaki C koduna benzer (SIMD’de tüm 16 iterasyon paralel gerçekleşir): + +```c +for(int i = 0; i < 16; i++) { + if(src[i] & 0x80) + dst[i] = 0; + else + dst[i] = dst[src[i]] +} +``` +İşte basit bir assembly örneği: + +```assembly +SECTION_DATA 64 + +shuffle_mask: db 4, 3, 1, 2, -1, 2, 3, 7, 5, 4, 3, 8, 12, 13, 15, -1 + +section .text + +movu m0, [srcq] +movu m1, [shuffle_mask] +pshufb m0, m1 ; m0’u m1’e göre karıştır +``` + +Kolay okunması için -1, çıktıyı sıfırlayacak shuffle indeksi olarak kullanılır: bir bayt olarak -1 = 0b11111111 bit alanı (two’s complement) olduğundan MSB (0x80) ayarlanmış durumdadır. + +[image1]: