[루아1.1] opcode.c 읽기 (1)

3 분 소요

Opcode.c (1)

드디어 루아 1.1 코드 읽기의 마지막 파일입니다. Opcode.c 파일은 루아 1.1 소스 파일 중에서 두 번째로 긴 파일입니다. 내용이 많아요. 그래도 이 파일만 읽고 나면 루아 2.1로 넘어 갈 수 있습니다. Opcode.c 파일은 이름만 보면 루아 VM을 구현한 것으로 보입니다. 그럼 오늘도 읽어보겠습니다.

매번 반복하는 #include 구문은 쭉 읽고 지나갑니다.

#define tonumber(o) ((tag(o) != T_NUMBER) && (lua_tonumber(o) != 0))
#define tostring(o) ((tag(o) != T_STRING) && (lua_tostring(o) != 0))

시작하자마자 나오는 매크로가 두 개 보입니다. 이름을 보니 tonumber() 매크로는 루아 number 타입으로 컨버팅하는 것이고 tostring() 매크로는 루아 string 타입으로 컨버팅하는 것으로 보입니다. lua_tonumber() 함수와 lua_tostring() 함수는 Opcode.c 파일에 있으므로 이따가 읽을 것입니다.

#ifndef MAXSTACK
#define MAXSTACK 256
#endif
static Object stack[MAXSTACK] = { {T_MARK, NULL} };
static Object *top=stack+1, *base=stack+1;

루아 VM은 스택 기반입니다. 그래서 일단 스택 공간을 확보하고 시작합니다. 스택 크기는 Object 타입으로 256개입니다. 스택 맨 아래에는 {T_MARK, {NULL}}로 값을 넣고 시작합니다. 바닥을 표시하는 용도인가 봅니다. 그리고 스택 top 포인터와 base 포인터를 같은 값으로 지정합니다. 아직 스택에 아무 것도 없으니까요. 아마 스택에 값을 넣으면 top은 증가하겠지요.


/*
** Concatenate two given string, creating a mark space at the beginning.
** Return the new string pointer.
*/
static char *lua_strconc (char *l, char *r)
{
 char *s = calloc (strlen(l)+strlen(r)+2, sizeof(char));
 if (s == NULL)
 {
  lua_error ("not enough memory");
  return NULL;
 }
 *s++ = 0; 			/* create mark space */
 return strcat(strcpy(s,l),r);
}

주석에 설명이 잘 나와 있습니다. 문자열 두 개를 합치는 함수입니다. 맨 앞에 mark space가 뭐 하는 역할인지 모르겠군요. 어떤 명령어에 쓰이냐면

   case CONCOP:
   {
    Object *l = top-2;
    Object *r = top-1;
    if (tostring(r) || tostring(l))
     return 1;
    svalue(l) = lua_createstring (lua_strconc(svalue(l),svalue(r)));
    if (svalue(l) == NULL)
     return 1;
    --top;
   }
   break; 

CONCOP 명령어를 처리할 때 호출하는 함수입니다. CONCOP 명령어는 스택에서 두 개를 빼서 합치고 다시 스택에 넣습니다. 그래서 전체적으로 스택은 하나가 줄어드네요.

expr : 
    ... 중략 ...
    | expr1 CONC expr1 { code_byte(CONCOP);  $$ = 1; ntemp--;}
    ... 후략 ...

앞서 읽었던 Lua.stx에 expr 문법 중 CONC 토큰(점 두 개 연산자)을 파싱할 때 CONCOP 명령을 명령어 스택에 넣습니다. 그것을 처리하는 것이지요.

/*
** Duplicate a string,  creating a mark space at the beginning.
** Return the new string pointer.
*/
char *lua_strdup (char *l)
{
 char *s = calloc (strlen(l)+2, sizeof(char));
 if (s == NULL)
 {
  lua_error ("not enough memory");
  return NULL;
 }
 *s++ = 0; 			/* create mark space */
 return strcpy(s,l);
}

문자열 복사하는 함수입니다. 앞서와 마찬가지로 첫 바이트를 0으로 비워 놓는데, 왜 비우는건지 모르겠습니다. 코드 자체는 쉽습니다. lua_strdup() 함수는 여러 군데서 호출됩니다. 공통적으로 lua_createstring(lua_strdup(s)) 형태로 호출됩니다. lua_createstring() 함수는 Table.c를 읽을 때 읽었던 함수입니다. 문자열 테이블에서 검사해서 있으면 리턴하고 없으면 문자열 테이블에 새 문자열을 추가하는 함수지요.

/*
** Convert, if possible, to a number tag.
** Return 0 in success or not 0 on error.
*/ 
static int lua_tonumber (Object *obj)
{
 char *ptr;
 if (tag(obj) != T_STRING)
 {
  lua_reportbug ("unexpected type at conversion to number");
  return 1;
 }
 nvalue(obj) = strtod(svalue(obj), &ptr);
 if (*ptr)
 {
  lua_reportbug ("string to number convertion failed");
  return 2;
 }
 tag(obj) = T_NUMBER;
 return 0;
}

lua_tonumber() 함수는 Opcode.c 파일 시작에 나오는 tonumber() 매크로에서 호출하는 함수입니다. 이름은 tonumber면서 실제로는 string 타입만 number로 바꿀 수 있네요. 그러면 이름을 strtonum 뭐 이런식으로 지어야 하는 것 아닌가하는 생각이 듭니다.

/*
** Test if is possible to convert an object to a number one.
** If possible, return the converted object, otherwise return nil object.
*/ 
static Object *lua_convtonumber (Object *obj)
{
 static Object cvt;
 
 if (tag(obj) == T_NUMBER)
 {
  cvt = *obj;
  return &cvt;
 }
  
 tag(&cvt) = T_NIL;
 if (tag(obj) == T_STRING)
 {
  char *ptr;
  nvalue(&cvt) = strtod(svalue(obj), &ptr);
  if (*ptr == 0)
   tag(&cvt) = T_NUMBER;
 }
 return &cvt;
}

실질적으로 동작 자체는 lua_tonumber()와 동일합니다. 왜 같은 동작하는 함수를 또 만들었는지 모르겠습니다. 이 함수는 opcode 처리등에 관여하는 함수가 아니라, Table.c에 있는 tablebuffer 배열에 연결되 있습니다.

/*
** Convert, if possible, to a string tag
** Return 0 in success or not 0 on error.
*/ 
static int lua_tostring (Object *obj)
{
 static char s[256];
 if (tag(obj) != T_NUMBER)
 {
  lua_reportbug ("unexpected type at conversion to string");
  return 1;
 }
 if ((int) nvalue(obj) == nvalue(obj))
  sprintf (s, "%d", (int) nvalue(obj));
 else
  sprintf (s, "%g", nvalue(obj));
 svalue(obj) = lua_createstring(lua_strdup(s));
 if (svalue(obj) == NULL)
  return 1;
 tag(obj) = T_STRING;
 return 0;
}

lua_tostring() 함수도 lua_tonumber() 함수처럼 tostring() 매크로에서 호출합니다. (매크로에서 호출한다는 표현은 엄밀히 보면 잘못된 표현이긴합니다.) 이 함수 역시 루아 number 타입만 루아 string 타입으로 바꿉니다. 문자열로 바꿀 때 조금 전 읽었던 lua_createstring() 함수를 호출해서 문자열 테이블을 검사하네요.

이 다음에 나오는 함수는 lua_execute() 입니다. 쭉 훑어보면 뭐하는 함수인지 알 수 있습니다. 루아 VM 명령어 구현이 커다란 switch-case 구문으로 다 들어가 있는 긴 함수입니다. 이건 다음 글에서 읽는게 낫겠네요.

댓글남기기