diff --git a/README.md b/README.md index 67c4007..357d53b 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB **Translations** * [Français](./README.fr.md) -* [Spanish](./README.es.md) \ No newline at end of file +* [Spanish](./README.es.md) +* [Turkish](./README.tr.md) \ No newline at end of file diff --git a/README.tr.md b/README.tr.md new file mode 100644 index 0000000..8930fc8 --- /dev/null +++ b/README.tr.md @@ -0,0 +1,19 @@ +FFmpeg Assembly Dili Okuluna hoş geldiniz. Programlamanın en ilginç, zorlu ve ödüllendirici yolculuğunda ilk adımı attınız. Bu dersler, assembly dilinin FFmpeg'de nasıl yazıldığı konusunda size temel bilgi verecek ve bilgisayarınızda gerçekte neler olup bittiğine gözlerinizi açacaktır. + +**Gerekli Bilgiler** + +* C bilgisi, özellikle pointer'lar. Eğer C bilmiyorsanız, [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) kitabını çalışın +* Lise Matematiği (skalar vs vektör, toplama, çarpma vb.) + +**Dersler** + +Bu Git deposunda her derse karşılık gelen dersler ve ödevler (henüz yüklenmedi) bulunmaktadır. Derslerin sonunda FFmpeg'e katkıda bulunabileceksiniz. + +Soruları yanıtlamak için bir discord sunucusu mevcuttur: +https://discord.com/invite/Ks5MhUhqfB + +**Çeviriler** + +* [Français](./README.fr.md) +* [Spanish](./README.es.md) +* [Turkish](./README.tr.md) diff --git a/lesson_01/index.tr.md b/lesson_01/index.tr.md new file mode 100644 index 0000000..c7eabdf --- /dev/null +++ b/lesson_01/index.tr.md @@ -0,0 +1,212 @@ +**FFmpeg Assembly Dili Birinci Ders** + +**Giriş** + +FFmpeg Assembly Dili Okuluna hoş geldiniz. Programlamanın en ilginç, zorlu ve ödüllendirici yolculuğunda ilk adımı attınız. Bu dersler, assembly dilinin FFmpeg'de nasıl yazıldığı konusunda size temel bilgi verecek ve bilgisayarınızda gerçekte neler olup bittiğine gözlerinizi açacaktır. + +**Gerekli Bilgiler** + +* C bilgisi, özellikle pointer'lar. Eğer C bilmiyorsanız, [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) kitabını çalışın +* Lise Matematiği (skalar vs vektör, toplama, çarpma vb.) + +**Assembly dili nedir?** + +Assembly dili, CPU'nun işlediği talimatlara 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 veriye *derlenir*. Assembly dili kodunun kısaca "assembly" veya "asm" olarak anıldığını görebilirsiniz. + +FFmpeg'deki assembly kodunun büyük çoğunluğu *SIMD, Single Instruction Multiple Data* (Tek Talimat Çoklu Veri) olarak bilinen türdedir. SIMD bazen vektör programlama olarak da anılır. Bu, belirli bir talimatın aynı anda birden fazla veri öğesi üzerinde çalıştığı anlamına gelir. Çoğu programlama dili bir seferde bir veri öğesi üzerinde çalışır, bu skalar programlama olarak bilinir. + +Tahmin edebileceğiniz gibi, SIMD kendisini bellekte sıralı olarak düzenlenmiş çok miktarda veriye sahip görüntü, video ve ses işlemeye iyi uyarlar. CPU'da sıralı veriyi işlememize yardımcı olacak özel talimatlar mevcuttur. + +FFmpeg'de "assembly fonksiyonu", "SIMD" ve "vektör(leştirme)" terimlerinin birbirinin yerine kullanıldığını göreceksiniz. Hepsi aynı şeyi ifade eder: Birden fazla veri öğesini tek seferde işlemek için elle assembly dilinde bir fonksiyon yazmak. Bazı projeler bunları "assembly kernel'leri" olarak da adlandırabilir. + +Tüm bunlar karmaşık gelebilir, ancak FFmpeg'de lise öğrencilerinin assembly kodu yazdığını hatırlamak önemlidir. Her şeyde olduğu gibi, öğrenme %50 jargon ve %50 gerçek öğrenmedir. + +**Neden assembly dilinde yazıyoruz?** +Multimedya işlemeyi hızlı hale getirmek için. Assembly kodu yazmaktan 10 kat veya daha fazla hız iyileştirmesi elde etmek çok yaygındır, bu özellikle videoları takılma olmadan gerçek zamanlı oynatmak istediğinizde önemlidir. Ayrıca enerji tasarrufu sağlar ve pil ömrünü uzatır. Video kodlama ve kod çözme fonksiyonlarının hem son kullanıcılar hem de büyük şirketlerin veri merkezlerinde dünyadaki en yoğun kullanılan fonksiyonlardan bazıları olduğunu belirtmek gerekir. Bu nedenle küçük bir iyileştirme bile hızla büyük sonuçlar doğurur. + +Çevrimiçi olarak, insanların daha hızlı geliştirme için assembly talimatlarına eşlenen C benzeri fonksiyonlar olan *intrinsic'ler* kullandığını sık sık görürsünüz. FFmpeg'de intrinsic kullanmayız, bunun yerine assembly kodunu elle yazarız. Bu tartışmalı bir alan olmakla birlikte, intrinsic'ler genellikle derleyiciye bağlı olarak elle yazılmış assembly'den yaklaşık %10-15 daha yavaştır (intrinsic destekçileri buna katılmaz). FFmpeg için her parça ekstra performans yardımcı olur, bu yüzden assembly kodunu doğrudan yazarız. Ayrıca intrinsic'lerin "[Hungarian Notation](https://en.wikipedia.org/wiki/Hungarian_notation)" kullanımları nedeniyle okunması zor olduğu argümanı da vardır. + +Ayrıca FFmpeg'de tarihsel nedenlerle veya Linux Kernel gibi projelerde çok özel kullanım durumları nedeniyle birkaç yerde *inline assembly* (yani intrinsic kullanmayan) kalıntıları görebilirsiniz. Bu, assembly kodunun ayrı bir dosyada değil, C kodu ile satır içinde yazıldığı durumdur. FFmpeg gibi projelerdeki genel kanı, bu kodun okunması zor, derleyiciler tarafından yaygın olarak desteklenmeyen ve bakımı yapılamayan kod olduğudur. + +Son olarak, çevrimiçi birçok sözde uzmanın bunların hiçbirinin gerekli olmadığını ve derleyicinin sizin için tüm bu "vektörleştirmeyi" yapabileceğini söylediğini göreceksiniz. En azından öğrenme amacıyla, onları görmezden gelin: örneğin [dav1d projesi](https://www.videolan.org/projects/dav1d.html)'ndeki son testler bu otomatik vektörleştirmeden yaklaşık 2 kat hızlanma gösterirken, elle yazılmış sürümler 8 kata ulaşabiliyordu. + +**Assembly dili çeşitleri** +Bu dersler x86 64-bit assembly diline odaklanacak. Bu amd64 olarak da bilinir, ancak Intel CPU'larında da çalışır. ARM ve RISC-V gibi diğer CPU'lar için başka assembly türleri vardır ve potansiyel olarak gelecekte bu dersler bunları da kapsayacak şekilde genişletilebilir. + +Çevrimiçi göreceğiniz x86 assembly sözdiziminin iki çeşidi vardır: AT&T ve Intel. AT&T Sözdizimi daha eskidir ve Intel sözdizimime kıyasla okunması daha zordur. Bu nedenle Intel sözdizimini kullanacağız. + +**Destekleyici materyaller** +Kitapların veya Stack Overflow gibi çevrimiçi kaynakların referans olarak özellikle yardımcı olmadığını duymak sizi şaşırtabilir. Bu kısmen Intel sözdizimi ile elle yazılmış assembly kullanma seçimimizden kaynaklanır. Ayrıca birçok çevrimiçi kaynak, genellikle SIMD olmayan kod kullanarak işletim sistemi programlama veya donanım programlamaya odaklanır. FFmpeg assembly'si özellikle yüksek performanslı görüntü işlemeye odaklanı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 durumlarını anlamak kolaydır. + +Birçok kitap assembly öğretmeden önce çok sayıda bilgisayar mimarisi detayına girer. Öğrenmek istediğiniz bu ise bu iyidir, ancak bizim bakış açımızdan, araba kullanmayı öğrenmeden önce motorları incelemek gibidir. + +Bununla birlikte, "The Art of 64-bit assembly" kitabının sonraki bölümlerinde SIMD talimatlarını ve davranışlarını görsel formda gösteren diyagramlar yardımcıdır: [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) + +**Register'lar** +Register'lar CPU'da verinin işlenebileceği alanlardır. CPU'lar doğrudan bellek üzerinde çalışmaz, bunun yerine veri register'lara yüklenir, işlenir ve belleğe geri yazılır. Assembly dilinde, genellikle, veriyi önce bir register'dan geçirmeden bir bellek konumundan diğerine doğrudan kopyalayamazsınız. + +**Genel Amaçlı Register'lar** +İlk register türü Genel Amaçlı Register (GPR) olarak bilinir. GPR'lar genel amaçlı olarak anılır çünkü bu durumda 64-bit'e kadar bir değer veya bir bellek adresi (bir pointer) içerebilirler. Bir GPR'daki değer toplama, çarpma, kaydırma vb. işlemlerle işlenebilir. + +Çoğu assembly kitabında, GPR'ların inceliklerine, tarihsel geçmişe vb. adanmış tüm bölümler vardır. Bunun nedeni GPR'ların işletim sistemi programlama, tersine mühendislik vb. söz konusu olduğunda önemli olmalarıdır. FFmpeg'de yazılan assembly kodunda, GPR'lar daha çok iskelet gibidir ve çoğu zaman karmaşıklıkları gerekli değildir ve soyutlanmıştır. + +**Vektör register'ları** +Vektör (SIMD) register'ları, adından da anlaşılacağı gibi, birden fazla veri öğesi içerir. Çeşitli vektör register türleri vardır: + +* mm register'ları - MMX register'ları, 64-bit boyutunda, tarihsel ve artık pek kullanılmıyor +* xmm register'ları - XMM register'ları, 128-bit boyutunda, yaygın olarak mevcut +* ymm register'ları - YMM register'ları, 256-bit boyutunda, bunları kullanırken bazı komplikasyonlar var +* zmm register'ları - ZMM register'ları, 512-bit boyutunda, sınırlı erişilebilirlik + +Video sıkıştırma ve açmadaki hesaplamaların çoğu integer tabanlıdır, bu nedenle buna sadık kalacağız. İşte bir xmm register'ında 16 byte örneği: + +| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +Ancak sekiz word (16-bit integer) olabilir + +| a | b | c | d | e | f | g | h | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +Veya dört double word (32-bit integer) + +| a | b | c | d | +| :---- | :---- | :---- | :---- | + +Veya iki quadword (64-bit integer): + +| a | b | +| :---- | :---- | + +Özetlemek gerekirse: + +* **b**yte'lar - 8-bit veri +* **w**ord'ler - 16-bit veri +* **d**oubleword'ler - 32-bit veri +* **q**uadword'ler - 64-bit veri +* **d**ouble **q**uadword'ler - 128-bit veri + +Kalın harfler daha sonra önemli olacak. + +**x86inc.asm include** +Birçok örnekte x86inc.asm dosyasını dahil ettiğimizi göreceksiniz. X86inc.asm, FFmpeg, x264 ve dav1d'de bir assembly programcısının hayatını kolaylaştırmak için kullanılan hafif bir soyutlama katmanıdır. Birçok şekilde yardımcı olur, ancak başlangıç için faydalı yaptığı şeylerden biri GPR'ları r0, r1, r2 olarak etiketlemesidir. Bu, herhangi bir register adını hatırlamanız gerekmediği anlamına gelir. Daha önce bahsedildiği gibi, GPR'lar genellikle sadece iskelet olduğu için bu hayatı çok kolaylaştırır. + +**Basit bir skalar asm parçacığı** + +Neler olup bittiğini görmek için basit (ve oldukça yapay) bir skalar asm parçacığına (her talimat içinde aynı anda bireysel veri öğeleri üzerinde çalışan assembly kodu) bakalım: + +```assembly +mov r0q, 3 +inc r0q +dec r0q +imul r0q, 5 +``` + +İlk satırda, *immediate değer* 3 (bellekten getirilen bir değerin aksine doğrudan assembly kodunun kendisinde saklanan bir değer) r0 register'ına quadword olarak saklanıyor. Intel sözdiziminde, kaynak operand (veriyi sağlayan değer veya konum, sağda yer alır) hedef operand'a (veriyi alan konum, solda yer alır) aktarılır, tıpkı memcpy davranışı gibi. Bunu "r0q = 3" olarak da okuyabilirsiniz, çünkü sıra aynıdır. r0'ın "q" eki register'ın quadword olarak kullanıldığını belirtir. inc değeri artırır böylece r0q 4 içerir, dec değeri 3'e geri azaltır. imul değeri 5 ile çarpar. Sonunda, r0q 15 içerir. + +mov ve inc gibi makine koduna derleyici tarafından derlenen insan tarafından okunabilir talimatların *mnemonik* olarak bilindiğini unutmayın. Çevrimiçi ve kitaplarda MOV ve INC gibi büyük harflerle temsil edilen mnemonikleri görebilirsiniz ancak bunlar küçük harf sürümleri ile aynıdır. FFmpeg'de küçük harf mnemonikleri kullanırız ve büyük harfleri makrolar için saklı tutarız. + +**Temel bir vektör fonksiyonunu anlama** + +İş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 gözden geçirelim: + +```assembly +%include "x86inc.asm" +``` + +Bu, assembly yazmayı basitleştirmek için yardımcılar, önceden tanımlanmış isimler ve makrolar (aşağıdaki cglobal gibi) sağlamak amacıyla x264, FFmpeg ve dav1d topluluklarında geliştirilen bir "header"dır. + +```assembly +SECTION .text +``` + +Bu, çalıştırmak istediğiniz kodun yerleştirildiği bölümü belirtir. Bu, sabit veri koyabileceğiniz .data bölümünün aksine. + +```assembly +;static void add_values(uint8_t *src, const uint8_t *src2) +INIT_XMM sse2 +``` + +İlk satır fonksiyon argümanının C'de nasıl göründüğünü gösteren bir yorumdur (assembly'de noktalı virgül ";" C'deki "//" gibidir). İkinci satır, paddb bir sse2 talimatı olduğu için sse2 talimat setini kullanarak XMM register'larını nasıl başlattığımızı gösterir. sse2'yi bir sonraki derste daha detaylı ele alacağız. + +```assembly +cglobal add_values, 2, 2, 2, src, src2 +``` + +Bu, "add_values" adlı bir C fonksiyonu tanımladığı için önemli bir satırdır. + +Her öğeyi tek tek gözden geçirelim: + +* Sonraki parametre iki fonksiyon argümanına sahip 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 söylemek zorundayız. +* Bundan sonraki parametre x86util'e kaç XMM register'ı kullanacağımızı söyler. +* Takip eden iki parametre fonksiyon argümanları için etiketlerdir. + +Eski kodların fonksiyon argümanları için etiket bulundurmayabileceğini, bunun yerine r0, r1 vb. kullanarak GPR'lara doğrudan hitap edebileceğini belirtmek gerekir. + +```assembly + movu m0, [srcq] + movu m1, [src2q] +``` + +movu, movdqu'nun (move double quad unaligned - hizalanmamış çift quad taşıma) kısaltmasıdır. Hizalama başka bir derste ele alınacak ancak şimdilik movu, [srcq]'den 128-bit taşıma olarak değerlendirilebilir. mov durumunda, köşeli parantezler [srcq]'deki adresin referans alındığı anlamına gelir, C'de *src'nin eşdeğeridir. Bu *yükleme* olarak bilinir. "q" ekinin pointer'ın boyutunu ifade ettiğini (yani C'de sizeof(*src) == 64-bit sistemlerde 8 ve x86asm 32-bit sistemlerde 32-bit kullanacak kadar akıllı) ancak temel yüklemenin 128-bit olduğunu unutmayın. + +Vektör register'larını bu durumda xmm0 olan tam adlarıyla değil, soyutlanmış bir form olan m0 olarak andığımızı unutmayın. Gelecek derslerde bunun kod yazmayı bir kez yapıp birden fazla SIMD register 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 register'daki her byte'ı topluyor. "p" ön eki "packed" (paketlenmiş) anlamına gelir ve vektör talimatları ile skalar talimatları tanımlamak için kullanılır. "b" eki bunun byte'lık toplama (byte 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 *saklama* olarak bilinir. Veri srcq pointer'ındaki adrese geri yazılır. + +```assembly +RET +``` + +Bu, fonksiyonun döndüğünü belirten bir makrodur. FFmpeg'deki assembly fonksiyonlarının neredeyse tamamı bir değer döndürmek yerine argümanlardaki veriyi değiştirir. + +Ödevde göreceğiniz gibi, assembly fonksiyonlarına fonksiyon pointer'ları oluşturuyoruz ve mevcut olduklarında bunları kullanıyoruz. + +[Sonraki Ders](../lesson_02/index.md) diff --git a/lesson_02/index.tr.md b/lesson_02/index.tr.md new file mode 100644 index 0000000..3f54b10 --- /dev/null +++ b/lesson_02/index.tr.md @@ -0,0 +1,168 @@ +**FFmpeg Assembly Dili İkinci Ders** + +Artık ilk assembly dili fonksiyonunuzu yazdığınıza göre, şimdi dallanma ve döngüleri tanıtacağız. + +Önce etiket ve atlama fikrini tanıtmamız gerekiyor. Aşağıdaki yapay örnekte, jmp talimatı kod talimatını ".loop:" dan sonrasına taşır. ".loop:" *etiket* olarak bilinir, etiketin önündeki nokta bunun *yerel etiket* olduğu anlamına gelir, bu da aynı etiket adını birden fazla fonksiyon boyunca yeniden kullanmanıza olanak tanır. Bu örnek tabii ki sonsuz bir döngü gösteriyor, 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ü yapmadan önce *FLAGS* register'ını tanıtmamız gerekiyor. *FLAGS*'ın inceliklerine çok fazla dalacağımız yok (yine GPR işlemleri büyük ölçüde iskelet olduğu için) ancak Sıfır-Bayrağı, İşaret-Bayrağı ve Taşma-Bayrağı gibi birkaç bayrak vardır ve bunlar skalar veri üzerinde aritmetik işlemler ve kaydırmalar gibi mov olmayan talimatların çıktısına göre ayarlanır. + +İşte döngü sayacının sıfıra kadar geriye saydığı ve jg'nin (sıfırdan büyükse atla) döngü koşulu olduğu bir örnek. dec r0q talimat sonrasında r0q değerine göre FLAGS'ı ayarlar ve bunlara göre atlayabilirsiniz. + +```assembly +mov r0q, 3 +.loop: + ; bir şey yap + dec r0q + jg .loop ; sıfırdan büyükse atla +``` + +Bu aşağıdaki C koduna eşdeğerdir: + +```c +int i = 3; +do +{ + // bir şey yap + i--; +} while(i > 0) +``` + +Bu C kodu biraz doğal değil. Genellikle C'de bir döngü şöyle yazılır: + +```c +int i; +for(i = 0; i < 3; i++) { + // bir şey yap +} +``` + +Bu kabaca şuna eşdeğerdir (bu ```for``` döngüsünü eşleştirmenin basit bir yolu yoktur): + +```assembly +xor r0q, r0q +.loop: + ; bir şey yap + inc r0q + cmp r0q, 3 + jl .loop ; (r0q - 3) < 0 ise atla, yani (r0q < 3) +``` + +Bu parçacıkta işaret edilecek birkaç şey vardır. İlki ```xor r0q, r0q``` olup, bu bir register'ı sıfıra ayarlamanın yaygın bir yoludur ve bazı sistemlerde ```mov r0q, 0```'dan daha hızlıdır, çünkü basitçe söylemek gerekirse gerçek bir yükleme gerçekleşmez. ```pxor m0, m0``` ile SIMD register'larında tüm register'ı sıfırlamak için de kullanılabilir. Dikkat edilecek bir diğer şey ise cmp kullanımıdır. cmp etkili olarak ikinci register'ı birinciden çıkarır (değeri herhangi bir yerde saklamadan) ve *FLAGS*'ı ayarlar, ancak yorumda da belirtildiği gibi, ```r0q < 3``` ise atlamak için atlama ile birlikte okunabilir (jl = sıfırdan küçükse atla). + +Bu parçacıkta bir ekstra talimat (cmp) olduğuna dikkat edin. Genel olarak, daha az talimat daha hızlı kod anlamına gelir, bu yüzden önceki parçacık tercih edilir. Gelecek derslerde göreceğiniz gibi, bu ekstra talimatı önlemek ve *FLAGS*'ın aritmetik veya başka bir işlemle ayarlanmasını sağlamak için daha fazla numara kullanılır. Assembly'de C döngülerini tam olarak eşleştirmek için assembly yazmadığımıza, assembly'de mümkün olduğunca hızlı hale getirmek için döngüler yazdığımıza dikkat edin. + +İşte kullanacağınız bazı yaygın atlama mnemonikleri (*FLAGS* eksiksizlik için oradadır, ancak döngü yazmak için ayrıntıları bilmenize gerek yoktur): + +| Mnemonic | Açıklama | FLAGS | +| :---- | :---- | :---- | +| JE/JZ | Eşitse/Sıfırsa Atla | ZF = 1 | +| JNE/JNZ | Eşit Değilse/Sıfır Değilse Atla | ZF = 0 | +| JG/JNLE | Büyükse/Küçük veya Eşit Değilse Atla (işaretli) | ZF = 0 and SF = OF | +| JGE/JNL | Büyük veya Eşitse/Küçük Değilse Atla (işaretli) | SF = OF | +| JL/JNGE | Küçükse/Büyük veya Eşit Değilse Atla (işaretli) | SF ≠ OF | +| JLE/JNG | Küçük veya Eşitse/Büyük Değilse Atla (işaretli) | ZF = 1 or SF ≠ OF | + +**Sabitler** + +Sabitlerin nasıl kullanılacağını gösteren 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 veri bölümü olduğunu belirtir. (Bu bir makrodur çünkü işletim sistemlerinin kullandığı farklı çıktı dosya formatları bunu farklı şekilde beyan eder) +* constants_1: constants_1 etiketi ```db``` (declare byte - byte beyan et) olarak tanımlanır - yani uint8_t constants_1[4] = {1, 2, 3, 4}; eşdeğeri +* constants_2: Bu, beyan edilen word'leri tekrarlamak için ```times 2``` makrosunu kullanır - yani uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1}; eşdeğeri + +Derleyicinin bir bellek adresine dönüştürdüğü bu etiketler daha sonra yüklemelerde kullanılabilir (salt okunur oldukları için saklamalarda değil). Bazı talimatlar operand olarak bir bellek adresi alır, bu nedenle register'a açık yükleme olmadan kullanılabilirler (bunun artıları ve eksileri vardır). + +**Offset'ler** + +Offset'ler bellekteki ardışık öğeler arasındaki mesafedir (byte olarak). Offset, veri yapısındaki **her öğenin boyutu** ile belirlenir. + +Artık döngü yazabildiğimize göre, veri almaya zaman. Ancak C ile karşılaştırıldığında bazı farklar var. Şu C döngüsüne bakalım: + +```c +uint32_t data[3]; +int i; +for(i = 0; i < 3; i++) { + data[i]; +} +``` + +Veri öğeleri arasındaki 4-byte offset C derleyicisi tarafından önceden hesaplanır. Ancak assembly'yi elle yazarken bu offset'leri kendiniz hesaplamanız gerekir. + +Bellek adresi hesaplamaları için sözdizimini inceleyelim. Bu her türlü bellek adresi için geçerlidir: + +```assembly +[base + scale*index + disp] +``` + +* base - Bu bir GPR'dır (genellikle bir C fonksiyon argümanından gelen pointer) +* scale - Bu 1, 2, 4, 8 olabilir. 1 varsayılandır +* index - Bu bir GPR'dır (genellikle bir döngü sayacı) +* disp - Bu bir integer'dır (32-bit'e kadar). Displacement veri içine bir offset'tir + +x86asm, çalıştığınız SIMD register'ının boyutunu bilmenizi sağlayan mmsize sabitini sağlar. + +Özel offset'lerden yüklemeyi göstermek için basit (ve anlamsız) bir örnek: + +```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] + + ; bazı şeyler yap + + add srcq, mmsize +dec r1q +jg .loop + +RET +``` + +```movu m1, [srcq+2*r1q+3+mmsize]```'de derleyicinin kullanılacak doğru displacement sabitini önceden hesaplayacağına dikkat edin. Bir sonraki derste döngüde add ve dec yapmaktan kaçınma numarasını göstereceğiz, bunları tek bir add ile değiştireceğiz. + +**LEA** + +Artık offset'leri anladığınıza göre lea (Load Effective Address) kullanabilirsiniz. Bu, çarpma ve toplamayı tek talimat ile yapmanıza olanak tanır, bu da birden fazla talimat kullanmaktan daha hızlı olacaktır. Tabii ki çarpabileceğiniz ve toplayabileceğiniz şeylerde sınırlamalar vardır ancak bu lea'nın güçlü bir talimat olmasını engellemez. + +```assembly +lea r0q, [base + scale*index + disp] +``` + +Adına rağmen, LEA normal aritmetik için ve adres hesaplamaları için kullanılabilir. Şu kadar karmaşık bir şey yapabilirsiniz: + +```assembly +lea r0q, [r1q + 8*r2q + 5] +``` + +Bunun r1q ve r2q içeriklerini etkilemediğine dikkat edin. Ayrıca *FLAGS*'ı da etkilemez (bu nedenle çıktıya göre atlayamazsınız). LEA kullanmak tüm bu talimatları ve geçici register'ları ö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 +``` + +lea'nın döngülerden önce adresler ayarlamak veya yukarıdaki gibi hesaplamalar yapmak için çok kullanıldığını göreceksiniz. Tabii ki her türlü çarpma ve toplama yapamazsınız, ancak 1, 2, 4, 8 ile çarpmalar ve sabit bir offset toplamı yaygındır. + +Ödevde bir sabit yüklemeniz ve değerleri bir döngüde bir SIMD vektörüne eklemeniz gerekecek. + +[Sonraki Ders](../lesson_03/index.md) diff --git a/lesson_03/index.tr.md b/lesson_03/index.tr.md new file mode 100644 index 0000000..a35688b --- /dev/null +++ b/lesson_03/index.tr.md @@ -0,0 +1,200 @@ +**FFmpeg Assembly Üçüncü Ders** + +Biraz daha jargon açıklayalım ve size kısa bir tarih dersi verelim. + +**Talimat Setleri** + +Önceki derste SSE2'den bahsettiğimizi, bunun bir SIMD talimat seti olduğunu görmüş olabilirsiniz. Yeni bir CPU nesli piyasaya çıktığında yeni talimatlar ve bazen daha büyük register boyutları ile gelebilir. x86 talimat setinin tarihçesi çok karmaşıktır, bu nedenle bu basitleştirilmiş bir tarihçedir (çok daha fazla alt kategori vardır): + +* MMX - 1997'de piyasaya çıktı, Intel İşlemcilerinde ilk SIMD, 64-bit register'lar, tarihsel +* SSE (Streaming SIMD Extensions) - 1999'da piyasaya çıktı, 128-bit register'lar +* SSE2 - 2000'de piyasaya çıktı, birçok yeni talimat +* SSE3 - 2004'te piyasaya çıktı, ilk yatay talimatlar +* SSSE3 (Supplemental SSE3) - 2006'da piyasaya çıktı, yeni talimatlar ancak en önemlisi pshufb shuffle talimatı, tartışmasız video işlemede en önemli talimat +* SSE4 - 2008'de piyasaya çıktı, paketlenmiş minimum ve maksimum dahil birçok yeni talimat. +* AVX - 2011'de piyasaya çıktı, 256-bit register'lar (sadece float) ve yeni üç-operand sözdizimi +* AVX2 - 2013'te piyasaya çıktı, integer talimatları için 256-bit register'lar +* AVX512 - 2017'de piyasaya çıktı, 512-bit register'lar, yeni işlem maskesi özelliği. Yeni talimatlar kullanıldığında CPU frekans düşürme nedeniyle o zamanlar FFmpeg'de sınırlı kullanımları vardı. vpermb ile tam 512-bit shuffle (permute). +* AVX512ICL - 2019'da piyasaya çıktı, artık clock frekans düşürme yok. +* AVX10 - Yakında + +Talimat setlerinin CPU'lara eklenmesinin yanı sıra kaldırılabileceğini de belirtmek gerekir. Örneğin AVX512, 12. Nesil Intel CPU'larında [tartışmalı şekilde kaldırılmıştır](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/). Bu nedenle FFmpeg çalışma zamanı CPU algılaması yapar. FFmpeg üzerinde çalıştığı CPU'nun yeteneklerini algılar. + +Ödevde gördüğünüz gibi, fonksiyon pointer'ları varsayılan olarak C'dir ve belirli bir talimat seti varyantı ile değiştirilir. Bu, algılamanın bir kez yapıldığı ve bir daha asla yapılması gerekmediği anlamına gelir. Bu, belirli bir talimat setini hardcode eden ve mükemmel çalışan bir bilgisayarı eskiten birçok tescilli uygulamanın aksine. Bu ayrıca optimize edilmiş fonksiyonların çalışma zamanında açılıp/kapatılmasına olanak tanır. Bu, açık kaynak kodun büyük faydalarından biridir. + +FFmpeg gibi programlar dünya çapında milyarlarca cihazda kullanılır, bunların bazıları çok eski olabilir. FFmpeg teknik olarak 25 yaşındaki sadece SSE destekleyen makineleri destekler! Neyse ki x86inc.asm belirli bir talimat setinde mevcut olmayan bir talimat kullanıp kullanmadığınızı söyleyebilir. + +Gerçek dünya yetenekleri hakkında bir fikir vermek için, Kasım 2024 itibariyle [Steam Anketi](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam)'nden talimat seti kullanılabilirliği (bu açıkça oyunculara yönelik önyargılıdır): + +| Talimat Seti | Kullanılabilirlik | +| :---- | :---- | +| SSE2 | %100 | +| SSE3 | %100 | +| SSSE3 | %99.86 | +| SSE4.1 | %99.80 | +| AVX | %97.39 | +| AVX2 | %94.44 | +| AVX512 (Steam AVX512 ve AVX512ICL arasında ayrım yapmıyor) | %14.09 | + +Milyarlarca kullanıcısı olan FFmpeg gibi bir uygulama için, %0.1 bile çok büyük bir kullanıcı sayısıdır ve bir şey bozulursa hata raporları gelir. FFmpeg, [FATE test paketimizde](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F) CPU/OS/Derleyici varyasyonlarını test etmek için kapsamlı test altyapısına sahiptir. Her commit hiçbir şeyin bozulmadığından emin olmak için yüzlerce makinede çalıştırılır. + +Intel detaylı talimat seti kılavuzunu burada sağlar: [https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) + +PDF'te arama yapmak zahmetli olabilir, bu nedenle burada resmi olmayan web tabanlı bir alternatif var: [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/) + +SIMD talimatlarının görsel temsili de burada mevcuttur: +[https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/) + +x86 assembly'nin zorluklarından biri ihtiyaçlarınız için doğru talimatı bulmaktır. Bazı durumlarda talimatlar orijinal amaçlarından farklı şekilde kullanılabilir. + +**Pointer offset numarası** + +1. Dersten orijinal fonksiyonumuza geri dönelim, ancak C fonksiyonuna bir width argümanı ekleyelim. + +Width değişkeni için int yerine ptrdiff_t kullanıyoruz, 64-bit argümanın üst 32-bitinin sıfır olduğundan emin olmak için. Eğer fonksiyon imzasında doğrudan bir int width geçirsek ve sonra bunu pointer aritmetiği için quad olarak kullanmaya çalışsak (`widthq` kullanarak) register'ın üst 32-biti rastgele değerlerle doldurulabilir. Bunu `movsxd` ile width'i işaret genişleterek düzeltebilirdik (x86inc.asm'daki `movsxdifnidn` makrosuna da bakın), ancak bu daha kolay bir yol. + +Aşağıdaki fonksiyon pointer offset numarasını içerir: + +```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 +``` + +Kafa karıştırabileceği için bunu adım adım gözden geçirelim: + +```assembly + add srcq, widthq + add src2q, widthq + neg widthq +``` + +Width her pointer'a eklenir böylece her pointer artık işlenecek buffer'ın sonunu işaret eder. Width daha sonra negatif yapılır. + +```assembly + movu m0, [srcq+widthq] + movu m1, [src2q+widthq] +``` + +Yüklemeler daha sonra widthq negatif olacak şekilde yapılır. Bu nedenle ilk iterasyonda [srcq+widthq] srcq'nun orijinal adresini işaret eder, yani buffer'ın başlangıcına geri işaret eder. + +```assembly + add widthq, mmsize + jl .loop +``` + +mmsize negatif widthq'ya eklenir ve onu sıfıra yaklaştırır. Döngü koşulu artık jl'dir (sıfırdan küçükse atla). Bu numara widthq'nun aynı anda pointer offset **ve** döngü sayacı olarak kullanılması anlamına gelir, cmp talimatından tasarruf sağlar. Ayrıca pointer offset'inin birden fazla yüklemede ve saklamada kullanılmasına ve gerektiğinde pointer offset'lerinin katlarının kullanılmasına olanak tanır (ödev için bunu hatırlayın). + +**Hizalama** + +Tüm örneklerde hizalama konusunu önlemek için movu kullanıyorduk. Birçok CPU, veri hizalanmışsa, yani bellek adresi SIMD register boyutuna bölünebiliyorsa veriyi daha hızlı yükleyip saklayabilir. Mümkün olduğunda FFmpeg'de mova kullanarak hizalanmış yükleme ve saklamaları kullanmaya çalışırız. + +FFmpeg'de av_malloc heap'te hizalanmış bellek sağlayabilir ve DECLARE_ALIGNED C ön işlemci direktifi stack'te hizalanmış bellek sağlayabilir. mova hizalanmamış bir adresle kullanılırsa, segmentasyon hatası oluşturacak ve uygulama çökecektir. Ayrıca hizalama değerinin SIMD register boyutuna karşılık geldiğinden emin olmak önemlidir, yani xmm ile 16, ymm için 32 ve zmm için 64. + +RODATA bölümünün başlangıcını 64-byte'a hizalamak için: + +```assembly +SECTION_RODATA 64 +``` + +Bunun sadece RODATA'nın başlangıcını hizaladığını unutmayın. Bir sonraki etiketin 64-byte sınırında kalmasını sağlamak için dolgu byte'ları gerekebilir. + +**Aralık genişletmesi** + +Şimdiye kadar kaçındığımız bir diğer konu da taşmadır. Bu, örneğin toplama veya çarpma gibi bir işlemden sonra bir byte'ın değeri 255'i geçtiğinde olur. Ara değerin bir byte'dan (örn. word'ler) daha büyük olmasına ihtiyaç duyabiliriz veya potansiyel olarak veriyi o daha büyük ara boyutta bırakmak isteyebiliriz. + +İşaretsiz byte'lar için punpcklbw (packed unpack low bytes to words - paketlenmiş düşük byte'ları word'lere açma) ve punpckhbw (packed unpack high bytes to words - paketlenmiş yüksek byte'ları word'lere açma) devreye girer. + +punpcklbw'nin nasıl çalıştığına bakalım. Intel Kılavuzundan SSE2 sürümü için sözdizimi şöyledir: + +| PUNPCKLBW xmm1, xmm2/m128 | +| :---- | + +Bu, kaynağının (sağ taraf) bir xmm register'ı veya bellek adresi (m128, standart [base + scale*index + disp] sözdizimi ile bir bellek adresi anlamına gelir) ve hedefin bir xmm register'ı olabileceği anlamına gelir. + +Yukarıdaki officedaytime.com web sitesi neler olup bittiğini gösteren iyi bir diyagrama sahiptir: + +![Bu nedir](image1.png) + +Byte'ların her register'ın alt yarısından sırasıyla interleave edildiğini görebilirsiniz. Peki bunun aralık genişletmesi ile ne ilgisi var? Eğer src register'ı tamamen sıfırsa, bu dst'deki byte'ları sıfırlarla interleave eder. Bu, byte'lar işaretsiz olduğu için *sıfır genişletmesi* olarak bilinir. punpckhbw yüksek byte'lar için aynı şeyi yapmak için kullanılabilir. + +Bunun nasıl yapıldığını gösteren bir parçacık: + +```assembly +pxor m2, m2 ; m2'yi sıfırla + +movu m0, [srcq] +movu m1, m0 ; m0'ın bir kopyasını m1'de yap +punpcklbw m0, m2 +punpckhbw m1, m2 +``` + +```m0``` ve ```m1``` artık orijinal byte'ları word'lere sıfır genişletilmiş olarak içerir. Bir sonraki derste AVX'deki üç-operand talimatlarının ikinci movu'yu nasıl gereksiz kıldığını göreceksiniz. + +**İşaret genişletmesi** + +İşaretli veri biraz daha karmaşık. İşaretli bir integer'ı aralık genişletmek için [işaret genişletmesi](https://en.wikipedia.org/wiki/Sign_extension) olarak bilinen bir işlem kullanmamız gerekir. Bu MSB'leri işaret biti ile doldurur. Örneğin: int8_t'de -2, 0b11111110'dur. int16_t'ye işaret genişletmek için 1'in MSB'si 0b1111111111111110 yapmak için tekrarlanır. + +```pcmpgtb``` (packed compare greater than byte - paketlenmiş byte'dan büyük karşılaştırma) işaret genişletmesi için kullanılabilir. (0 > byte) karşılaştırmasını yaparak, eğer byte negatifse hedef byte'daki tüm bitler 1'e ayarlanır, aksi takdirde hedef byte'daki bitler 0'a ayarlanır. punpckX yukarıdaki gibi işaret genişletmesi gerçekleştirmek için kullanılabilir. Eğer byte negatifse karşılık gelen byte 0b11111111'dir ve aksi takdirde 0x00000000'dır. Byte değerini pcmpgtb'nin çıktısı ile interleave etmek sonuç olarak word'e işaret genişletmesi gerçekleştirir. + +```assembly +pxor m2, m2 ; m2'yi sıfırla + +movu m0, [srcq] +movu m1, m0 ; m0'ın bir kopyasını m1'de yap + +pcmpgtb m2, m0 +punpcklbw m0, m2 +punpckhbw m1, m2 +``` + +Gördüğünüz gibi işaretsiz duruma kıyasla bir ekstra talimat var. + +**Paketleme** + +packuswb (pack unsigned word to byte - işaretsiz word'ü byte'a paketleme) ve packsswb word'den byte'a gitmenizi sağlar. Word içeren iki SIMD register'ını byte içeren bir SIMD register'ında interleave etmenizi sağlar. Değerler byte aralığını aşarsa, doyurulacaklarını (yani en büyük değerde kısıtlanacaklarını) unutmayın. + +**Shuffle'lar** + +Shuffle'lar, permute olarak da bilinirler, tartışmasız video işlemede en önemli talimattır ve SSSE3'te mevcut olan pshufb (packed shuffle bytes - paketlenmiş byte shuffle), en önemli varyantıdır. + +Her byte için karşılık gelen kaynak byte hedef register'ının bir indeksi olarak kullanılır, MSB ayarlandığında hariç hedef byte sıfırlanır. Aşağıdaki C koduna benzerdir (SIMD'de tüm 16 döngü iterasyonu paralel olarak 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 ; m1'e göre m0'ı shuffle et +``` + +Kolay okunabilirlik için çıktı byte'ını sıfırlamak için shuffle indeksi olarak -1 kullanıldığını unutmayın: byte olarak -1, 0b11111111 bit alanıdır (iki'nin tümleyeni), ve bu nedenle MSB (0x80) ayarlanmıştır.