1. CUDA 코드
대상 커널은 대략 이 구조이다.
__global__ void copy_global_kernel(const float* __restrict__ x,
float* __restrict__ y,
int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
y[i] = x[i];
}
}
이 커널의 high-level 의미는 단순하다.
각 thread 가 하나의 index i 를 계산하고, i < n 이면 x[i] 를 읽어서 y[i] 에 저장한다.
즉 핵심 연산은 다음과 같다
y[i] = x[i];
SASS 에서는 이 한 줄이 보통 아래 역할들로 분해된다.
- thread / block index 읽기
- linear index i 계산
- bounds check
- x[i] 주소 계산
- x[i] load
- y[i] 주소 계산
- y[i] store
- exit
2. SASS 전체 구조 요약
SASS 핵심 부분은 다음이다.
/*0000*/ MOV R1, c[0x0][0x28] ;
/*0010*/ S2R R4, SR_CTAID.X ;
/*0020*/ S2R R3, SR_TID.X ;
/*0030*/ IMAD R4, R4, c[0x0][0x0], R3 ;
/*0040*/ ISETP.GE.AND P0, PT, R4, c[0x0][0x170], PT ;
/*0050*/ @P0 EXIT ;
/*0060*/ MOV R5, 0x4 ;
/*0070*/ ULDC.64 UR4, c[0x0][0x118] ;
/*0080*/ IMAD.WIDE R2, R4, R5, c[0x0][0x160] ;
/*0090*/ LDG.E.CONSTANT R3, [R2.64] ;
/*00a0*/ IMAD.WIDE R4, R4, R5, c[0x0][0x168] ;
/*00b0*/ STG.E [R4.64], R3 ;
/*00c0*/ EXIT ;
역할만 압축하면
R4 = blockIdx.x * blockDim.x + threadIdx.x
P0 = (R4 >= n)
if P0 exit
R2 = x + R4 * 4
R3 = load x[i]
R4 = y + R4 * 4
store y[i] = R3
exit
역할만 압축하면
R4 = blockIdx.x * blockDim.x + threadIdx.x
P0 = (R4 >= n)
if P0 exit
R2 = x + R4 * 4
R3 = load x[i]
R4 = y + R4 * 4
store y[i] = R3
exit
3. 명령어별 매핑
- MOV R1, c[0x0][0x28]
- 스택 포인터 / 런타임용 레지스터 초기화 - CUDA 코드 대응 없음
- S2R R4, SR_CTAID.x
- blockIdx.x 읽기
- blockIdx.x
- blockIdx.x 읽기
- S2R R3, SR_TID.x
- threadIdx.x 읽기
- threadIdx.x
- threadIdx.x 읽기
- IMAD R4, R4, c[0x0][0x0], R3
- blockIdx.x * blockDim.x = threadIdx.x
- int i - ...
- blockIdx.x * blockDim.x = threadIdx.x
- ISETP.GE.AND P0, PT, R4, x[0x0] [0x170], PT
- i >= n 비교
- if ( i < n ) 의 반대 조건
- i >= n 비교
- @P0 EXIT
- i >= n 이면 종료
- bounds check
- i >= n 이면 종료
- MOV R5, 0x4
- sizeof(float) = 4
- float index byte offset 계산
- sizeof(float) = 4
- IMAX.WIDE R2, R4, R5, c[0x0][0x160]
- x + i * 4 주소 계산
- &y[i]
- x + i * 4 주소 계산
- STG.R [R4.64], R3
- global memory 에 저장
- y[i] = ...
- global memory 에 저장
- EXIT
- 커널 종료
- 함수 종료
- 커널 종료
4. CUDA 코드와 SASS 대응
CUDA
int i = blockIdx.x * blockDim.x + threadIdx.x;
SASS
S2R R4, SR_CTAID.x ;
S2R R3, SR_TID.x ;
IMAD R4, R4, c[0x0][0x0], R3 ;
여기서 핵심은 IMAD 이다
R4 = R4 * c[0x0][0x0] + R3
각 값은 다음처럼 해석할 수 있다.
R4 = blockIdx.x
c[0x0][0x0] = blockDim.x
R3 = threadIdx.x
그래서 결괒거으로
R4 = blockIdx.x * blockDim.x + threadIdx.x
즉, CUDA 의 i 가 SASS 에서는 R4 에 들어간다.
5. bounds check 분석
cuda
if (i < n) {
y[i] = x[i];
}
SASS
ISETP.GE.AND P0, PT, R4, c[0x0][0x170], PT ;
@P0 EXIT ;
여기서 CUDA 코드는 i < n 인데 SASS 는 반대로 검사한다.
CUDA : if ( i < n ) 실행
SASS : if ( i >= n ) 종료
즉, 컴파일러가 조건을 이렇게 바꾼다.
if (i >= n) return;
y[i] = x[i];
이 형태가 SASS 에서는 더 자연스럽다.
P0 = (i >= n)
@P0 EXIT
P0 는 predicate register 이다. 일반 값 레지스터가 아니라 조건 실행을 위한 불리언 레지스터
6. 주소 계산 분석
CUDA
x[i]
SASS
MOV R5, 0x4 ;
IMAD.WIDE R2, R4, R5, c[0x0][0x160] ;
LDG.E.CONSTANT R3, [R2.64] ;
여기서 R5 = 4 이다.
float 하나 = 4 bytes
따라서
R2 = x_base + i * 4
그 다음
LDG.E.CONSTANT R3, [R2.64] ;
는
float value = x[i];
에 대응된다.
R2.64 는 R2:R3 같은 64-bit 주소 표현으로 볼 수 있다. 주소는 64-bit 이고 , 데이터는 float 32-bit 이다.
다만 이 SASS 에서는 LDG 결과가 R3 에 들어간다.
R3 = x[i]
7. store 분석
CUDA
y[i] = x[i];
SASS
IMAD.WIDE R4, R4, R5, c[0x0][0x168] ;
STG.E [R4.64], R3 ;
여기서는 y[i] 주소를 계산한다.
R4 = y_base + i * 4
그 다음
STG.E [R4.64], R3 ;
즉
global memory의 y[i] 위치에 R3 값을 저장
앞에서 R3 에는 x[i] 가 들어 있었다.
따라서 전체적으로
y[i] = x[i]
가 된다.
8. register 흐름
이 커널에서 핵심 register 흐름은 이렇게 볼 수 있다.
R4
├─ 처음: blockIdx.x
├─ IMAD 후: i = blockIdx.x * blockDim.x + threadIdx.x
├─ bounds check에 사용
├─ x 주소 계산에 사용
└─ 나중에 y 주소 계산 결과로 덮어쓰기됨
R3
├─ 처음: threadIdx.x
└─ LDG 후: x[i] 값으로 덮어쓰기됨
R5
└─ 4, 즉 sizeof(float)
R2
└─ x[i]의 64-bit global address
중요한 관찰은 이것이다.
- 같은 register 가 계속 같은 의미를 유지하지 않는다.
그래서 SASS 분석에서는 R3는 무엇이다라고 고정하면 안되다.
정확히는 이 시점의 R3 는 무엇이다라고 봐야 한다.
9. high-level pseudo SASS
R4 = blockIdx.x;
R3 = threadIdx.x;
R4 = R4 * blockDim.x + R3;
P0 = R4 >= n;
if (P0) exit;
R5 = 4;
R2 = x_base + R4 * R5;
R3 = global_load_float(R2);
R4 = y_base + R4 * R5;
global_store_float(R4, R3);
exit;
10. 여기서 얻는 기본 패턴
copy_global 에서 얻는 첫 번째 SASS 패턴은 이것이다.
Pattern: 1D elementwise kernel index 계산
S2R R?, SR_CTAID.X
S2R R?, SR_TID.X
IMAD R_i, R_cta, blockDim.x, R_tid
의미
int i = blockIdx.x * blockDim.x + threadIdx.x;
두 번째 패턴
Pattern: bounds check
ISETP.GE.AND P0, PT, R_i, n, PT
@P0 EXIT
의미
if (i >= n) return;
CUDA 원본과 반대 조건으로 exit 하는 조건이 SASS 에서 자주 나온다..
세 번째 패턴
Pattern: float global load
MOV R_stride, 0x4
IMAD.WIDE R_addr, R_i, R_stride, x_base
LDG.E R_val, [R_addr.64]
의미
float val = x[i];
네 번째 패턴
Pattern: float global store
IMAD.WIDE R_addr, R_i, R_stride, y_base
STG.E [R_addr.64], R_val
의미
y[i] = val;
11. copy_global 에서 특히 중요한 관찰
- special register 읽기
- linear thread index 계산
- predicate 기반 bounds check
- byte offset 계산
- global load
- global store
- register reuse
'SASS_Probe' 카테고리의 다른 글
| fma_f32 분석 (0) | 2026.06.13 |
|---|---|
| mul_f32 분석 (0) | 2026.06.13 |
| SASS 분석을 통한 연산 구조 해석과 최적화 (0) | 2026.06.13 |
| add_f32 분석 (0) | 2026.06.11 |
| SASS 명령어 분류, 내용 확인 (0) | 2026.06.11 |