관리 메뉴

Value Creator의 IT(프로그래밍 / 전자제품)

#11 [lua 1.1] opcode.h, opcode.c 파일 읽기 본문

1. 프로그래밍/2) LUA

#11 [lua 1.1] opcode.h, opcode.c 파일 읽기

valuecreatort 2019. 8. 27. 21:31
반응형

 

opcode는 루아 VM(Virtual Machine) 을 구현한 코드.

(스크롤 압박 주의

 

 

opcode.h

/*
** TeCGraf - PUC-Rio
** $Id: opcode.h,v 2.1 1994/04/20 22:07:57 celes Exp $
*/

#ifndef opcode_h
#define opcode_h

#ifndef STACKGAP
#define STACKGAP	128
#endif 

#ifndef real
#define real float
#endif

#define FIELDS_PER_FLUSH 40

typedef unsigned char  Byte;

typedef unsigned short Word;

typedef union
{
 struct {char c1; char c2;} m;
 Word w;
} CodeWord;

typedef union
{
 struct {char c1; char c2; char c3; char c4;} m;
 float f;
} CodeFloat;

typedef enum 
{ 
 PUSHNIL,
 PUSH0, PUSH1, PUSH2,
 PUSHBYTE,
 PUSHWORD,
 PUSHFLOAT,
 PUSHSTRING,
 PUSHLOCAL0, PUSHLOCAL1, PUSHLOCAL2, PUSHLOCAL3, PUSHLOCAL4,
 PUSHLOCAL5, PUSHLOCAL6, PUSHLOCAL7, PUSHLOCAL8, PUSHLOCAL9,
 PUSHLOCAL,
 PUSHGLOBAL,
 PUSHINDEXED,
 PUSHMARK,
 PUSHOBJECT,
 STORELOCAL0, STORELOCAL1, STORELOCAL2, STORELOCAL3, STORELOCAL4,
 STORELOCAL5, STORELOCAL6, STORELOCAL7, STORELOCAL8, STORELOCAL9,
 STORELOCAL,
 STOREGLOBAL,
 STOREINDEXED0,
 STOREINDEXED,
 STORELIST0,
 STORELIST,
 STORERECORD,
 ADJUST,
 CREATEARRAY,
 EQOP,
 LTOP,
 LEOP,
 ADDOP,
 SUBOP,
 MULTOP,
 DIVOP,
 CONCOP,
 MINUSOP,
 NOTOP,
 ONTJMP,
 ONFJMP,
 JMP,
 UPJMP,
 IFFJMP,
 IFFUPJMP,
 POP,
 CALLFUNC,
 RETCODE,
 HALT,
 SETFUNCTION,
 SETLINE,
 RESET
} OpCode;

typedef enum
{
 T_MARK,
 T_NIL,
 T_NUMBER,
 T_STRING,
 T_ARRAY,
 T_FUNCTION,
 T_CFUNCTION,
 T_USERDATA
} Type; 

typedef void (*Cfunction) (void);
typedef int  (*Input) (void);

typedef union
{
 Cfunction 	 f; //루아의 Cfunction
 real    	 n; //number
 char      	*s; //string
 Byte      	*b; //바이트 저장
 struct Hash    *a; //array
 void           *u; //Userdata
} Value;

typedef struct Object
{
 Type  tag;
 Value value;
} Object;

typedef struct
{
 char   *name;
 Object  object;
} Symbol;

/* Macros to access structure members */
#define tag(o)		((o)->tag)
#define nvalue(o)	((o)->value.n)
#define svalue(o)	((o)->value.s)
#define bvalue(o)	((o)->value.b)
#define avalue(o)	((o)->value.a)
#define fvalue(o)	((o)->value.f)
#define uvalue(o)	((o)->value.u)

/* Macros to access symbol table */
#define s_name(i)	(lua_table[i].name)
#define s_object(i)	(lua_table[i].object)
#define s_tag(i)	(tag(&s_object(i)))
#define s_nvalue(i)	(nvalue(&s_object(i)))
#define s_svalue(i)	(svalue(&s_object(i)))
#define s_bvalue(i)	(bvalue(&s_object(i)))
#define s_avalue(i)	(avalue(&s_object(i)))
#define s_fvalue(i)	(fvalue(&s_object(i)))
#define s_uvalue(i)	(uvalue(&s_object(i)))


//현재 바이트 코드값을 word로 가져가거나
//float로 가져가는 define 구문
#define get_word(code,pc)    {code.m.c1 = *pc++; code.m.c2 = *pc++;}
#define get_float(code,pc)   {code.m.c1 = *pc++; code.m.c2 = *pc++;\
                              code.m.c3 = *pc++; code.m.c4 = *pc++;}
 


/* Exported functions */
int     lua_execute   (Byte *pc);
void    lua_markstack (void);
char   *lua_strdup (char *l);

void    lua_setinput   (Input fn);	/* from "lex.c" module */
char   *lua_lasttext   (void);		/* from "lex.c" module */
int     lua_parse      (void); 		/* from "lua.stx" module */
void    lua_type       (void);
void 	lua_obj2number (void);
void 	lua_print      (void);
void 	lua_internaldofile (void);
void 	lua_internaldostring (void);
void    lua_travstack (void (*fn)(Object *));

#endif

 

 

 

 

opcode.c

/*
** opcode.c
** TecCGraf - PUC-Rio
*/

char *rcs_opcode="$Id: opcode.c,v 2.1 1994/04/20 22:07:57 celes Exp $";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* stdlib.h does not have this in SunOS */
extern double strtod(const char *, char **);

#include "mm.h"

#include "opcode.h"
#include "hash.h"
#include "inout.h"
#include "table.h"
#include "lua.h"


//tonumber() 매크로는 루아 number 타입으로 컨버팅하는 것이고 
//tostring() 매크로는 루아 string 타입으로 컨버팅하는 것으로 보입니다.
//lua_tonumber() 함수와 lua_tostring() 함수는 Opcode.c 파일에 있음.
#define tonumber(o) ((tag(o) != T_NUMBER) && (lua_tonumber(o) != 0))
#define tostring(o) ((tag(o) != T_STRING) && (lua_tostring(o) != 0))




//루아 VM은 스택 기반입니다. 그래서 일단 스택 공간을 확보하고 시작합니다.
//스택 크기는 Object 타입으로 256개입니다. 
#ifndef MAXSTACK
#define MAXSTACK 256
#endif
static Object stack[MAXSTACK] = {{T_MARK, {NULL}}}; //스택 맨 아래에는 {T_MARK, {NULL}}로 값을 넣고 시작합니다. 바닥을 표시하는 용도인가 봅니다.
static Object *top=stack+1, *base=stack+1; //그리고 스택 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);
}
/*****아래 참조
   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.
*/
//문자열 복사하는 함수입니다. 
//앞서와 마찬가지로 첫 바이트를 0으로 비워 놓습니다.
//lua_strdup() 함수는 여러 군데서 호출됩니다. 
//공통적으로 lua_createstring(lua_strdup(s)) 형태로 호출됩니다. 
//lua_createstring() 함수는 Table.c를 읽을 때 읽었던 함수입니다.
//문자열 테이블에서 검사해서 있으면 리턴하고 없으면 문자열 테이블에 새 문자열을 추가하는 함수지요.

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);
}

/*
** Convert, if possible, to a number tag.
** Return 0 in success or not 0 on error.
*/ 
//lua_tonumber() 함수는 Opcode.c 파일 시작에 나오는 tonumber() 매크로에서 호출하는 함수입니다.
//이름은 tonumber면서 실제로는 string 타입만 number로 바꿀 수 있네요. 
//from string to number
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;
}

/*
** Test if is possible to convert an object to a number one.
** If possible, return the converted object, otherwise return nil object.
*/ 
//실질적으로 동작 자체는 lua_tonumber()와 동일합니다. 
//이 함수는 opcode 처리등에 관여하는 함수가 아니라, Table.c에 있는 tablebuffer 배열에 연결되어 있습니다.
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;
}



/*
** Convert, if possible, to a string tag
** Return 0 in success or not 0 on error.
*/ 
//lua_tostring() 함수도 lua_tonumber() 함수처럼 tostring() 매크로에서 호출합니다.
//이 함수 역시 루아 number 타입만 루아 string 타입으로 바꿉니다.
//문자열로 바꿀 때 조금 전 읽었던 lua_createstring() 함수를 호출해서 문자열 테이블을 검사하네요.
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;
}


/*
** Execute the given opcode. Return 0 in success or 1 on error.
*/
//루아 VM 명령어 구현이 커다란 switch-case 구문으로 들어가 있는 함수.
//lua_execute() 함수가 루아 VM 구현 자체인것으로 보입니다.
int lua_execute (Byte *pc)
{
 Object *oldbase = base;
 base = top;
 while (1)
 {
  OpCode opcode;
  switch (opcode = (OpCode)*pc++)
  //시작 코드입니다. pc 배열은 Lua.stx에서 yacc 문법을 파싱하면서 만든 바이트 스택입니다.
  //명령어와 데이터가 코드 문법 파싱 순서와 규칙에 따라 쭉 들어가 있지요. 
  //이것을 하나씩 읽으면서 동작을 처리하고 그 결과를 Object 스택에 넣는 방식으로 루아 VM이 동작하는가 봅니다.
  {
  //PUSH 시리즈 명령어 처리 부분입니다.
  //PUSHNIL, PUSH0, PUSH1, PUSH2는 명령어 자체에 값을 스택에 넣으라고 지시하고 있습니다. 그래서 pc를 변경하지 않고 바로 스택에 nil, 0, 1, 2를 넣습니다.
   case PUSHNIL: tag(top++) = T_NIL; break;
   
   case PUSH0: tag(top) = T_NUMBER; nvalue(top++) = 0; break;
   case PUSH1: tag(top) = T_NUMBER; nvalue(top++) = 1; break;
   case PUSH2: tag(top) = T_NUMBER; nvalue(top++) = 2; break;

   //PUSHBYTE는 명령어를 처리하는데 2 바이트가 필요합니다.
   //명령어 자체와 스택에 넣을 값, 이렇게 2 바이트가 필요하죠. 
   //명령어 자체는 루프 돌면서 switch 문에서 처리했고 case 문에서는 데이터를 바이트 스택에서 빼야 합니다. 
   //그래서 nvalue(top++) = *pc++로 pc를 하나 증가하는 코드를 코딩한 것입니다.
   case PUSHBYTE: tag(top) = T_NUMBER; nvalue(top++) = *pc++; break;
   
   //PUSHWORD 명령어는 opcode 자체를 제외하고 2 바이트가 더 필요합니다. 그러므로 pc를 두 번 증가시켜야 하지요.
   case PUSHWORD: 
   {
    CodeWord code;
    get_word(code,pc); //get_word() 매크로 구현을 보면 pc가 두 번 증가합니다.
    tag(top) = T_NUMBER; nvalue(top++) = code.w;
   }
   break;
   
   //PUSHFLOAT는 4바이트이므로 opcode 자체를 제외하고 4바이트가 더 필요합니다. 그러므로 pc를 네 번 증가시켜야 합니다.   
   case PUSHFLOAT:
   {
    CodeFloat code;
    get_float(code,pc); //case 구문에서 호출하는 get_float() 매크로를 보면 pc가 네 번 증가하는 코드가 있습니다.
    tag(top) = T_NUMBER; nvalue(top++) = code.f;
   }
   break;
   
   /***아래 참조
   #define get_float(code,pc)   {code.m.c1 = *pc++; code.m.c2 = *pc++;\
                              code.m.c3 = *pc++; code.m.c4 = *pc++;}
   ***********/
   
   //루아 문법을 파싱할 때 문자열은 문자열 테이블에 저장하고 바이트 스택엔 문자열 테이블 인덱스를 저장합니다.
   //Lex.c 파일에서 STRING 토큰을 처리하는 코드를 다시 보면 알 수 있습니다.
   //그러므로 바이트 스택에서 2바이트 word 크기 데이터를 읽어서 그 값을 인덱스로 lua_constant 배열에서 문자열 포인터를 받아 옵니다.
   //이 포인터 값이 Object 스택에 루아 string 타입으로 저장됩니다.
   case PUSHSTRING:
   {
    CodeWord code;
    get_word(code,pc);
    tag(top) = T_STRING; svalue(top++) = lua_constant[code.w];
   }
   break;
   

   //PUSHLOCAL 계열 명령어를 처리하는 코드입니다. 
   //스택 베이스 포인터 위치를 기준으로 N 칸 (단위가 Object 자료형 크기라서 ‘칸’이라는 표현이 제일 적당해 보입니다.) 위에 있는 값을 읽어서 스택에 넣는 동작을 합니다. 
   //이 동작과 PUSHLOCAL이라는 이름이 머릿속에서 잘 어울리지 않네요.
   //PUSHLOCAL은 스택에 있는 값을 읽어서 다시 스택에 넣는 명령.
   //PUSHGLOBAL은 심볼 테이블에 있는 심볼값을 읽어서 스택에 넣는 명령.
   case PUSHLOCAL0: case PUSHLOCAL1: case PUSHLOCAL2:
   case PUSHLOCAL3: case PUSHLOCAL4: case PUSHLOCAL5:
   case PUSHLOCAL6: case PUSHLOCAL7: case PUSHLOCAL8:
   case PUSHLOCAL9: *top++ = *(base + (int)(opcode-PUSHLOCAL0)); break;
   
   case PUSHLOCAL: *top++ = *(base + (*pc++)); break;
   
   //그 다음에는 PUSHGLOBAL입니다. 이름이 PUSHLOCAL과 반대네요. 
   case PUSHGLOBAL: 
   {
    CodeWord code;
    get_word(code,pc);
    *top++ = s_object(code.w);
   }
   break;
   
   /*****아래 참조
   get_word() 매크로는 앞에서 읽었던 매크로입니다. s_object() 매크로 구현을 보죠.
   #define s_object(i)	(lua_table[i].object)
   get_word() 매크로로 바이트 코드 스택에서 2바이트 값을 읽습니다.
   이 값은 lua_table 배열의 인덱스로 들어가서 lua_table의 object 맴버 값을 읽습니다.
   Object 객체의 포인터 값이겠죠.
   종합해보면 PUSHLOCAL은 스택에 있는 값을 읽어서 다시 스택에 넣는 것이고 PUSHGLOBAL은 심볼 테이블에 있는 심볼값을 읽어서 스택에 넣는 명령입니다. 
   ************/
   
   //PUSHINDEXED 명령어는 동작 추적이 쉽지 않습니다. 사용되는 곳은 lua_pushvar() 함수 한 곳입니다.
   
   case PUSHINDEXED:
    --top;
    if (tag(top-1) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-1), top);
     if (h == NULL) return 1;
     *(top-1) = *h;
    }
   break;
   
   /*****아래 참조
   static void lua_pushvar (long number)
    { 
     if (number > 0)	/* global var */
     {
      code_byte(PUSHGLOBAL);
      code_word(number-1);
      incr_ntemp();
     }
     else if (number < 0)	/* local var */
     {
      number = (-number) - 1;
      if (number < 10) code_byte(PUSHLOCAL0 + number);
      else
      {
       code_byte(PUSHLOCAL);
       code_byte(number);
      }
      incr_ntemp();
     }
     else
     {
      code_byte(PUSHINDEXED);
      ntemp--;
     }
    }
    number가 0일 때만 PUSHINDEXED 명령어를 바이트 코드 스택에 넣거든요. 
    그런데 문법 코드만 봐서는 number가 0이 되는 경우가 어떤 상황인지 알 수가 없습니다.    
   ************/  
   
   case PUSHMARK: tag(top++) = T_MARK; break;
   
   case PUSHOBJECT: *top = *(top-3); top++; break;
   
   	
   //위에 있는 값들을 STACK에서 뺀 다음
   //STORELOCAL은 STACK에 넣기.
   //STOREGLOBAL은 STACK의 값을 심볼테이블에 있는 심볼값에 넣기.    
   //STORELOCAL 시리즈 명령어와 STOREGLOBAL 명령어는 PUSHLOCAL/PUSHGLOBAL 명령어와 동작이 반대입니다.
   case STORELOCAL0: case STORELOCAL1: case STORELOCAL2:
   case STORELOCAL3: case STORELOCAL4: case STORELOCAL5:
   case STORELOCAL6: case STORELOCAL7: case STORELOCAL8:
   case STORELOCAL9: *(base + (int)(opcode-STORELOCAL0)) = *(--top); break;
    
   case STORELOCAL: *(base + (*pc++)) = *(--top); break;
   
   case STOREGLOBAL:
   {
    CodeWord code;
    get_word(code,pc);
    s_object(code.w) = *(--top);
   }
   break;



   //INDEXED라고 붙은 명령어들은 루아 테이블 타입 변수를 다루는 명령어 인것 같습니다.
   //루아 테이블 타입 변수의 인덱스를 받아서 값을 읽거나 쓰는 동작
   case STOREINDEXED0:
    if (tag(top-3) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-3), top-2);
     if (h == NULL) return 1;
     *h = *(top-1);
    }
    top -= 3;
   break;
   
   case STOREINDEXED:
   {
    int n = *pc++;
    if (tag(top-3-n) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-3-n), top-2-n);
     if (h == NULL) return 1;
     *h = *(top-1);
    }
    top--;
   }
   break;
   
   case STORELIST0:
   case STORELIST:
   {
    int m, n;
    Object *arr;
    if (opcode == STORELIST0) m = 0;
    else m = *(pc++) * FIELDS_PER_FLUSH;
    n = *(pc++);
    arr = top-n-1;
    if (tag(arr) != T_ARRAY)
    {
     lua_reportbug ("internal error - table expected");
     return 1;
    }
    while (n)
    {
     tag(top) = T_NUMBER; nvalue(top) = n+m;
     *(lua_hashdefine (avalue(arr), top)) = *(top-1);
     top--;
     n--;
    }
   }
   break;
   
   case STORERECORD:
   {
    int n = *(pc++);
    Object *arr = top-n-1;
    if (tag(arr) != T_ARRAY)
    {
     lua_reportbug ("internal error - table expected");
     return 1;
    }
    while (n)
    {
     CodeWord code;
     get_word(code,pc);
     tag(top) = T_STRING; svalue(top) = lua_constant[code.w];
     *(lua_hashdefine (avalue(arr), top)) = *(top-1);
     top--;
     n--;
    }
   }
   break;
         
   //STACK의 top을 조정.
   //newtop을 base에서 N만큼 더한 위치.
   //base는 STACK 바닥이므로 top을 newtop으로 바꾼다는 말은 STACK 꼭대기를 아래로 내린다는 뜻.   
   //코드만 보면 스택 top을 조정하는 명령어군요. 그런데 코드 내용을 보면 newtop을 base에서 N만큼 더한 위치로 잡습니다.
   //base는 스택 바닥이므로 top을 newtop으로 바꾼다는 말은 스택 꼭대기를 아래로 내린다는 뜻
   case ADJUST:
   {
    Object *newtop = base + *(pc++);
    while (top < newtop) tag(top++) = T_NIL;
    top = newtop;  /* top could be bigger than newtop */
   }
   break;
   
   
   //명령어 이름이 CREATEARRAY이니, 배열을 만드는 명령어라는걸 미루어 짐작할 수 있습니다.
   //스택에서는 배열 크기를 받아 오는 듯 합니다. 
   //배열 크기를 받아야 하는데 그 값이 nil이면 배열을 만들 수 없는데 101을 넣습니다.
   //101을 넣은 채로 lua_createarray() 함수를 호출하는 것 보니 101은 에러 코드 같은 것이 아니라 기본값으로 지정하는 배열 크기인듯 합니다.   
   case CREATEARRAY:
    if (tag(top-1) == T_NIL) 
     nvalue(top-1) = 101;
    else 
    {
     if (tonumber(top-1)) return 1;
     if (nvalue(top-1) <= 0) nvalue(top-1) = 101;
    }
    avalue(top-1) = lua_createarray(nvalue(top-1));
    if (avalue(top-1) == NULL)
     return 1;
    tag(top-1) = T_ARRAY;
   break;
   
   //EQOP 명령어는 “같다” 연산자를 처리하는 명령어입니다. 
   //일단 비교하는 두 값의 타입이 다르면 비교를 안하네요. 
   //타입이 같으면 문자열을 제외하고 나머지는 C 언어 기준으로 같음을 비교합니다.
   //즉, 루아 입장에서는 레퍼런스의 값이 같아야 같은 것으로 처리됩니다.
   //예를 들어 루아 배열 같은 경우, 배열에 소속된 값이 모두 같더라도 두 배열이 서로 다른 레퍼런스라면 (두 배열이 서로 다른 메모리 위치에 할당되 있다면) 다른 배열로 취급한다는 겁니다. 
   //문자열은 문자열 내용을 비교합니다.
   //같으면 1과 TYPE을 설정한다.
   //다른 경우 Type = T_NIL로 설정한다.
   
   case EQOP:
   {
    Object *l = top-2;
    Object *r = top-1;
    --top;
    if (tag(l) != tag(r)) 
     tag(top-1) = T_NIL;
    else
    {
     switch (tag(l))
     {
      case T_NIL:       tag(top-1) = T_NUMBER; break;
      case T_NUMBER:    tag(top-1) = (nvalue(l) == nvalue(r)) ? T_NUMBER : T_NIL; break;
      case T_ARRAY:     tag(top-1) = (avalue(l) == avalue(r)) ? T_NUMBER : T_NIL; break;
      case T_FUNCTION:  tag(top-1) = (bvalue(l) == bvalue(r)) ? T_NUMBER : T_NIL; break;
      case T_CFUNCTION: tag(top-1) = (fvalue(l) == fvalue(r)) ? T_NUMBER : T_NIL; break;
      case T_USERDATA:  tag(top-1) = (uvalue(l) == uvalue(r)) ? T_NUMBER : T_NIL; break;
      case T_STRING:    tag(top-1) = (strcmp (svalue(l), svalue(r)) == 0) ? T_NUMBER : T_NIL; break;
      case T_MARK:      return 1;
     }
    }
    nvalue(top-1) = 1;
   }
   break;


   

   //LTOP와 LEOP 명령어 처리 코드는 거의 같습니다.
   //“작다”와 “작거나 같다”를 처리하는 명령어 구현이라 그렇습니다. 연산자 말고는 다를게 없지요.   
   //일단 number 타입은 바로 비교해서 결과를 스택에 넣습니다. number 타입이 아닐 때는 문자열로 변경하는데, 문자열로 변경을 실패하면 비교를 안하네요. 
   
   case LTOP:
   {
    Object *l = top-2;
    Object *r = top-1;
    --top;
    if (tag(l) == T_NUMBER && tag(r) == T_NUMBER)
     tag(top-1) = (nvalue(l) < nvalue(r)) ? T_NUMBER : T_NIL;
    else
    {
     if (tostring(l) || tostring(r))
      return 1;
     tag(top-1) = (strcmp (svalue(l), svalue(r)) < 0) ? T_NUMBER : T_NIL;
    }
    nvalue(top-1) = 1; 
   }
   break;
   
   case LEOP:
   {
    Object *l = top-2;
    Object *r = top-1;
    --top;
    if (tag(l) == T_NUMBER && tag(r) == T_NUMBER)
     tag(top-1) = (nvalue(l) <= nvalue(r)) ? T_NUMBER : T_NIL;
    else
    {
     if (tostring(l) || tostring(r))
      return 1;
     tag(top-1) = (strcmp (svalue(l), svalue(r)) <= 0) ? T_NUMBER : T_NIL;
    }
    nvalue(top-1) = 1; 
   }
   break;
   
   //ADD, SUB, MULTIPLE, DIVIDE 사칙연산 구현.
   
   //STACK의 두 값을 더해서 STACK(top-1)에 넣는다.
   case ADDOP:
   {
    Object *l = top-2;
    Object *r = top-1;
    if (tonumber(r) || tonumber(l))
     return 1;
    nvalue(l) += nvalue(r);
    --top;
   }
   break; 
   
   //STACK의 두 값을 빼서 STACK(top-1)에 넣는다.
   case SUBOP:
   {
    Object *l = top-2;
    Object *r = top-1;
    if (tonumber(r) || tonumber(l))
     return 1;
    nvalue(l) -= nvalue(r);
    --top;
   }
   break; 
   
   //STACK의 두 값을 곱해서 STACK(top-1)에 넣는다.
   case MULTOP:
   {
    Object *l = top-2;
    Object *r = top-1;
    if (tonumber(r) || tonumber(l))
     return 1;
    nvalue(l) *= nvalue(r);
    --top;
   }
   break; 
   
   //STACK의 두 값을 나눠서 STACK(top-1)에 넣는다.
   //0으로 나뉘는 것은 고려 안하나?
   case DIVOP:
   {
    Object *l = top-2;
    Object *r = top-1;
    if (tonumber(r) || tonumber(l))
     return 1;
    nvalue(l) /= nvalue(r);
    --top;
   }
   break; 
   
   //문자열 두 개 붙이는 구현.
   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; 
   
   
   //숫자나 변수 앞에 마이너스(-)를 붙이면 역수로 바꿉니다.
   //역시 기본적인 수학 연산자 구현입니다. 
   case MINUSOP:
    if (tonumber(top-1))
     return 1;
    nvalue(top-1) = - nvalue(top-1);
   break; 
   
   //NOTOP는 코드 구현만 보면 스택 꼭대기에 있는 값이 nil이면 number로 바꾸고 아니면 nil로 바꾸는 동작을 합니다.
   //동작을 보니 C언어의 not 연산자(~ 연산자)는 아닌 것 같네요.
   //그렇다면 흔히 nonop라고 많이 부르는 아무일 안하는 연산자로 볼 수도 있는데 그러기엔 타입 변경을 하네요.
   case NOTOP:
    tag(top-1) = tag(top-1) == T_NIL ? T_NUMBER : T_NIL;
   break; 
   
   // ONTJMP는 스택 꼭대기 값이 nil이 아닐 때 점프합니다.
   case ONTJMP:
   {
    CodeWord code;
    get_word(code,pc);
    if (tag(top-1) != T_NIL) pc += code.w;
   }
   break;
   
   // ONFJMP는 스택 꼭대기 값이 nil일 때 점프합니다.
   case ONFJMP:	   
   {
    CodeWord code;
    get_word(code,pc);
    if (tag(top-1) == T_NIL) pc += code.w;
   }
   break;
   
   //JMP는 무조건 점프합니다.
   case JMP:
   {
    CodeWord code;
    get_word(code,pc);
    pc += code.w;
   }
   break;
    
   // UPJMP는 바이트 코드 스택을 거슬러서 (역방향) 점프합니다. 
   case UPJMP:
   {
    CodeWord code;
    get_word(code,pc);
    pc -= code.w;
   }
   break;
   
   //IFFJMP는 기본적으로 ONFJMP와 같은 동작을 하지만 스택 top 인덱스를 하나 내립니다.
   case IFFJMP:
   {
    CodeWord code;
    get_word(code,pc);
    top--;
    if (tag(top) == T_NIL) pc += code.w;
   }
   break;

   // IFFUPJMP는 스택 top 인덱스를 하나 내리면서 역방향으로 점프합니다. 
   case IFFUPJMP:
   {
    CodeWord code;
    get_word(code,pc);
    top--;
    if (tag(top) == T_NIL) pc -= code.w;
   }
   break;

   case POP: --top; break; //그냥 스택 top을 하나 내립니다.
   
   
   
   
   /************아래 참조
   명령어 이름이 동작을 설명한다.
   CALLFUNC는 함수를 호출하는 명령어입니다. 크게 두 부분으로 나눴습니다.
   하나는 루아 함수 호출이고 다른 하나는 C 함수 호출입니다.
   루아 함수를 호출하면 스택에서 Object 타입 인스턴스를 하나 꺼냅니다. 
   이 인스턴스는 함수 자체의 바이트 코드 스택 위치를 포인터로 가지고 있지요.
   이 포인터에서 함수 바이트 코드를 가져와서 현재 프로그램 카운터를 옮깁니다. 
   C 함수 호출 할 때는 일단 함수 포인터로 바로 점프해서 C 함수를 내부적으로 호출하는 코드가 보입니다.
   그리고 C 함수에서 보낸 리턴값을 스택에서 정리하는 코드가 나옵니다. 
   *******************/
   
   case CALLFUNC:
   {
    Byte *newpc;
    Object *b = top-1;
    while (tag(b) != T_MARK) b--;
    if (tag(b-1) == T_FUNCTION)
    {
     lua_debugline = 0;			/* always reset debug flag */
     newpc = bvalue(b-1);
     bvalue(b-1) = pc;		        /* store return code */
     nvalue(b) = (base-stack);		/* store base value */
     base = b+1;
     pc = newpc;
     if (MAXSTACK-(base-stack) < STACKGAP)
     {
      lua_error ("stack overflow");
      return 1;
     }
    }
    else if (tag(b-1) == T_CFUNCTION)
    {
     int nparam; 
     lua_debugline = 0;			/* always reset debug flag */
     nvalue(b) = (base-stack);		/* store base value */
     base = b+1;
     nparam = top-base;			/* number of parameters */
     (fvalue(b-1))();			/* call C function */
     
     /* shift returned values */
     { 
      int i;
      int nretval = top - base - nparam;
      top = base - 2;
      base = stack + (int) nvalue(base-1);
      for (i=0; i<nretval; i++)
      {
       *top = *(top+nparam+2);
       ++top;
      }
     }
    }
    else
    {
     lua_reportbug ("call expression not a function");
     return 1;
    }
   }
   break;
   
   
   
   //RETCODE는 함수 리턴값을 처리하는 명령어 구현입니다.
   //루아는 값을 여러개 리턴할 수 있습니다.(다중 리턴 가능. 즉, 다중입력된 값에 다중출력이 가능하다.)
   //f(a,b) -> return c,d => a에 c가 들어가고, b에 d가 들어간다.
   //그래서 for 문을 돌면서 스택에 리턴값을 쌓는 코드가 보입니다.
   //바이트 코드와 스택 사이에서 리턴값을 어떻게 핸들링하는지는 바로 파악이 안됩니다.
   //HALT는 말 그대로 그냥 종료입니다.
   case RETCODE:
   {
    int i;
    int shift = *pc++;
    int nretval = top - base - shift;
    top = base - 2;
    pc = bvalue(base-2);
    base = stack + (int) nvalue(base-1);
    for (i=0; i<nretval; i++) //여러개의 리턴 for문을 돌면서 스택에 리턴값을 쌓는다.
    {
     *top = *(top+shift+2);
     ++top;
    }
   }
   break;
   
   case HALT:
    base = oldbase;
   return 0;		/* success */
   
   
   //SETFUNCTION는 루아 디버깅 정보 출력용 명령어입니다.
   //함수를 선언할 때 함수 정보를 루아 디버깅 정보 기록용 배열에 넣습니다.
   case SETFUNCTION:
   {
    CodeWord file, func;
    get_word(file,pc);
    get_word(func,pc);
    if (lua_pushfunction (file.w, func.w))
     return 1;
   }
   break;

   //SETLINE 명령어도 마찬가지로 디버깅 정보 출력용입니다.   
   case SETLINE:
   {
    CodeWord code;
    get_word(code,pc);
    lua_debugline = code.w;
   }
   break;
   
   //RESET도 디버깅 정보 처리용입니다. 함수 동작이 끝날 때 디버깅 정보 배열에서 마지막 함수 정보를 제거합니다.
   case RESET:
    lua_popfunction ();
   break;
   
   default:
    lua_error ("internal error - opcode didn't match");
   return 1;
  }
 }
}


/*
** Traverse all objects on stack
*/
//Opcode.c 파일은 크게 두 부분으로 나뉩니다. 
//전반부는 루아 VM 구현이고 후반부는 루아 외부 API 구현입니다.
//호스트 프로그램에서 Lua.h를 통해 호출하는 루아 API들을 구현한 것이죠. 

//가비지 컬랙션을 할 때 lua_pack() 함수에서 부르는 함수입니다.
//스택에 있는 Object 인스턴스들에 대해 마킹하는데 동원됩니다. 
//확장하면 여러 용도로 쓸 수 있겠으나 루아 1.1에서는 일단 마킹하는데만 씁니다.
void lua_travstack (void (*fn)(Object *))
{
 Object *o;
 for (o = top-1; o >= stack; o--)
  fn (o);
}

/*
** Open file, generate opcode and execute global statement. Return 0 on
** success or 1 on error.
*/

//아마 호스트 프로그램에서 루아 코드를 돌리고 싶을때 호출하는 게이트웨이 함수가 아닐까 싶습니다. 
//루아 파일을 읽어서 루아를 돌릴 땐 lua_dofile() 함수를 호출하고 호스트 프로그램에서 문자열로 루아 코드를 넘겨서 실행합니다.
int lua_dofile (char *filename)
{
 if (lua_openfile (filename)) return 1;
 if (lua_parse ()) { lua_closefile (); return 1; }
 lua_closefile ();
 return 0;
}

/*
** Generate opcode stored on string and execute global statement. Return 0 on
** success or 1 on error.
*/
int lua_dostring (char *string)
{
 if (lua_openstring (string)) return 1;
 if (lua_parse ()) return 1;
 lua_closestring();
 return 0;
}

/*
** Execute the given function. Return 0 on success or 1 on error.
*/
//루아 함수를 별도로 실행할 때 호출하는 함수입니다.
//파라메터로 넘어오는 함수 이름으로 심볼 테이블에서 함수 객체를 가져옵니다.
//파라메터 개수만큼 스택을 조정하고 스택에 함수 객체를 넣습니다.
//그리고 VM에서는 CALLFUNC 명령어를 실행합니다.
int lua_call (char *functionname, int nparam)
{
 static Byte startcode[] = {CALLFUNC, HALT};
 int i; 
 Object func = s_object(lua_findsymbol(functionname));
 if (tag(&func) != T_FUNCTION) return 1;
 for (i=1; i<=nparam; i++)
  *(top-i+2) = *(top-i);
 top += 2;
 tag(top-nparam-1) = T_MARK;
 *(top-nparam-2) = func;
 return (lua_execute (startcode));
}

/*
** Get a parameter, returning the object handle or NULL on error.
** 'number' must be 1 to get the first parameter.
*/
//코드 패턴이 모두 같아서 한 번에 읽어보겠습니다.
//함수 이름도 getxxx()로 아마 호스트 프로그램에서 루아 오브젝트 객체에서 필요한 자료형 값을 받는 함수입니다.
Object *lua_getparam (int number)
{
 if (number <= 0 || number > top-base) return NULL;
 return (base+number-1);
}

/*
** Given an object handle, return its number value. On error, return 0.0.
*/
real lua_getnumber (Object *object)
{
 if (object == NULL || tag(object) == T_NIL) return 0.0;
 if (tonumber (object)) return 0.0;
 else                   return (nvalue(object));
}

/*
** Given an object handle, return its string pointer. On error, return NULL.
*/
char *lua_getstring (Object *object)
{
 if (object == NULL || tag(object) == T_NIL) return NULL;
 if (tostring (object)) return NULL;
 else                   return (svalue(object));
}

/*
** Given an object handle, return a copy of its string. On error, return NULL.
*/
char *lua_copystring (Object *object)
{
 if (object == NULL || tag(object) == T_NIL) return NULL;
 if (tostring (object)) return NULL;
 else                   return (strdup(svalue(object)));
}

/*
** Given an object handle, return its cfuntion pointer. On error, return NULL.
*/
lua_CFunction lua_getcfunction (Object *object)
{
 if (object == NULL) return NULL;
 if (tag(object) != T_CFUNCTION) return NULL;
 else                            return (fvalue(object));
}

/*
** Given an object handle, return its user data. On error, return NULL.
*/
void *lua_getuserdata (Object *object)
{
 if (object == NULL) return NULL;
 if (tag(object) != T_USERDATA) return NULL;
 else                           return (uvalue(object));
}

/*
** Given an object handle and a field name, return its field object.
** On error, return NULL.
*/
Object *lua_getfield (Object *object, char *field)
{
 if (object == NULL) return NULL;
 if (tag(object) != T_ARRAY)
  return NULL;
 else
 {
  Object ref;
  tag(&ref) = T_STRING;
  svalue(&ref) = lua_createstring(lua_strdup(field));
  return (lua_hashdefine(avalue(object), &ref));
 }
}

/*
** Given an object handle and an index, return its indexed object.
** On error, return NULL.
*/

//lua_getindexed() 함수와 lua_getglobal() 함수도 getxxx 계열 함수입니다만 코드 패턴이 조금 다릅니다.
//lua_getindexed() 함수는 루아 오브젝트 인스턴스가 배열일 경우에 index 위치의 아이템 오브젝트 인스턴스를 찾아서 포인터를 리턴합니다. 
//lua_getglobal() 함수는 심볼 이름으로 심볼 테이블에서 오브젝트 인스턴스 포인터를 받아서 리턴합니다.
Object *lua_getindexed (Object *object, float index)
{
 if (object == NULL) return NULL;
 if (tag(object) != T_ARRAY)
  return NULL;
 else
 {
  Object ref;
  tag(&ref) = T_NUMBER;
  nvalue(&ref) = index;
  return (lua_hashdefine(avalue(object), &ref));
 }
}

/*
** Get a global object. Return the object handle or NULL on error.
*/
Object *lua_getglobal (char *name)
{
 int n = lua_findsymbol(name);
 if (n < 0) return NULL;
 return &s_object(n);
}

/*
** Pop and return an object
*/
//lua_pop() 함수는 스택 팝하는 함수입니다.
//나머지 push 계열 함수들은 스택에 필요한 값을 넣는 함수입니다.
//호스트 프로그램에서 루아 VM 스택에 값을 직접 넣을 수 있도록 제공하는 API인가봅니다.
Object *lua_pop (void)
{
 if (top <= base) return NULL;
 top--;
 return top;
}

/*
** Push a nil object
*/
int lua_pushnil (void)
{
 if ((top-stack) >= MAXSTACK-1)
 {
  lua_error ("stack overflow");
  return 1;
 }
 tag(top) = T_NIL;
 return 0;
}

/*
** Push an object (tag=number) to stack. Return 0 on success or 1 on error.
*/
int lua_pushnumber (real n)
{
 if ((top-stack) >= MAXSTACK-1)
 {
  lua_error ("stack overflow");
  return 1;
 }
 tag(top) = T_NUMBER; nvalue(top++) = n;
 return 0;
}

/*
** Push an object (tag=string) to stack. Return 0 on success or 1 on error.
*/
int lua_pushstring (char *s)
{
 if ((top-stack) >= MAXSTACK-1)
 {
  lua_error ("stack overflow");
  return 1;
 }
 tag(top) = T_STRING; 
 svalue(top++) = lua_createstring(lua_strdup(s));
 return 0;
}

/*
** Push an object (tag=cfunction) to stack. Return 0 on success or 1 on error.
*/
int lua_pushcfunction (lua_CFunction fn)
{
 if ((top-stack) >= MAXSTACK-1)
 {
  lua_error ("stack overflow");
  return 1;
 }
 tag(top) = T_CFUNCTION; fvalue(top++) = fn;
 return 0;
}

/*
** Push an object (tag=userdata) to stack. Return 0 on success or 1 on error.
*/
int lua_pushuserdata (void *u)
{
 if ((top-stack) >= MAXSTACK-1)
 {
  lua_error ("stack overflow");
  return 1;
 }
 tag(top) = T_USERDATA; uvalue(top++) = u;
 return 0;
}

/*
** Push an object to stack.
*/
int lua_pushobject (Object *o)
{
 if ((top-stack) >= MAXSTACK-1)
 {
  lua_error ("stack overflow");
  return 1;
 }
 *top++ = *o;
 return 0;
}

/*
** Store top of the stack at a global variable array field. 
** Return 1 on error, 0 on success.
*/
//store 계열 함수는 스택에서 값을 읽는 함수입니다. 스택 자체도 조정하네요.
int lua_storeglobal (char *name)
{
 int n = lua_findsymbol (name);
 if (n < 0) return 1;
 if (tag(top-1) == T_MARK) return 1;
 s_object(n) = *(--top);
 return 0;
}

/*
** Store top of the stack at an array field. Return 1 on error, 0 on success.
*/
int lua_storefield (lua_Object object, char *field)
{
 if (tag(object) != T_ARRAY)
  return 1;
 else
 {
  Object ref, *h;
  tag(&ref) = T_STRING;
  svalue(&ref) = lua_createstring(lua_strdup(field));
  h = lua_hashdefine(avalue(object), &ref);
  if (h == NULL) return 1;
  if (tag(top-1) == T_MARK) return 1;
  *h = *(--top);
 }
 return 0;
}


/*
** Store top of the stack at an array index. Return 1 on error, 0 on success.
*/
int lua_storeindexed (lua_Object object, float index)
{
 if (tag(object) != T_ARRAY)
  return 1;
 else
 {
  Object ref, *h;
  tag(&ref) = T_NUMBER;
  nvalue(&ref) = index;
  h = lua_hashdefine(avalue(object), &ref);
  if (h == NULL) return 1;
  if (tag(top-1) == T_MARK) return 1;
  *h = *(--top);
 }
 return 0;
}


/*
** Given an object handle, return if it is nil.
*/
//is계열 함수는 파라메터로 전달한 오브젝트 인스턴스의 루아 자료형을 판단합니다.
//역시 호스트 프로그램에서 루아 객체 포인터가 어떤 타입인지 알고 싶을 때 쓰는 API 함수입니다.
int lua_isnil (Object *object)
{
 return (object != NULL && tag(object) == T_NIL);
}

/*
** Given an object handle, return if it is a number one.
*/
int lua_isnumber (Object *object)
{
 return (object != NULL && tag(object) == T_NUMBER);
}

/*
** Given an object handle, return if it is a string one.
*/
int lua_isstring (Object *object)
{
 return (object != NULL && tag(object) == T_STRING);
}

/*
** Given an object handle, return if it is an array one.
*/
int lua_istable (Object *object)
{
 return (object != NULL && tag(object) == T_ARRAY);
}

/*
** Given an object handle, return if it is a cfunction one.
*/
int lua_iscfunction (Object *object)
{
 return (object != NULL && tag(object) == T_CFUNCTION);
}

/*
** Given an object handle, return if it is an user data one.
*/
int lua_isuserdata (Object *object)
{
 return (object != NULL && tag(object) == T_USERDATA);
}

/*
** Internal function: return an object type. 
*/
void lua_type (void)
{
 Object *o = lua_getparam(1);
 lua_pushstring (lua_constant[tag(o)]);
}

/*
** Internal function: convert an object to a number
*/
void lua_obj2number (void)
{
 Object *o = lua_getparam(1);
 lua_pushobject (lua_convtonumber(o));
}

/*
** Internal function: print object values
*/
void lua_print (void)
{
 int i=1;
 void *obj;
 while ((obj=lua_getparam (i++)) != NULL)
 {
  if      (lua_isnumber(obj))    printf("%g\n",lua_getnumber (obj));
  else if (lua_isstring(obj))    printf("%s\n",lua_getstring (obj));
  else if (lua_iscfunction(obj)) printf("cfunction: %p\n",lua_getcfunction (obj));
  else if (lua_isuserdata(obj))  printf("userdata: %p\n",lua_getuserdata (obj));
  else if (lua_istable(obj))     printf("table: %p\n",obj);
  else if (lua_isnil(obj))       printf("nil\n");
  else			         printf("invalid value to print\n");
 }
}

/*
** Internal function: do a file
*/
void lua_internaldofile (void)
{
 lua_Object obj = lua_getparam (1);
 if (lua_isstring(obj) && !lua_dofile(lua_getstring(obj)))
  lua_pushnumber(1);
 else
  lua_pushnil();
}

/*
** Internal function: do a string
*/
void lua_internaldostring (void)
{
 lua_Object obj = lua_getparam (1);
 if (lua_isstring(obj) && !lua_dostring(lua_getstring(obj)))
  lua_pushnumber(1);
 else
  lua_pushnil();
}

 

반응형
Comments