이번에는 Loop문이 어떻게 바뀌는지에 대해서 살펴 봅니다.
예제를 새로 짜는 것 보다는 기존에 있던것을 가지고 한번 해보겠습니다.
아래는 NEWLIB 라이브러리에 있는 memset 함수입니다.
참고로, newlib은 c에서 사용하는 여러가지 라이브러리를 embedded 등에서 사용할 수 있도록 제공해주는 라이브러리입니다. 다양한 프로세서에 포팅되어 있습니다. 대부분의 함수가 C로 코딩되어 있지만, 속도가 필요한 부분이나 프로세서에 종속적인 부분에 대해서는 assembler로 되어 있습니다.
주어진 어드레스 m에서 시작해서 주어진 값 c를 n개의 메모리에 차례로 기록하는 것입니다.
........
#define PREFER_SIZE_OVER_SPEED 1
_PTR
_DEFUN (memset, (m, c, n),
_PTR m _AND
int c _AND
size_t n)
{
#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) || defined(__mips16)
char *s = (char *) m;
while (n-- != 0)
{
*s++ = (char) c;
}
return m;
#else
char *s = (char *) m;
int i;
unsigned wordtype buffer;
......
#ifdef -else에서 #else는 복잡한(?) 판단문을 가지고 있으므로 위의 #ifdef를 이용하기로 합니다.
그래서 PERFER_SIZE_OF_SPEED를 Define하여서 #ifdef로 컴파일 되게 합니다.
코드를 보면 S라고 하는 스타팅 어드레스는 주어진 인자 m으로 세팅됩니다.
이후 while에서 n을 하나씩 밑으로 카운트 하면서 주어진 인자 c를 S포인터에 기록합니다.
이후 S 포인터를 하나씩 증가시킵니다.
컴파일 커맨드는 아래와 같습니다.
mips-elf-gcc -S -O2 memset.c -o memset.s
컴파일 결과는 아래와 같습니다.
.file 1 "memset.c"
.section .mdebug.abi32
.previous
.gnu_attribute 4, 1
.text
.align 2
.globl memset
.set nomips16
.ent memset
.type memset, @function
memset:
.frame $sp,0,$31 # vars= 0, regs= 0/0, args= 0, gp= 0
.mask 0x00000000,0
.fmask 0x00000000,0
.set noreorder
.set nomacro
beq $6,$0,$L7
move $2,$4
sll $5,$5,24
sra $5,$5,24
move $3,$4
$L3:
addiu $6,$6,-1
sb $5,0($3)
bne $6,$0,$L3
addiu $3,$3,1
$L7:
j $31
nop
.set macro
.set reorder
.end memset
.size memset, .-memset
.ident "GCC: (GNU) 4.4.2"
위에서 보면 우선 $6이 $0과 같으면 $L7으로 분기하도록 되어 있습니다.
$0는 0 레지스터 값이므로 항상 0을 가집니다. 즉 주어진 카운트 값이 0이면 더이상 할 필요가 없으므로, 그냥 일을 마치고 돌아가는 것입니다.
따라서 $6이 인자 c (count)에 해당된다는 것을 알수 있습니다.
그리고 그 밑에
sll $5,$5,24
sra $5,$5,24
구분은 쉬프트 구문인데 $5를 왼쪽으로 24번 보냈다가 오른쪽으로 다시 24번 보내는 구문입니다.
뻉뻉이 돌리는 것인데요, 이렇게 하면 상위 24비트는 모두 0가 됩니다. 위의 구문은 그냥
and $5 , $5 , 0xFF와 같은 구문이 됩니다.
이렇게 해서 하위 8비트 즉 하위 1Byte만 사용한다는 것입니다. 인자 리스트를 쭈욱 보면 c에 해당하는 것이
함수 중간에서 char로 사용하고 있으므로 $5는 c에 해당하는 것임을 눈치밥으로 알수 있습니다.
.......
*s++ = (char) c; <-- 요기서 c가 char로 쓰이므로 불필요한 상위 24비트를 날려버리는 것이빈다.
....
이렇게 해서 필요한 / 사용할 데이터 타입과 변수들을 정리해 두고 whil에 해당하는 루프를 시작합니다.
$L3:
addiu $6,$6,-1
sb $5,0($3)
bne $6,$0,$L3
addiu $3,$3,1
을 살펴 봅니다.
$6이 카운트 값이라고 하였으니 addiu $6,$6,-1 구문은 자연스럽게
...
while (n-- != 0)
...
에서 n--에 해당함을 알 수 있습니다.
이제 갑자기 나온 레지스터 $3 이 보이는데 코드를 보면 레지스터 $5의 값을 어드레스 $3에 기록하는 것을 알 수 있습니다. 즉 메모리에 쓰는 어드레스를 $3에 기록합니다. 따라서 C코드에서 변수 s에 해당하는 것이 $3임을 알수 있습니다.
이후 카운트 값 레지스터 $6이 0이 아니면 계속 루프를 돌고 0이면 밑으로 내려가도록 되어 있습니다.
addiu $3,$3,1 은
어드레스를 1 증가시키는 것에 해당합니다.
*s++ = (char) c;
에서 s++에 해당합니다.
이는 loop를 돌때 마다 해야 하므로 branch delay slot에 해당하는 BNE다음 구문으로 집어 넣습니다.
이렇게 해서 loop를 돌고 끝나면 jump로 복귀하게 됩니다.
복귀 문인 j 문 다음에 있는 NOP는 그냥 Delay Slot을 사용하지 않도록 하기 위해서 0으로 넣어버린 것입니다.