Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB
**Traducciones**

* [English](./README.md)
* [Français](./README.fr.md)
* [Français](./README.fr.md)
* [Korean](./README.ko.md)
3 changes: 2 additions & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB
**Traductions**

* [English](./README.md)
* [Spanish](./README.es.md)
* [Spanish](./README.es.md)
* [Korean](./README.ko.md)
19 changes: 19 additions & 0 deletions README.ko.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FFempeg 어셈블리 언어 강좌에 오신 것을 환영합니다. 여러분은 프로그래밍에서 가장 흥미롭고, 도전적이며, 보람찬 여정의 첫 걸음을 내디뎠습니다. 이 강의들은 FFmpeg에서 어셈블리 언어가 작성되는 방식에 대한 기초를 다지게 해주며, 컴퓨터 내부에서 실제로 어떤 일이 일어나는지에 대한 시야를 넓혀 줄 것입니다.

**필수 지식**

* C 언어 지식, 특히 포인터에 대한 이해. C를 모른다면, [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) 책을 충분히 학습(공부)하십시오.
* 고등학교 수준의 수학 지식 (스칼라와 벡터의 차이, 덧셈, 곱셈 등)

**강의**

이 Git 저장소에는 각 강의에 해당하는 강의 자료와 과제(미업로드)가 포함되어 있습니다. 모든 강의를 마치면 FFmpeg에 직접 기여할 수 있게 될 것입니다.

질문이 있을 경우 아래의 디스코드 서버에서 도움을 받을 수 있습니다.
https://discord.com/invite/Ks5MhUhqfB

**번역**

* [English](./README.md)
* [Français](./README.fr.md)
* [Spanish](./README.es.md)
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB
**Translations**

* [Français](./README.fr.md)
* [Spanish](./README.es.md)
* [Spanish](./README.es.md)
* [Korean](./README.ko.md)
218 changes: 218 additions & 0 deletions lesson_01/index.ko.md

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions lesson_02/index.ko.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
**FFmpeg 어셈블리 언어 2강**

이제 첫 번째 어셈블리 함수를 작성했으니, 이번에는 분기(Branch)와 루프(Loop)를 소개하겠습니다.

먼저 레이블(label)과 점프(jump)의 개념을 알아야 합니다. 아래의 인위적인 예시에서 jmp 명령은 코드 실행을 ".loop:" 이후로 이동시킵니다. ".loop:"은 *레이블*로 불리며, 이름 앞의 점(.)은 이것이 *로컬 레이블(local label)* 임을 나타냅니다. 이는 여러 함수에서 동일한 레이블 이름을 재사용할 수 있게 해줍니다. 물론 아래의 예시는 무한 루프이지만, 이후에 더 현실적인 예시로 확장하겠습니다.

```assembly
mov r0q, 3
.loop:
dec r0q
jmp .loop
```

현실적인 루프를 만들기 전에 *FLAGS* 레지스터를 소개해야 합니다. *FLAGS*의 세부 동작에 너무 깊게 들어가지는 않겠지만(GPR 연산은 대부분 보조적이기 때문입니다), 산술 연산이나 시프트 같은 스칼라 연산의 결과에 따라 설정되는 여러 플래그(Zero-Flag, Sign-Flag, Overflow-Flag 등)가 존재합니다.

다음은 루프 카운터가 0이 될 때까지 감소하는 예시입니다. jg(jump if greater than zero) 명령은 루프 조건으로 사용됩니다. dec r0q 명령은 실행 후 r0q의 값에 따라 FLAGS를 설정하며, 이 플래그를 기반으로 분기할 수 있습니다.

```assembly
mov r0q, 3
.loop:
; do something
dec r0q
jg .loop ; 0보다 크면 점프
```

이는 다음 C 코드와 동일한 의미를 가집니다:

```c
int i = 3;
do
{
// do something
i--;
} while(i > 0);
```

이 C 코드는 약간 부자연스러우며, 일반적으로 C에서는 루프를 다음과 같이 작성합니다.

```c
int i;
for(i = 0; i < 3; i++) {
// do something
}
```

이 C 코드는 완전히 동일하게 표현하기는 어렵지만, 대략 다음 어셈블리와 비슷합니다.

```assembly
xor r0q, r0q
.loop:
; do something
inc r0q
cmp r0q, 3
jl .loop ; (r0q - 3) < 0, 즉 (r0q < 3)일 때 점프
```

여기서 주목할 부분이 몇 가지 있습니다. 먼저 xor r0q, r0q는 레지스터 값을 0으로 설정하는일반적인 방법으로, 일부 시스템에서는 mov r0q, 0보다 빠릅니다. 이는 단순히 실제 로드(load) 연산이 일어나지 않기 때문입니다. 또한 SIMD 레지스터에서도 pxor m0, m0를 사용해 전체 레지스터를 0으로 초기화할 수 있습니다.

이 코드 조각에는 cmp라는 추가 명령어가 한 줄 더 있습니다. 일반적으로 명령어 수가 적을수록 코드가 더 빠르기 때문에, 이전 루프 형태가 더 선호됩니다. 이후 강의에서는 이런 추가 명령을 피하고 산술 연산이나 다른 연산을 통해 *FLAGS*를 직접 설정하는 여러 방법을 배우게 됩니다. 우리는 C 루프와 정확히 동일한 구조를 맞추기보다는, 어셈블리에서 가능한 한 빠른 루프를 작성하는 것을 목표로 합니다.

다음은 자주 사용되는 점프 니모닉(mnemonic)들입니다. (*FLAGS* 항목은 참고용이며, 루프를 작성하기 위해 세부 내용을 알 필요는 없습니다.)

| Mnemonic | 설명 | FLAGS |
| :---- | :---- | :---- |
| JE/JZ | 같을 때 / 0일 때 점프 | ZF = 1 |
| JNE/JNZ | 같지 않을 때 / 0이 아닐 때 점프 | ZF = 0 |
| JG/JNLE | 크거나 / 작지 않거나 같을 때 점프 (부호 있음) | ZF = 0 and SF = OF |
| JGE/JNL | 크거나 같을 때 / 작지 않을 때 점프 (부호 있음) | SF = OF |
| JL/JNGE | 작을 때 / 크거나 같지 않을 때 점프 (부호 있음) | SF ≠ OF |
| JLE/JNG | 작거나 같을 때 / 크지 않을 때 점프 (부호 있음) | ZF = 1 or SF ≠ OF |

**상수**

상수를 사용하는 방법을 보여주는 예시를 살펴보겠습니다.

```assembly
SECTION_RODATA

constants_1: db 1,2,3,4
constants_2: times 2 dw 4,3,2,1
```

* SECTION_RODATA는 이 섹션이 읽기 전용 데이터 영역임을 지정합니다. (이것은 매크로이며, 운영체제에서 사용하는 출력 파일 형식에 따라 선언 방식이 다르기 때문입니다.)
* constants_1: constants_1이라는 레이블은 ```db```(declare byte)로 정의되어 있습니다. 즉, uint8_t constants_1[4] = {1, 2, 3, 4};와 동일합니다.
* constants_2: ```times 2``` 매크로를 사용하여 선언된 워드(16비트 단위)를 두 번 반복합니다. 즉, uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1};과 같습니다.

이러한 레이블은 어셈블러에 의해 메모리 주소로 변환되며, 이후 로드(load) 연산에서 사용할 수 있습니다. (읽기 전용이므로 스토어(store) 연산은 불가능합니다.) 일부 명령어는 메모리 주소를 피연산자로 직접 취할 수 있으므로, 레지스터에 명시적으로 로드하지 않고도 사용할 수 있습니다. (이 방식에는 장단점이 있습니다.)

**오프셋**

오프셋은 메모리에서 연속된 요소들 사이의 거리(바이트 단위)를 의미합니다. 오프셋은 데이터 구조에서 **각 요소의 크기**에 의해 결정됩니다.

이제 루프를 작성할 수 있게 되었으니 데이터를 가져올 차례입니다. 하지만 C와는 약간의 차이가 있습니다. 다음의 C 코드를 보겠습니다.

```c
uint32_t data[3];
int i;
for(i = 0; i < 3; i++) {
data[i];
}
```

이때 data의 각 요소 사이의 4바이트 오프셋은 C 컴파일러가 미리 계산합니다. 하지만 어셈블리를 직접 작성할 때는 이러한 오프셋을 스스로 계산해야 합니다.

메모리 주소 계산의 문법은 다음과 같습니다. 이 문법은 모든 형태의 메모리 주소에 적용됩니다.

```assembly
[base + scale*index + disp]
```

* base - GPR이며, 일반적으로 C 함수 인자로부터 전달된 포인터 입니다.
* scale - 1, 2, 4, 8 중 하나의 값을 가질 수 있으며, 기본값은 1입니다.
* index - GPR이며, 일반적으로 루프 카운터로 사용됩니다.
* disp - 정수(최대 32비트)로, 데이터 내부의 오프셋(Displacement)을 의미합니다.

x86asm은 현재 사용 중인 SIMD 레지스터의 크기를 알려주는 mmsize 상수를 제공합니다.

다음은 사용자 정의 오프셋으로부터 데이터를 로드하는 간단한 (실제로는 의미 없는) 예시입니다.

```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]``` 구문에서 어셈블러는 적절한 디스플레이스먼트 값을 자동으로 계산합니다. 다음 강의에서는 루프 내에서 add와 dec를 모두 사용하는 대신, 이를 단일 add로 대체하는 트릭을 다룰 것입니다.

**LEA**

이제 오프셋을 이해했으니 LEA(Load Effective Address)를 사용할 수 있습니다. 이는 하나의 명령으로 곱셈과 덧셈을 수행하게 해주며, 여러 명령을 사용하는 것보다 더 빠릅니다. 물론 곱하거나 더할 수 있는 값에는 제한이 있지만, 그렇다고 해서 lea가 강력한 명령이 아니라는 뜻은 아닙니다.

```assembly
lea r0q, [base + scale*index + disp]
```

이름과 달리 LEA는 주소 계산뿐만 아니라 일반 산술 연산에도 사용할 수 있습니다. 예를 들어 다음과 같은 복잡한 계산도 가능합니다.

```assembly
lea r0q, [r1q + 8*r2q + 5]
```

이 명령은 r1q와 r2q의 내용을 변경하지 않으며, *FLAGS*에도 영향을 주지 않습니다. 따라서 lea의 결과를 기반으로 점프할 수는 없습니다. lea를 사용하면 아래와 같은 여러 명령어와 임시 레지스터를 사용할 필요가 없습니다(아래 코드는 add가 *FLAGS*를 변경하기 때문에 완전히 동일한 동작은 아닙니다).

```assembly
movq r0q, r1q
movq r3q, r2q
sal r3q, 3 ; 왼쪽으로 3비트 시프트 = ×8
add r3q, 5
add r0q, r3q
```

lea는 루프 전에 주소를 설정하거나 위와 같은 계산을 수행할 때 자주 사용됩니다. 물론 모든 형태의 곱셈과 덧셈을 수행할 수 있는 것은 아니지만, 1, 2, 4, 8로의 곱셈과 고정된 오프셋의 덧셈은 흔하게 사용됩니다.

과제에서는 상수를 로드하고 루프안에서 SIMD 벡터에 그 값을 더하는 작업을 하게 될 것입니다.

[다음 강의](../lesson_03/index.md)
Loading