출처 : PEP 339 design of the cpython compiler
개요
보통의 python 개발자라면 굳이 cpython 의 구현/설계 원리를 알 필요가 없으며, 실제로도 코딩에 하등 도움이 되지 않는다.
허나,
- 본인의 성향이 오덕이고
- 요즘 언어의 추세인 managed resource 의 메모리 관리가 궁금한 아재이고
- python compiler/interpreter 개발에 기여하고 싶거나
- compiler 수업을 듣는 학생이라면
공부할 가치가 있다. 다시 말하지만 본인이 생업에 바빠 python 코드를 찍어내야 하는 반도의 흔한 개발자라면 굳이 PEP 같은건 볼 필요가 없다. 그냥 language reference 만으로 충분하다.
어쨋든 코딩=밥벌이에 큰 도움은 되지 않으나, 본인이 python에 대한 깊이있는 이해를 하고 있다는 착각에 빠지는 효과가 있고, 요즘 모던한 언어들이 다양한 형태의 vm을 어떻게 대응하는지에 대한 추세를 가늠할 수 있으며, 학생 시절에나 배울 법한 컴파일러/언어 이론을 찾아보다 보면 뇌단련이 되는 긍정적인 효과가 있다.
python life cycle
python code 는 크게 다음과 같은 흐름으로 실행된다.
- src code 작성
- bytecode 변환(by compiler)
- 실행(by interpreter on vm)
등장인물의 면면을 살펴보면,
- 내가 짠 src code
- compiler
- interpreter
- virtual machine
으로 총 4가지 이다.
일반적으로 널리 쓰이는 cpython 의 경우, compiler/interpreter/vm 을 c 로 구현하였고, 자체 정의한 bytecode로 compile/실행한다.
반면, jython 의 경우는 java bytecode로 compile/실행 하기 때문에 vm 의 경우 jvm을 사용할 수 있다.
아래 그림을 보면 관계가 이해갈 것이다.
cpython/jython 외에도 다양한 python compiler/interpreter 구현이 있다.
- pypy
- ironpython
- 기타등등
cpython compiler 구조
가장 common 한 cpython 의 compiler design 을 살펴보자.
위에 내용을 상기하면, compiler 는 src code -> bytecode 로 변환하는 역할이며,
cpython 이므로 c언어로 작성되었다.
관련 정보는 PEP(python enhancement proposals)-339 문서를 찾아보면 얻을 수 있는데, python 이라는 언어 자체가 구현체에 대한 스펙을 강제하지 않기 때문에 이 내용은 cpython 에만 해당한다는걸 염두에 두길 바란다.
src code 가 bytecode 로 변환되는 과정은 다음과 같다.
- src code
- parse tree(=Concrete Syntax Tree)
- AST(=Abstract Syntax Tree)
- CFG(=Control Flow Graph)
- bytecode
parse tree(concrete syntax tree)
cpython parser 는 LL parser 를 base 로 한다.
문법 규칙은 graminit.h 에 명세되어 있으며, type 매핑 코드는 token.h 에 정의되어 있다.
파서 : https://github.com/python/cpython/blob/master/Parser/pgen.c
문법 규칙 : https://github.com/python/cpython/blob/master/Include/graminit.h
type 매핑코드 : https://github.com/python/cpython/blob/master/Include/token.h
parser 의 결과물은 CST 라 부르는 parse tree 이다. CST 는 변환과정을 거쳐 AST 로 바뀐다. 여기서 CST와 AST 같은 중간단계가 왜 필요한지, 차이점은 무엇인지 의문이 들 것이다.
CST vs AST
CST와 AST 의 설명을 언뜻 보면 잘 이해가 가지 않을 수도 있다. 이해를 돕기 위해 다음 아주 간단한 python code 를 parse tree 로 변환해 보도록 하자.
1+2
이 코드를 CST(=parse tree) 로 변환하면 아래와 같다.
간단한 산술연산인데도 CST로 변환하니 뭔가 복잡하다. CST 에는 파싱된 python code 에서 요구하는 모든 syntax 및 token 의 정보가 포함되어 있다. 따라서 간단한 코드 길이에 비해 CST 의 depth 는 장황해진다.
조금 더 이해를 돕기 위해 다음 code 도 CST 로 변환해보자.
a=1
bytecode 입장에서는 단지
- opcode 와
- operand 만 필요할 뿐이어서
CST에는 불필요한 정보들이 많다. 1+2 와 a=1 을 각각 AST 로 변환해보자.
1+2
a=1
참고로, CST와 AST 는 모두 형식적으로 Extended BNF 를 따른다. BNF 가 궁금하면 링크를 참조하기 바란다.
(todo) 메모리 관리 – PyArena / PyObject
AST(abstract syntax tree) : https://github.com/python/cpython/blob/master/Python/ast.c
pyArena : https://github.com/python-git/python/blob/master/Python/pyarena.c
CFG(control flow graph) : https://github.com/python/cpython/blob/master/Python/compile.c
bytecode : https://github.com/python/cpython/blob/master/Python/compile.c
(BNF, Backus Naur Form) : CST
(Deterministic Finite Automaton) : AST
(NonDeterministic Finite AUtomaton) : AST
(memory management 추가자료)
컴파일 시 필요한 메모리 리소스는 PyArena 라 불리는 memory pool (PyObject 의 linked list) 로 관리되며 대부분의 경우 신경쓰지 않아도 되나, PyObject 를 직접 관리할 때에는 명시적으로 PyArena 에 해당 PyObject 를 추가해야 한다.
As stated above, in general you should not have to worry about memory management when working on the compiler. The technical details have been designed to be hidden from you for most cases.
The only exception comes about when managing a PyObject. Since the rest of Python uses reference counting, there is extra support added to the arena to cleanup each PyObject that was allocated. These cases are very rare. However, if you’ve allocated a PyObject, you must tell the arena about it by calling PyArena_AddPyObject().