SWP 명령어 (Swap) — 원자적 교환
Summary
정의: 메모리 위치의 값을 원자적으로 교환(swap) 하는 명령어. 새 값을 메모리에 쓰는 동시에, 그 자리에 있던 이전 값을 레지스터로 반환함. ARMv8.1 LSE(Large System Extensions) 확장의 일부로, 이전에 다룬 LDADD/LDCLR/LDEOR/LDSET 계열과 정확히 같은 뿌리를 가진 명령어임.
기본 문법
SWP Xs, Xt, [Xn]
Xs: 메모리에 새로 쓸 값(source)Xt: 메모리에서 읽어온 이전 값이 저장될 목적 레지스터Xn: 대상 메모리 주소가 담긴 레지스터
동작 수식:
Xt = [Xn] (이전 값을 레지스터로 읽어옴)
[Xn] = Xs (새 값을 메모리에 씀)
핵심: 이 두 단계가 하나의 원자적 연산으로 처리됨 — 즉, 다른 코어가 이 두 단계 사이에 끼어들어 값을 바꿀 수 없음.
LSE 계열과의 연결 — 왜 함께 묶이는가
이전 LDADD 문서 복습: LDADD가 “메모리 값 += Xs”였다면, SWP는 그보다 더 근본적인 “메모리 값을 통째로 교체”하는 연산임. 사실 개념적으로는 LDADD류보다 더 단순하고 원초적인 원자적 연산이라고 볼 수 있음.
같은 접미사 규칙 적용됨: 이전 문서에서 다룬 메모리 순서(ordering) 접미사 체계가 SWP에도 동일하게 적용됨.
| 접미사 | 의미 | 사용례 |
|---|---|---|
| (없음) | Relaxed | SWP |
A |
Acquire | SWPA |
L |
Release | SWPL |
AL |
Acquire-Release | SWPAL |
swp x2, x0, [x1] // Relaxed
swpa x2, x0, [x1] // Acquire
swpl x2, x0, [x1] // Release
swpal x2, x0, [x1] // Acquire-Release (락 구현에 가장 많이 쓰임)
결합 표기 형태:
SWP{A}{L} Xs, Xt, [Xn]
A와 L은 독립적으로 조합 가능하여 총 4가지 변형이 존재함 — 이전 LDADD 계열과 완전히 동일한 패턴.
SWP 이전 방식 — LDXR/STXR 재시도 루프와의 비교
LSE 이전(ARMv8.0) 방식:
retry:
ldxr x0, [x1] // 이전 값 로드 + 배타적 모니터
stxr w2, x3, [x1] // 새 값(x3) 저장 시도
cbnz w2, retry // 실패 시 재시도
LSE 방식:
swp x3, x0, [x1] // 한 줄로 끝남: x0=이전값, [x1]=x3(새값)
성능 이점: 이전 LDADD 문서에서 설명한 것과 동일한 이유 — 재시도 루프가 사라지므로 멀티코어 경쟁(contention) 상황에서 훨씬 효율적임.
실전 활용 — 스핀락(Spinlock) 구현의 핵심
SWP의 가장 대표적인 용도: 락(lock) 획득 시도 자체를 한 명령어로 구현.
전형적 스핀락 획득 패턴:
.data
lock_var: .quad 0 // 0 = unlocked, 1 = locked
.text
acquire_lock:
mov x0, #1 // 새로 쓸 값 (락 상태 = locked)
try_lock:
swpal x0, x1, [x2] // 원자적 교환: x2=lock 주소, x1=이전값 받음
cbnz x1, try_lock // 이전 값이 1(이미 잠겨있음)이면 계속 재시도
// 이전 값이 0이었다면 → 락 획득 성공
ret
동작 논리:
- 락이 풀려있었다면(
[lock_var]=0), 스왑 후x1=0을 받고 동시에 락을 1로 설정 → 락 획득 성공 - 락이 이미 걸려있었다면(
[lock_var]=1), 스왑 후에도x1=1을 받음(자신도 1을 썼으니 상태 변화 없음) → 재시도 필요 - 이 판단(이전 값이 0이었는지 확인)이 원자적으로 이루어지므로 경쟁 조건(race condition) 없이 안전함
SWPAL 선택 이유: 락 획득은 이후 임계 구역(critical section) 접근 전에 반드시 완료되어야 하므로 Acquire 순서가 필요하고, 동시에 이전 코드와의 순서도 보장하려면 AL(Acquire-Release) 조합이 안전한 선택으로 흔히 쓰임.
CAS와의 비교 — 언제 SWP, 언제 CAS
이전 LDADD 문서에서 언급한 CAS(Compare and Swap) 재소환: SWP와 CAS는 비슷해 보이지만 근본적 차이가 있음.
| 명령어 | 교환 조건 | 용도 |
|---|---|---|
SWP |
무조건 교환 | 단순 값 교체, 락 획득(단순 flag 방식) |
CAS |
기존 값이 예상값과 일치할 때만 교환 | 조건부 갱신, ABA 문제 방지, 더 정교한 동기화 |
CAS 예시 재확인:
mov x0, #0 // expected = 0
mov x1, #1 // new = 1
cas x0, x1, [x2] // [x2]가 0일 때만 1로 교체, 아니면 x0에 실제 현재값 반환(교체 안 함)
판단 기준:
- “무조건 값을 바꾸고 이전 값만 알면 됨” →
SWP - “특정 조건(기존 값이 예상과 같을 때)에서만 바꾸고 싶음” →
CAS
단순 스핀락(0/1 플래그)에는 SWP로도 충분하지만, 더 복잡한 락-프리 자료구조(연결 리스트, 큐 등)에서는 CAS가 필수적임.
STSWP는 없음 — 주의 사항 (STADD 등과의 차이 짚기)
이전 LDADD 문서 패턴 재확인: LDADD는 Xt=XZR일 때 STADD라는 별칭이 존재했음. 하지만 SWP는 이 별칭 패턴이 적용되지 않음. Xt에 XZR을 넣어 이전 값을 버리는 것은 문법적으로 가능하나, STSWP라는 축약 별칭은 존재하지 않음 — SWP 자체를 그대로 사용함.
swp x0, xzr, [x1] // 이전 값을 버리고 싶어도, 그냥 SWP 그대로 사용 (별칭 없음)
예제 코드 — 값 교환 실습
.global _start
.align 2
.data
shared_var: .quad 100
.text
_start:
adrp x1, shared_var@PAGE
add x1, x1, shared_var@PAGEOFF // x1 = shared_var 주소
mov x0, #999 // 새로 쓸 값
swp x0, x2, [x1] // 원자적 교환
// x2 = 이전 값(100), [shared_var] = 999(갱신됨)
mov x0, #0
mov x16, #1
svc #0x80
종합 정리 — LSE 원자적 명령어 가족 내 SWP의 위치
| 명령어 | 연산 | 조건부 여부 |
|---|---|---|
LDADD/LDCLR/LDEOR/LDSET 등 |
산술/비트 연산 후 저장 | 무조건 |
SWP |
단순 값 교체 | 무조건 |
CAS/CASP |
비교 후 조건부 교체 | 조건부 |
최종 결론: SWP는 LSE 원자적 명령어군 중에서도 가장 단순하고 직관적인 형태 — “값을 원자적으로 바꿔치기하고 이전 값을 돌려받는다”는 것이 전부임. 실전에서는 단순 스핀락의 lock/unlock 플래그 구현, 또는 세마포어·뮤텍스 라이브러리(macOS의 os_unfair_lock, pthread 내부 구현)의 최하단 원자적 연산으로 자주 등장하며, 더 정교한 조건부 동기화가 필요한 경우에는 CAS 계열로 넘어가는 것이 일반적인 설계 흐름임.