RPython 시작하기

RPython은 정적으로 컴파일할 수 있는 Python2의 부분집합입니다. PyPy 인터프리터는 대부분 RPython으로 작성되어 있고(일부는 Python으로 작성되어 있습니다), RPython 컴파일러는 Python으로 작성되어 있습니다. 이해하기 어려운 부분은 Python이 RPython의 메타프로그래밍 언어라는 점입니다. 즉, “유효한 RPython인가”라는 질문은 임포트가 끝난 후의 살아 있는 객체에 대해서만 의미가 있습니다. 이 부분은 설명이 좀 더 필요할 수 있습니다. RPython 작성은 entry_point에서 시작하며, rpython/translator/goal/targetnopstandalone.py가 좋은 출발점입니다. 이 파일이 하는 일은 그리 많지 않지만, 시작점으로는 충분합니다. 이제 분석 대상 코드(이 경우 entry_point)가 어떤 함수를 호출하면, 그 호출을 따라갑니다. 그렇게 따라간 호출 자체(그리고 그 호출이 다시 호출하는 모든 것 등)는 RPython이어야 하지만, 모듈 파일 전체가 RPython일 필요는 없습니다. 메타프로그래밍을 어떻게 활용할 수 있는지 보여 드리기 위해 간단한 예제를 하나 만들어 봅시다(클로저는 RPython이 아니라는 점에 유의하십시오):

def generator(operation):
    if operation == 'add':
       def f(a, b):
           return a + b
    else:
       def f(a, b):
           return a - b
    return f

add = generator('add')
sub = generator('sub')

def entry_point(argv):
    print add(sub(int(argv[1]), 3) 4)
    return 0

이 예제에서 entry_point는 RPython이고, add와 sub도 RPython이지만, generator는 RPython이 아닙니다.

다음과 같은 입문 수준의 글을 읽어 보실 수 있습니다:

변환기(translator) 사용해 보기

변환기는 PyPy 인터프리터를 기반으로 한 도구로, 충분히 정적인 RPython 프로그램을 저수준 코드로 변환할 수 있습니다(특히 full Python interpreter를 변환하는 데 사용할 수 있습니다). 변환기를 실험해 보려면 다음 항목의 일반적인 (CPython2) 버전을 내려받아 설치해야 합니다:

대화형 변환기(translator) 셸을 시작하려면 다음과 같이 하십시오:

cd rpython
python2 bin/translatorshell.py

변환 가능한 코드의 테스트 스니펫은 rpython/translator/test/snippet.py 파일에 있으며, 이 파일은 snippet이라는 이름으로 임포트됩니다. 예를 들면 다음과 같습니다.:

>>> t = Translation(snippet.is_perfect_number, [int])
>>> t.view()

그러면 그래프 뷰어가 나타나며, 이를 통해 흐름 그래프를 대화식으로 살펴볼 수 있습니다. 이동하려면 살펴보고 싶은 대상을 클릭하십시오. 사용법에 대한 도움말을 보려면 ‘H’를 누르십시오. 다시 닫으려면 ‘Q’를 누르십시오.

타입 어노테이터 사용해 보기

is_perfect_number와 같은 함수(그리고 훨씬 더 큰 예제)의 타입을 완전히 추론할 수 있는 타입 어노테이터가 있습니다:

>>> t.annotate()
>>> t.view()

빨간색으로 표시된 변수 이름 위에 마우스를 올리면 추론된 타입을 확인할 수 있습니다.

흐름 그래프를 C 코드로 변환하기

그래프는 C 코드로 변환할 수 있습니다:

>>> t.rtype()
>>> lib = t.compile_c()

첫 번째 명령은 연산을 C에서 사용할 수 있는 저수준 타입(예: int)만 사용하는 다른 저수준 버전으로 바꿉니다. 이제 컴파일된 버전은 .so 라이브러리에 들어 있습니다. 예를 들어 ctypes를 사용하여 실행할 수 있습니다:

>>> f = get_c_function(lib, snippet.is_perfect_number)
>>> f(5)
0
>>> f(6)
1

조금 더 큰 예제

변환기(translator)와 어노테이터(annotator)를 보여 주는 중소 규모의 데모가 있습니다:

python2 bin/rpython --view --annotate translator/goal/bpnn.py

이렇게 하면 bpnn.py가 자신을 호출 그래프와 클래스 계층 구조로 표시합니다. 함수를 클릭하면 해당 함수의 흐름 그래프가 표시됩니다. 클래스를 클릭하면 그 클래스 인스턴스의 속성이 표시됩니다. 이 모든 정보(호출 그래프, 지역 변수의 타입, 인스턴스의 속성)는 어노테이터가 계산합니다.

이 예제를 C 코드로 변환하려면(실행 파일 bpnn-c로 컴파일됩니다) 다음과 같이 입력하기만 하면 됩니다.:

python2 bin/rpython translator/goal/bpnn.py

전체 프로그램 변환하기

전체 RPython 프로그램을 변환하는 데 쓰는 rpython/bin/rpython 스크립트가 있습니다. 그 예로는 약간 수정한 버전의 Pystone이 있습니다:

python2 bin/rpython translator/goal/targetrpystonedalone

이렇게 하면 실행 파일 “targetrpystonedalone-c”가 생성됩니다.

이 과정의 가장 큰 예는 full Python interpreter를 변환하는 것입니다. 직접 만든 인터프리터에 이 과정을 설정하는 방법을 다룬 FAQ도 있습니다.

RPython을 다루면서 유용하게 쓸 수 있는 환경 변수가 몇 가지 있습니다:

PYPY_USESSION_DIR
RPython은 변환 과정에서 생성되는 파일(예: 변환된 C 파일)을 저장하기 위해 임시 세션 디렉터리를 사용합니다. PYPY_USESSION_DIR은 이러한 세션 디렉터리의 기준 디렉터리 역할을 합니다. 이 변수의 기본값은 시스템의 임시 디렉터리입니다.
PYPY_USESSION_KEEP
RPython은 기본적으로 PYPY_USESSION_DIR 안의 세션 디렉터리 중 최근 PYPY_USESSION_KEEP 개(기본값 3)만 유지합니다. C 파일을 더 오래 보존하려면 이 값을 늘리십시오(lldebug 빌드를 많이 만들 때 유용합니다).

출처

  • rpython/translator에는 코드 분석 및 생성과 관련된 코드가 들어 있습니다. translator.py부터 읽기 시작하십시오. 거기서부터 여러 변환 단계에 관여하는 코드 조각들을 쉽게 따라갈 수 있을 것입니다.
  • rpython/annotator에는 그래프에 대해 추론할 수 있는 타입 어노테이션의 데이터 모델이 들어 있습니다. 이 데이터 모델을 사용하는 그래프 “워커(walker)”는 rpython/annotator/annrpython.py에 있습니다.
  • rpython/rtyper에는 RPython 타이퍼의 코드가 들어 있습니다. 타이퍼는 어노테이션된 흐름 그래프를 C 코드와 매우 비슷한 형태로 바꾸어 쉽게 변환할 수 있게 합니다. 그래프 변환은 rpython/rtyper/rtyper.py에 있는 코드가 제어합니다. 사용되는 객체 모델은 rpython/rtyper/lltypesystem/lltype.py에서 찾을 수 있습니다. 각 RPython 타입마다 rxxxx.py 파일이 있으며, 이 파일에는 해당 타입에 필요한 저수준 함수가 들어 있습니다.
  • rpython/rlib에는 RPython에서 사용할 수 있는 것들을 모은 RPython 표준 라이브러리가 들어 있습니다.