Embedded2010.01.03 07:59
이번에는  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으로 넣어버린 것입니다.






Posted by GUNDAM_IM