저번 글에서 택스트 형태의 소스 코드를 컴파일 가능한 논리 구조인
리더 (텍스트)->랙서 > CST-> 파서 > AST-> 컴파일러에서 랙서와 파서 부분을 정리 했고 이번엔 컴파일러에 대해서 정리를 하려합니다.
컴파일 작업은 두 부분으로 구성된다.
1. 컴파일러 AST를 순회하며 논리적 실행순서를 나타내는 제어 흐름 그래프를 생성한다.
2.어셈블러: CFG의 노드들을 실행 가능한 명령을 순차적으로 나열한 바이트 코드 형태로 변환한다.
컴파일 과정
파서 > AST -> 컴파일러 > CFG -> 어셈블러 > 바이트코드 -> 실행
AST 모듈을 코드 객체로 컴파일하는 과정
PyAST_CompileObject()
compile.c 경로에 정의돼 있다.
- 컴파일러 상태는 심벌 테이블을 담는 컨테이너 타입이다.
- 심벌 테이블은 변수 이름을 포함하고 추가로 하위 심벌 테이블을 포함할 수 도 있다.
- 컴파일러 타입에는 컴파일러 유닛도 포함된다.
- 각 컴파일러 유닛은 이름,변수 이름, 상수, 셀 변수들을 포함한다.
- 컴파일러 유닛은 기본 프레임 블록을 포함한다.
- 기폰 프레임 블록은 바이트코드 명령을 포함한다.
위 함수에서 컴파일러 상태 구조체인 compiler 타입은 컴파일러 플래그나 스택, PyArena등 컴파일러를 위한 다양한 프로퍼티를 포함한다.
컴파일러 상태 필드들
- c_arena : 메모리할당 아레나에 대한 포인터
- c_const_cache : names 튜플을 포함한 모든 상수를 담는 파이선 dict
- c_do_noet_emit_bytecode : 바이트 코드 컴파일 비활성화 플래그
- c_filename: 컴파일에 필요한 파일명
- c_flags: 상속받은 컴파일러 플래그들
- c_future: 대화형 모드를 위한 모듈의 __future__플래그에 대한 포인터
- c_interactive: 대화형 모드 활성화 플래그
- c_nestlevler : 현재 깊이 레벨
- c__optimize: 최적화 레벨
- c_st: 컴파일러의심벌 테이블
- c_stack: compiler_unit에 대한 포인터를 담는 파이썬 리스트
- u: 현재 블록의 컴파일러 상태
PyAST_CompileObject()가 다음과 같이 컴파일러 상태를 초기화한다.
- 모듈에 문서화 문자열__doc__이 포함되어 있지 않다면 빈 문서화 문자열을 생성한다. __annotations__프로퍼티에 대해서도 같은 작업을 수행한다.
- 스텍트레이스 및 예외 처리에 필요한 파일 이름을 컴파일러 상태에 설정한다.
- 인터프리터가 사용한 메모리 할당 아레나를 컴파일러의 메모리 할당 아레나로 설정한다.
- 코드 컴파일 전에 퓨처 플래그들을 설정한다.
퓨처플래그와 컴파일러 플래그
퓨처 플래그와 컴파일러 플래그는 컴파일러 기능을 설정한다.
- 환경 변수와 명령줄 플래그를 담는 구성 상태
- 모듈 소스 코드의__future__문
퓨처플래그
퓨처 플래그는 특정 모듈에서 문법이나 언어 기능을 활성화 하기 위해 사용된다. annotations 퓨처 플래그로 타입힌트의 평가를 지연시킬수 있다.
from __future__ import annotations
이 퓨처 문 아래 부터 결정되지 않은 타입힌트를 사용할 수 있다. 만약 퓨처문 없이 결정되지 않은 타입힌트를 사용하면 이 모듈을 임포트 할 수 없다.
대부분의 __future__플래그는 파이선 2와 3간 이식 지원을 위해 사용되었다고 한다. 파이썬 4.0이 나온다면 더많은퓨처 플래그가 추가 될 수 있다.
컴파일러 플래그
컴파일러 플래그는 실행 환경에 의존적이기 때문에 코드나컴파일러의 실행방식을 변경할 수 있다. __future__문과 달리 코드로 활성화 되지는 않는다.
컴팡릴러 플래그중 -0플래그는 디버그 용도로 추가된 모든 assert 문을 비활성화 하는 최적화를 실행한다. 이 플래그는 PYTHONPTIMIZE=1 환경변수로도 비활성화 할 수 있다.
심벌 테이블
코드를 컴파일하기 전에 PyAST_CompileObject() API 는 심벌 테이블을 생성한다.
심벌 테이블은 전역과 지역 등 이름 공간의 목록들을 컴파일러에 제공한다. 컴파일러는 심벌 테이블에서 얻은 이름 공간에서 스코프를 결정하고 참조를 실행한다.
심벌 테이블과 관련된 소스파일들
- symtable.c : 심펄테이블 구현
- symtable.h : 심펄테이블 api와 타입 정의
- symtable.py: 표준 라이브러리 symtable 모듈
컴파일러는 컴파일러당 하나의 symtable 인스턴스만 사용하기 때문에 이름 공간 관리가 중요하다. 예를 들어 두 클래스가 동일한 이름의 메서드를 가지고 있을 경우 모듈에서 어떤 메서드를 호출할지 정해주는 것이 symtable의 역할이다.
하위 스코프의 변수를 상위 스코프에서 사용하지 못하게 하는것도 symtable의 역할이다.
symtable 모듈
PyPI에서 받을 수 있는 tabulate 모듈을 사용해 심벌 테이블을 출력해 볼 수 도 있다.
위 스크립트를 실행해 보면 다음과 같은 심벌테이블이 출력된다.
심벌테이블 구현
심벌테이블 구현은 symtable.c 에서 찾을 수 있고 주 인터페이스는 PySymtable_buildObject() 다.
PySymtable_BuildObject()는 PyAST_FromNodeObject() 와 비슷하게 mod_ty타입에 따라 모듈 내의 문장들을순회한다.
심벌테이블은 mod_ty 타입인 AST의 노드와 분기를 재귀적으로 탐색하며 symtable의 엔트리로 추가한다.
PySymtable_BuildObject()는 모듈의 각 문을 순회하며 symtable_visit_stmt()를 호출한다. symtable_visit_stmt()는 Python/Python.asdl에서 정의한 모든문 타입에 대한 case를 가지고 있는 거대한 switch 문이다.
각 문 타입 마다 심벌을 처리하는 함수가 존재한다. 함수 정의문 타입을 처리하는 함수에는 다음을 처리하기 위한 로직들이 들어있다.
- 현재 재귀 깊이가 재귀 제한을 넘지 않았는지 검사한다.
- 함수가 함수 객체로 넘겨지거나 호출될 수 있도록 함수 이름을 심벌 테이블에 추가한다.
- 기본 인자 중 리터럴이 아닌 인자를 심벌테이블에서 찾는다.
- 타입 힌트를 처리한다.
- 함수 데코레이터를 처리한다.
마지막으로 symtable_enter_block()이 함수 블록을 방문해 인자와 함수 본문을 차례대로 처리한다.
다음은 symtable_visit_stmt() 중 함수에 대한 symtable을 구축하는 C코드의 일부입니다.
이렇게 생성된 심벌테이블은 컴파일러로 넘겨진다.
핵심 컴파일 과정
PyAST_CompileObject() 에 검파일러 상태와 symtable,AST로 파싱된 모듈이 준비되면 컴파일이 시작된다.
코어 컴파일러는 다음과 같은 두가지 작업을 수행한다.
- 컴파일러 상태와 심벌 테이블, AST를 제어흐름 그래프로 변환한다.
- 논리 오류나 코드 오류를 탐지해 실행 단계를 런터임 예외로 부터 보호한다.
파이썬에서 컴파일러 사용하기
내장 함수인 compile()로 컴파일러를 직접 호출 할 수 있다. compile()은 code object를 반환한다.
symtable() API 처럼 단순 표현식의 경우에는 eval 모드를, 모듈이나 함수, 클래스일 경우에는 exec 모드를 사용하면 된다.
컴파일된 코드는 코드 객체의 co_code 프로퍼티에 담긴다.
표준 라이브러리 바이트코드 역어셈블러 모듈 dis로 화면에 바이트코드를 출력하거나 Instruction 인스턴스 리스트를 얻을 수 있다.
dis를 임포트하고 코드 객체의 co_code 프로퍼티에 대해 dis()를 실행하면 역 어셈블러가 컴파일된 코드를 역어셈블한 후 REPL에 출력한다.
위에 명시된 LOAD_NAME, LOAD_CONST, BINARY_ADD, RETURN_VALUE는 모두 바이트 코드 명령이다. 바이너리 형태에서 명령을 1바이트로 표현한다는 뜻으로 바이트 코드란 이름이 붙었지만 사실 파이썬3.6부터 명령은 2바이트, 즉 워드로 표현되므로 지금은 바이트코드보다는 워드코드가 올바른 표현이라고 합니다.
'CPython' 카테고리의 다른 글
CPython/ 평가루프 (0) | 2023.01.24 |
---|---|
CPython/컴파일러 (2) '거의 같음' 연산자 구현하기(2) (0) | 2023.01.08 |
CPython/렉싱과 파싱/ '거의 같음'연산자 구현(1) (2) | 2022.12.10 |
CPython 구성과 입력 (2) | 2022.12.10 |
CPython 3.9 환경 설정 (macos) (0) | 2022.12.10 |