LSL / LSR 명령어 (Logical Shift Left / Right)
Summary
정의: 레지스터 값을 좌 또는 우로 비트 시프트하는 논리(logical) 시프트 명령어. 부호와 무관하게 빈 자리를 항상 0으로 채우는 것이 핵심 특징임.
기본 문법
LSL Xd, Xn, #shift // 즉치값만큼 왼쪽 시프트
LSL Xd, Xn, Xm // 레지스터 값만큼 왼쪽 시프트
LSR Xd, Xn, #shift // 즉치값만큼 오른쪽 시프트
LSR Xd, Xn, Xm // 레지스터 값만큼 오른쪽 시프트
32비트 버전: W 레지스터로 동일하게 사용 가능 (LSL Wd, Wn, #shift).
정식 명칭(레지스터 시프트 버전): LSLV, LSRV가 정식 니모닉이며, 어셈블러가 LSL/LSR로 자동 표시함 (이전 ASR/ASRV 관계와 동일한 구조).
LSL — 왼쪽 시프트
동작: 비트를 왼쪽으로 밀며, 오른쪽(하위) 빈 자리를 0으로 채움. 밀려나간 최상위 비트는 버려짐.
연산적 의미: LSL #n은 값에 2^n을 곱하는 것과 동일함 (오버플로우 무시 시).
mov x0, #5 // x0 = 0b101
lsl x1, x0, #2 // x1 = 0b10100 = 20 (5 × 4)
부호 유무와 무관한 이유: 이전에 설명한 대로, 왼쪽 시프트는 밀려나가는 방향이 최상위(부호) 비트 쪽이므로 “부호를 유지하며 채운다”는 개념 자체가 성립하지 않음. 그래서 ASL이 따로 존재하지 않고 LSL 하나로 통합되어 있음.
LSR — 오른쪽 시프트
동작: 비트를 오른쪽으로 밀며, 왼쪽(상위) 빈 자리를 무조건 0으로 채움. 부호 비트도 예외 없이 0으로 덮어씀.
연산적 의미: LSR #n은 부호 없는(unsigned) 값에서 2^n으로 나누는 것과 동일함.
mov x0, #20 // x0 = 0b10100
lsr x1, x0, #2 // x1 = 0b00101 = 5 (20 ÷ 4)
주의 — 음수(signed) 값에 LSR 사용 시 위험성:
mov x0, #-16 // x0 = 전부 1로 채워진 비트패턴 (signed -16)
lsr x1, x0, #2 // 부호 무시하고 0으로 채움 → 매우 큰 양수로 왜곡됨
asr x2, x0, #2 // 올바른 signed 결과: -4
판단 기준: 데이터가 부호 없는(unsigned) 값(주소, 인덱스, 비트마스크 등)일 때는 LSR, 부호 있는(signed) 값일 때는 ASR 사용이 원칙임.
LSL vs LSR vs ASR 종합 비교
| 명령어 | 방향 | 빈 자리 채움 | 연산적 의미 |
|---|---|---|---|
LSL |
왼쪽 | 0 (항상) | × 2^n |
LSR |
오른쪽 | 0 (항상) | ÷ 2^n (unsigned) |
ASR |
오른쪽 | 부호 비트 | ÷ 2^n (signed, 근사) |
비대칭 구조 재확인: 왼쪽은 LSL 하나뿐이지만, 오른쪽은 LSR/ASR 둘로 분리됨. 이는 이전 ASR 문서에서 다룬 핵심 원리와 정확히 일치함.
예제 코드 — LSL/LSR 비교 실습
.global _start
.align 2
.text
_start:
mov x0, #16 // x0 = 0b10000
lsl x1, x0, #1 // x1 = 32 (16 × 2)
lsr x2, x0, #1 // x2 = 8 (16 ÷ 2)
mov x0, #0
mov x16, #1
svc #0x80
Shifted Register 형태 — 단독보다 결합형이 더 자주 관찰됨
이전 ASR 설명과 동일한 패턴: LSL/LSR도 단독 명령어보다 다른 산술/논리 명령어의 shift operand로 결합되는 경우가 실전에서 압도적으로 많음.
add x0, x1, x2, lsl #2 // 배열 인덱싱: base + (index × 4), int 배열 주소 계산의 전형적 패턴
add x0, x1, x2, lsr #1 // x2를 절반으로 나눈 값을 x1에 더함
sub x0, x1, x2, lsl #3 // (index × 8) 뺄셈, long/double 배열 주소 계산
실전 최다 활용 사례 — 배열 주소 계산:
// C 코드: array[i] (int 배열, 4바이트 단위)
// x0 = base address, x1 = index
ldr w2, [x0, x1, lsl #2] // base + index*4 위치에서 4바이트 로드
결론: LSL은 특히 배열 인덱싱 시 요소 크기(2/4/8바이트)만큼 곱하는 용도로, 컴파일러가 생성하는 코드에서 극히 빈번하게 관찰됨. LSR은 상대적으로 단독 나눗셈 연산보다는 비트 추출(특정 상위 비트를 하위로 끌어내리는) 목적으로 더 자주 쓰임.
LSR을 이용한 비트 추출 패턴
mov x0, #0xABCD1234
lsr x1, x0, #16 // 상위 16비트를 하위로 이동 → x1의 하위 16비트 = 0xABCD
and x1, x1, #0xFFFF // 필요 시 상위 비트 마스킹까지 추가
용도: 32비트/64비트 값에서 특정 바이트 필드(색상값의 R/G/B 채널, 패킹된 구조체 필드 등)를 추출할 때 LSR + AND 조합이 표준 패턴으로 자주 사용됨.