|
1 | 1 | # 컴파일러 |
| 2 | + |
2 | 3 | 파이썬 코드를 파싱하면 연산자, 함수, 클래스, 이름 공간을 포함하는 AST가 생성 |
3 | 4 | AST를 CPU 명령으로 바꾸는 것이 **컴파일러** |
4 | 5 |
|
5 | | - |
| 6 | +## 컴파일 과정 |
| 7 | + |
| 8 | +컴파일 과정을 그림으로 표현 |
| 9 | +앞장에서 본 렉싱과 파서를 통하여 생성한 AST를 컴파일러를 통하여 CFG(Control Flow Graph)로 변환 |
| 10 | +어셈블러를 통하여 CFG의 노드를 순서대로 **바이트 코드로 변환** 후 **실행** |
| 11 | + |
| 12 | +### 컴파일과 관련된 소스 |
| 13 | + |
| 14 | +| 파일 | 목적 | |
| 15 | +|-------------------|-----------------| |
| 16 | +| Python/compile.c | 컴파일러의 구현 | |
| 17 | +| Include/compile.h | 컴파일러 API와 타입 정의 | |
| 18 | + |
| 19 | +PyAST_CompileObject() CPython 컴파일러의 주 진입점 |
| 20 | + |
| 21 | + |
| 22 | +**컴파일러 상태**는 심벌 테이블을 담는 타입 |
| 23 | +**심벌 테이블**은 변수 이름을 포함하고 추가로 하위 심벌 테이블을 포함할 수 있음 |
| 24 | +**컴파일러 타입**에는 컴파일러 유닛도 포함 |
| 25 | +각 **컴파일러 유닛**은 이름, 변수 이름, 상수, 셀(cell) 변수들을 포함 |
| 26 | +컴파일러 유닛은 **기본 프레임 블록**을 포함 |
| 27 | +기본 프레임 블록은 **바이트코드 명령**을 포함 |
| 28 | + |
| 29 | +## 컴파일러 인스턴스 생성 |
| 30 | +컴파일러를 실행하기 앞서 전역 컴파일러 상태가 생성 |
| 31 | +compiler 타입은 컴파일러 플래그, 스택, PyArena 등 컴파일러를 위한 다양한 프로퍼티를 포함 |
| 32 | +컴파일러 상태는 심벌 테이블 등의 다른 구조체도 포함 |
| 33 | +```cpp |
| 34 | +struct compiler { |
| 35 | + PyObject *c_filename; |
| 36 | + struct symtable *c_st; |
| 37 | + PyFutureFeatures *c_future; /* pointer to module's __future__ */ |
| 38 | + PyCompilerFlags *c_flags; |
| 39 | + |
| 40 | + int c_optimize; /* optimization level */ |
| 41 | + int c_interactive; /* true if in interactive mode */ |
| 42 | + int c_nestlevel; |
| 43 | + int c_do_not_emit_bytecode; /* The compiler won't emit any bytecode |
| 44 | + if this value is different from zero. |
| 45 | + This can be used to temporarily visit |
| 46 | + nodes without emitting bytecode to |
| 47 | + check only errors. */ |
| 48 | + |
| 49 | + PyObject *c_const_cache; /* Python dict holding all constants, |
| 50 | + including names tuple */ |
| 51 | + struct compiler_unit *u; /* compiler state for current block */ |
| 52 | + PyObject *c_stack; /* Python list holding compiler_unit ptrs */ |
| 53 | + PyArena *c_arena; /* pointer to memory allocation arena */ |
| 54 | +}; |
| 55 | +``` |
| 56 | +PyAST_CompileObject()가 다음과 같이 컴파일러 상태 초기화 |
| 57 | +
|
| 58 | +모듈에 문서화 문자열(\_\_doc\_\_)이 없다면 빈 문서화 문자열 생성 |
| 59 | +\_\_annotations\_\_ 프로퍼티도 동일 작업 수행 |
| 60 | +스택 트레이스 및 예외 처리에 필요한 파일 이름을 컴파일러 상태에 설정 |
| 61 | +인터프리터가 사용한 메모리 할당 아레나(arena)를 컴파일러의 메모리 할당 아레나로 설정 (메모리 할당자는 9장 메모리 관리 참고) |
| 62 | +코드 컴파일 전 퓨처 플래그들을 설정 |
| 63 | +
|
| 64 | +## 퓨처 플래그와 컴파일러 플래그 |
| 65 | +컴파일러 기능 설정 |
| 66 | +1. 환경 변수 명령줄 플래그를 담는 구성 상태 |
| 67 | +2. 모듈 소스 코드의 \_\_future\_\_ 문 |
| 68 | +
|
| 69 | +**퓨처 플래그**는 Python 2와 3 간 이식 지원을 위해 사용 |
| 70 | +
|
| 71 | +**컴파일러 플래그**는 실행 환경에 의존적, 실행 방식을 변경 할 수 있음 |
| 72 | +예시로 -O 플래그는 디버그 용도의 assert 문을 비활성화 하는 최적화를 진행 |
| 73 | +PYTHONOPTIMIZE=1 환경 변수로도 활성화 가능 |
| 74 | +
|
| 75 | +## 심벌 테이블 |
| 76 | +코드 컴파일 전 PySymtable_BuildObject() API로 심벌 테이블 생성 |
| 77 | +전역, 지역 등 이름 공간 목록을 컴파일러에 제공 |
| 78 | +컴파일러는 심벌 테이블에서 얻은 이름 공간에서 스코프를 결정, 참조를 실행 |
| 79 | +
|
| 80 | +```cpp |
| 81 | +struct symtable { |
| 82 | + PyObject *st_filename; /* name of file being compiled, |
| 83 | + decoded from the filesystem encoding */ |
| 84 | + struct _symtable_entry *st_cur; /* current symbol table entry */ |
| 85 | + struct _symtable_entry *st_top; /* symbol table entry for module */ |
| 86 | + PyObject *st_blocks; /* dict: map AST node addresses |
| 87 | + * to symbol table entries */ |
| 88 | + PyObject *st_stack; /* list: stack of namespace info */ |
| 89 | + PyObject *st_global; /* borrowed ref to st_top->ste_symbols */ |
| 90 | + int st_nblocks; /* number of blocks used. kept for |
| 91 | + consistency with the corresponding |
| 92 | + compiler structure */ |
| 93 | + PyObject *st_private; /* name of current class or NULL */ |
| 94 | + PyFutureFeatures *st_future; /* module's future features that affect |
| 95 | + the symbol table */ |
| 96 | + int recursion_depth; /* current recursion depth */ |
| 97 | + int recursion_limit; /* recursion limit */ |
| 98 | +}; |
| 99 | +``` |
| 100 | +컴파일러당 하나의 symtable 인스턴스만 사용, 공간 관리 중요 |
| 101 | +두 클래스가 동일한 이름의 메서드를 가지고 있을 경우 모듈에서 어떤 메서드를 호출할지 정해주는 것 |
| 102 | +하위 스코프의 변수를 상위 스코프에서 사용하지 못하게 하는 것 |
| 103 | +위 두 가지가 symtable의 역할 |
| 104 | + |
| 105 | +### 심벌 테이블 구현 |
| 106 | +symtable.c 에서 찾을 수 있다 |
| 107 | +주 인터페이스는 PySymtable_BuildObject() |
| 108 | +mod_ty 타입(Module, Interactive, Expression, FunctionType)에 따라 모듈 내의 문장을 순회 |
| 109 | +mod_ty 타입인 AST의 노드의 분기를 재귀적으로 탐색하며 symtable의 엔트리로 추가 |
| 110 | +```cpp |
| 111 | +struct symtable * |
| 112 | +PySymtable_BuildObject(mod_ty mod, PyObject *filename, PyFutureFeatures *future) |
| 113 | +{ |
| 114 | + struct symtable *st = symtable_new(); |
| 115 | + asdl_seq *seq; |
| 116 | + int i; |
| 117 | + PyThreadState *tstate; |
| 118 | + int recursion_limit = Py_GetRecursionLimit(); |
| 119 | + int starting_recursion_depth; |
| 120 | +... |
| 121 | + st->st_top = st->st_cur; |
| 122 | + switch (mod->kind) { |
| 123 | + case Module_kind: |
| 124 | + seq = mod->v.Module.body; |
| 125 | + for (i = 0; i < asdl_seq_LEN(seq); i++) |
| 126 | + if (!symtable_visit_stmt(st, |
| 127 | + (stmt_ty)asdl_seq_GET(seq, i))) |
| 128 | + goto error; |
| 129 | + break; |
| 130 | + case Expression_kind: |
| 131 | + if (!symtable_visit_expr(st, mod->v.Expression.body)) |
| 132 | + goto error; |
| 133 | + break; |
| 134 | + case Interactive_kind: |
| 135 | + seq = mod->v.Interactive.body; |
| 136 | + for (i = 0; i < asdl_seq_LEN(seq); i++) |
| 137 | + if (!symtable_visit_stmt(st, |
| 138 | + (stmt_ty)asdl_seq_GET(seq, i))) |
| 139 | + goto error; |
| 140 | + break; |
| 141 | + case FunctionType_kind: |
| 142 | + PyErr_SetString(PyExc_RuntimeError, |
| 143 | + "this compiler does not handle FunctionTypes"); |
| 144 | + goto error; |
| 145 | + } |
| 146 | +... |
| 147 | +``` |
| 148 | +모듈의 각 문을 순회, symtable_visit_stmt() 를 호출 |
| 149 | +Parser → Python.asdl에서 정의한 모든문 타입에 대한 case를 가지고 있는 거대한 swich 문 |
| 150 | +
|
| 151 | +각 문 타입마다 심벌을 처리하는 함수 존재 |
| 152 | +함수 정의문 타입을 처리하는 함수는 다음 처리를 위한 로직이 있다 |
| 153 | +- 현재 재귀 깊이가 제귀 제한을 넘지 않았는지 검사 |
| 154 | +- 함수가 함수 객체로 넘겨지거나 호출될 수 있도록 함수 이름 심벌 테이블에 추가 |
| 155 | +- 기본 인자 중 리터럴이 아닌 인자는 심벌 테이블에서 찾음 |
| 156 | +- 타입 힌트 처리 |
| 157 | +- 함수 데코레이터 처리 |
| 158 | +
|
| 159 | +마지막으로 symtable_enter_block()이 함수 블록을 방문해 인자와 함수 본문 처리 |
| 160 | +
|
| 161 | +이렇게 생성된 심벌 테이블은 컴파일러로 넘김 |
| 162 | +
|
| 163 | +## 핵심 컴파일 과정 |
| 164 | +PyAST_CompileObject()에 컴파일러 상태와 symtable, AST로 파싱된 모듈이 준비되면 컴파일 시작 |
| 165 | +1. 컴파일러 상태, 심벌 테이블, AST를 제어 흐름 그래프로 변환 |
| 166 | +2. 논리 오류나 코드 오류를 탐지, 실행 단계를 런타임 예외로부터 보호 |
6 | 167 |
|
0 commit comments