Skip to the content.

SWP 명령어 (Swap) — 원자적 교환

Summary

정의: 메모리 위치의 값을 원자적으로 교환(swap) 하는 명령어. 새 값을 메모리에 쓰는 동시에, 그 자리에 있던 이전 값을 레지스터로 반환함. ARMv8.1 LSE(Large System Extensions) 확장의 일부로, 이전에 다룬 LDADD/LDCLR/LDEOR/LDSET 계열과 정확히 같은 뿌리를 가진 명령어임.


기본 문법

SWP Xs, 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

동작 논리:

SWPAL 선택 이유: 락 획득은 이후 임계 구역(critical section) 접근 전에 반드시 완료되어야 하므로 Acquire 순서가 필요하고, 동시에 이전 코드와의 순서도 보장하려면 AL(Acquire-Release) 조합이 안전한 선택으로 흔히 쓰임.


CAS와의 비교 — 언제 SWP, 언제 CAS

이전 LDADD 문서에서 언급한 CAS(Compare and Swap) 재소환: SWPCAS는 비슷해 보이지만 근본적 차이가 있음.

명령어 교환 조건 용도
SWP 무조건 교환 단순 값 교체, 락 획득(단순 flag 방식)
CAS 기존 값이 예상값과 일치할 때만 교환 조건부 갱신, ABA 문제 방지, 더 정교한 동기화

CAS 예시 재확인:

mov x0, #0            // expected = 0
mov x1, #1            // new = 1
cas x0, x1, [x2]        // [x2]가 0일 때만 1로 교체, 아니면 x0에 실제 현재값 반환(교체 안 함)

판단 기준:

단순 스핀락(0/1 플래그)에는 SWP로도 충분하지만, 더 복잡한 락-프리 자료구조(연결 리스트, 큐 등)에서는 CAS가 필수적임.


STSWP는 없음 — 주의 사항 (STADD 등과의 차이 짚기)

이전 LDADD 문서 패턴 재확인: LDADDXt=XZR일 때 STADD라는 별칭이 존재했음. 하지만 SWP는 이 별칭 패턴이 적용되지 않음. XtXZR을 넣어 이전 값을 버리는 것은 문법적으로 가능하나, 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 계열로 넘어가는 것이 일반적인 설계 흐름임.