# entry-fpsimd.S — 말로 풀어 읽기
Linux v6.18.37 · arch/arm64/kernel/entry-fpsimd.S

프로그램의 계산 상태는 x0 같은 정수 레지스터에만 있지 않습니다. 소수 연산과 벡터 연산에도 별도 레지스터가 있습니다. 다른 태스크가 CPU를 쓰거나 커널이 이 기능을 사용할 때 원래 계산 상태를 잃지 않도록 저장하고 복원해야 합니다. 이 파일의 짧은 매크로 한 줄 안에는 여러 저장 명령이 들어 있습니다.

## 처음 읽을 때
q0 하나는 16바이트입니다. 정수 레지스터 두 개를 저장하는 STP와 q 레지스터 두 개를 저장하는 STP의 바이트 수가 같지 않다는 점부터 확인하세요.

## 더 깊이 살펴볼 때
SVE와 SME는 현재 벡터 길이에 따라 상태 크기가 달라집니다. 포인터의 기준 위치, FFR을 다룰 수 있는 모드, ZA 이후에 놓이는 ZT 상태를 구분해 보세요.

![단계별 개념 그림](../media/entry-fpsimd-overview.png)
화살표는 값 복사 방향입니다. Q0–Q31 전체를 하나의 그룹으로 줄여 그렸으며 SVE·SME 영역은 포함하지 않았습니다.

## 고정 크기 FP/SIMD 상태
원본 1–33행

### 9행
```asm
#include <linux/linkage.h>
```
SYM_FUNC_START·SYM_CODE_START·SYM_INNER_LABEL 등의 심볼 선언 규칙을 가져옵니다. 함수인지 코드 내부 라벨인지, 다른 오브젝트에 보일 이름인지와 정렬·크기 정보를 빌드 도구에 전달하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 11행
```asm
#include <asm/assembler.h>
```
ARM64의 큰 주소 구성, CPU별 값 조회, 시스템 레지스터 처리, 동기화 등에 쓰는 어셈블리 매크로를 가져옵니다. 각 사용 위치에서 일반 명령 여러 개로 펼쳐지는 작업입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 12행
```asm
#include <asm/fpsimdmacros.h>
```
FP/SIMD·SVE·SME 레지스터 저장 형식과 명령 인코딩 매크로를 가져옵니다. 어떤 상태를 어느 offset에 저장하는지, 임시 레지스터 인수가 무엇인지가 여기에 정의됩니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 19행
```asm
SYM_FUNC_START(fpsimd_save_state)
```
이 위치부터 (fpsimd_save_state)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 20행
```asm
	fpsimd_save x0, 8
```
x0가 가리키는 저장 영역에 q0~q31의 SIMD/FP 값과 FPSR·FPCR을 보존합니다. 8은 주소나 벡터 수가 아니라 임시 x8/w8을 고르는 매크로 인수입니다. task 전환·시그널 등에서 계산 상태를 잃지 않으려는 저장이며 매크로 내부에서 x0도 이동합니다.
원래 x0=B라면 q0은 B, q31은 B+496에 저장됩니다. FPSR은 B+512, FPCR은 B+516입니다.

### 21행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 22행
```asm
SYM_FUNC_END(fpsimd_save_state)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 29행
```asm
SYM_FUNC_START(fpsimd_load_state)
```
이 위치부터 (fpsimd_load_state)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 30행
```asm
	fpsimd_restore x0, 8
```
x0의 메모리 사본에서 q0~q31과 FPSR·FPCR을 CPU에 복원합니다. 임시 레지스터는 x8/w8이며 FPCR은 기존 값과 같으면 불필요한 쓰기를 피합니다. 저장 때와 같은 레이아웃의 버퍼가 필요합니다.

### 31행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 32행
```asm
SYM_FUNC_END(fpsimd_load_state)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## SVE 저장과 벡터 길이
원본 34–68행

### 34행
```asm
#ifdef CONFIG_ARM64_SVE
```
SVE는 기본 128비트 SIMD보다 긴 Z 벡터, P predicate와 FFR 상태를 추가하는 확장입니다. 이 설정을 켜야 아래 저장·복원·길이 조정 코드를 커널에 포함하여 지원 CPU의 task 상태를 관리할 수 있습니다. 꺼지면 아래 SVE 함수들은 빌드되지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 43행
```asm
SYM_FUNC_START(sve_save_state)
```
이 위치부터 (sve_save_state)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 44행
```asm
	sve_save 0, x1, x2, 3
```
x0를 SVE 저장 레이아웃의 FFR 기준 위치로 사용해 Z0~Z31과 P0~P15를 정해진 음수 offset에 저장합니다. 첫 인수 0은 주소 0이 아니라 x0 레지스터 번호입니다. x1은 FPSR/FPCR 저장 위치, x2는 FFR 보존 여부, 3은 임시 x3 번호입니다. x2=0이면 저장 이미지의 FFR은 0으로 만듭니다. 저장 공간의 크기와 Z·P·FFR 사이 오프셋은 현재 SVE 벡터 길이에 따라 달라집니다. 고정 크기인 FPSIMD 저장 형식과 구분해야 합니다.
0은 레지스터 번호 x0를 뜻합니다. x1은 FPSR/FPCR 저장 위치, x2는 FFR 저장 여부, 3은 임시 x3 번호입니다.

### 45행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 46행
```asm
SYM_FUNC_END(sve_save_state)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 55행
```asm
SYM_FUNC_START(sve_load_state)
```
이 위치부터 (sve_load_state)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 56행
```asm
	sve_load 0, x1, x2, 4
```
x0 기준의 Z·P 상태와 x1 위치의 FPSR/FPCR을 복원합니다. x2가 0이 아니면 FFR도 복원하고 0이면 FFR 로드를 생략합니다. 첫 인수 0과 마지막 4는 각각 x0 기준 주소와 임시 x4를 뜻하는 레지스터 번호입니다. 저장할 때와 같은 SVE 벡터 길이와 레이아웃을 사용해야 각 레지스터의 데이터를 올바른 위치에서 읽을 수 있습니다.
마지막 인자 4는 임시 x4 번호입니다.

### 57행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 58행
```asm
SYM_FUNC_END(sve_load_state)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 60행
```asm
SYM_FUNC_START(sve_get_vl)
```
이 위치부터 (sve_get_vl)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 61행
```asm
	_sve_rdvl	0, 1
```
RDVL 명령을 인코딩해 현재 SVE 벡터 길이의 1배를 바이트 단위로 x0에 반환합니다. 인수 0은 결과 레지스터 x0, 1은 길이 배수이며 bit 수나 저장 버퍼 주소가 아닙니다.
x0에 현재 SVE VL의 바이트 수를 반환합니다.

### 62행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 63행
```asm
SYM_FUNC_END(sve_get_vl)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 65행
```asm
SYM_FUNC_START(sve_set_vq)
```
이 위치부터 (sve_set_vq)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 66행
```asm
	sve_load_vq x0, x1, x2
```
x0의 VQ-1 값을 ZCR_EL1.LEN 필드에 반영하여 SVE 벡터 길이를 선택합니다. VQ는 16바이트 단위이며 0 인코딩은 128비트 길이입니다. x1·x2는 기존 제어값과 수정값을 계산할 임시 레지스터이고 같은 설정이면 레지스터 쓰기를 생략합니다.
x0는 VQ−1이며 x1/x2를 임시로 사용합니다.

### 67행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 68행
```asm
SYM_FUNC_END(sve_set_vq)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## SVE 상위 영역 지우기
원본 69–89행

### 79행
```asm
SYM_FUNC_START(sve_flush_live)
```
이 위치부터 (sve_flush_live)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 80행
```asm
	cbz		x1, 1f	// A VQ-1 of 0 is 128 bits so no extra Z state
```
x1은 VQ-1입니다. 값 0이면 Z 레지스터가 기본 128비트뿐이므로 지울 상위 SVE 부분이 없어 sve_flush_z를 건너뜁니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
x1=0은 VQ=1, 즉 128비트라서 Z의 추가 부분을 지울 필요가 없습니다.

### 81행
```asm
	sve_flush_z
```
모든 Z 레지스터의 하위 128비트 FP/SIMD 값은 유지하고 그 위의 SVE 부분만 0으로 만듭니다. 전체 계산 상태를 버리는 것이 아니라 기본 FPSIMD에서 확장 SVE 상태로 넘어갈 때의 잔여값을 정리합니다.

### 82행
```asm
1:	sve_flush_p
```
P0~P15를 모두 false로 만듭니다. predicate는 각 벡터 원소가 연산에 참여할지 정하는 마스크이므로, 이전 사용자의 predicate 상태가 확장 상태에 남지 않게 합니다. 각 predicate 레지스터에 PFALSE 명령을 적용합니다.

### 83행
```asm
	tbz		x0, #0, 2f
```
x0의 bit 0은 FFR도 정리할지 나타내는 인수입니다. 0이면 해당 작업을 생략하고, 1이면 다음 sve_flush_ffr를 실행합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
이 함수에서는 x0의 비트 0만 FFR 초기화 여부로 검사합니다.

### 84행
```asm
	sve_flush_ffr
```
이미 false로 만든 P0를 FFR에 써서 first-fault 상태를 0으로 정리합니다. FFR은 fault-first load에서 어느 원소까지 유효했는지 나타내는 상태이며 일반 condition flags가 아닙니다.

### 85행
```asm
2:	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 86행
```asm
SYM_FUNC_END(sve_flush_live)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 88행
```asm
#endif /* CONFIG_ARM64_SVE */
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

## SME의 ZA와 ZT
원본 90–134행

### 90행
```asm
#ifdef CONFIG_ARM64_SME
```
SME는 streaming vector 실행과 2차원 행렬 누산 상태 ZA 등을 추가합니다. 이 설정을 켜면 아래 streaming 길이·ZA·선택적 ZT 저장 복원 함수를 빌드하고, 꺼지면 포함하지 않습니다. 실제 SME/SME2 사용 가능 여부와 mode 활성화는 호출자도 확인해야 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 92행
```asm
SYM_FUNC_START(sme_get_vl)
```
이 위치부터 (sme_get_vl)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 93행
```asm
	_sme_rdsvl	0, 1
```
RDSVL로 streaming vector 길이의 1배를 바이트 단위로 x0에 읽습니다. 일반 SVE VL과 SME의 SVL은 별도로 선택될 수 있으므로 sve_get_vl 대신 이 함수를 사용합니다. 매크로 인수 0은 목적 레지스터 x0의 번호이고 1은 길이에 곱할 배수입니다. 어셈블러가 이 인수로 RDSVL 인코딩을 만듭니다.

### 94행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 95행
```asm
SYM_FUNC_END(sme_get_vl)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 97행
```asm
SYM_FUNC_START(sme_set_vq)
```
이 위치부터 (sme_set_vq)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 98행
```asm
	sme_load_vq x0, x1, x2
```
x0의 VQ-1 인코딩을 SMCR_EL1.LEN에 반영해 streaming vector 길이를 정합니다. x1·x2는 임시 값이며 ZCR_EL1을 쓰는 일반 SVE 길이 설정과 구분합니다.

### 99행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 100행
```asm
SYM_FUNC_END(sme_set_vq)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 108행
```asm
SYM_FUNC_START(sme_save_state)
```
이 위치부터 (sme_save_state)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 109행
```asm
	_sme_rdsvl	2, 1		// x2 = VL/8
```
streaming vector 길이를 바이트 수로 x2에 읽습니다. ZA 한 행의 크기이자 ZA 행 개수의 기준이므로 뒤의 반복 저장에 필요합니다. 매크로 인수 2은 목적 레지스터 x2의 번호이고 1은 길이에 곱할 배수입니다. 어셈블러가 이 인수로 RDSVL 인코딩을 만듭니다.
원문 VL/8은 비트 길이를 8로 나눈 바이트 수를 뜻합니다.

### 110행
```asm
	sme_save_za 0, x2, 12		// Leaves x0 pointing to the end of ZA
```
ZA의 각 행을 x0가 가리키는 버퍼에 저장합니다. 0은 기준 x0 번호, x2는 행 크기, 12는 행 선택용 w12 번호입니다. 매 행 뒤 x0를 늘려 전체 SVL_bytes² 영역을 저장하고 끝 위치를 남깁니다.
w12로 행을 세고 행마다 x0를 x2만큼 증가시켜 ZA 전체를 저장합니다.

### 112행
```asm
	cbz	x1, 1f
```
호출자가 x1로 ZT 상태 저장 필요 여부를 전달합니다. 0이면 ZA만 저장한 뒤 반환하고, 0이 아니면 바로 뒤의 ZT 저장을 실행합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 113행
```asm
	_str_zt 0
```
현재 x0, 즉 ZA 이미지 뒤의 위치에 ZT0 상태를 저장하는 인코딩을 출력합니다. ZT0는 지원 SME 확장의 추가 상태이며 이 줄이 모든 SME CPU에서 무조건 실행된다고 가정하지 않습니다.
앞 매크로가 증가시킨 x0는 ZA 뒤의 ZT0 저장 위치입니다.

### 114행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 115행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 116행
```asm
SYM_FUNC_END(sme_save_state)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 124행
```asm
SYM_FUNC_START(sme_load_state)
```
이 위치부터 (sme_load_state)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 125행
```asm
	_sme_rdsvl	2, 1		// x2 = VL/8
```
현재 streaming vector 길이를 바이트 단위로 x2에 읽어 ZA 복원 행 크기를 정합니다. 저장 버퍼의 레이아웃과 복원 시 길이가 맞아야 합니다. 매크로 인수 2은 목적 레지스터 x2의 번호이고 1은 길이에 곱할 배수입니다. 어셈블러가 이 인수로 RDSVL 인코딩을 만듭니다.

### 126행
```asm
	sme_load_za 0, x2, 12		// Leaves x0 pointing to the end of ZA
```
x0 기준 버퍼에서 ZA를 행 단위로 복원하고 x0를 ZA 이미지 끝으로 이동합니다. w12가 행 선택을 위한 임시 레지스터이며 x2가 각 행의 바이트 수입니다.

### 128행
```asm
	cbz	x1, 1f
```
x1=0이면 추가 ZT 상태 복원을 생략하고, 그 외에는 저장 이미지에서 ZA 뒤의 ZT0를 읽습니다. 하드웨어 지원에 맞는 인수를 주는 것은 호출자의 책임입니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 129행
```asm
	_ldr_zt 0
```
ZA 복원 뒤의 x0 위치에서 ZT0를 읽습니다. 첫 인수 0은 주소 상수가 아니라 x0 레지스터를 고르는 매크로 인수입니다.

### 130행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 131행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 132행
```asm
SYM_FUNC_END(sme_load_state)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 134행
```asm
#endif /* CONFIG_ARM64_SME */
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

## 설명한 뒤 함께 생각해 볼 질문

### q0–q31은 총 몇 바이트인가요?
32×16=512바이트입니다. FPSR와 FPCR 필드는 그 뒤에 각각 4바이트씩 있으며 구조체의 전체 정렬 크기는 별도입니다.

### fpsimd_save를 부른 뒤 x0가 그대로인가요?
마지막 쌍 저장에 writeback이 있어 상태 포인터가 바뀝니다. 인자 포인터 보존을 가정하면 안 됩니다.

### SVE에서 하위 128비트를 남기는 이유는 무엇인가요?
기존 FP/SIMD 상태와 겹치는 하위 부분을 유지하면서 추가 벡터 영역을 초기화하기 위한 동작입니다.
