PyPy - RPython toolchain

この文書は我々が開発した toolchain について記述しており, それは (PyPy のような) RPython プログラムを様々なプラットフォームを対象に解析し「コンパイル」するものです.

文書は3つの節からなります: やや簡潔な overview, toolchain の主要な部品の簡単な入門, それらがどう組み合わされるかについてのより包括的な記述. この文書を初めて読む場合は, Overview が最も役に立つでしょう. 再度 PyPy のことを思い出そうとして読んでいる場合は, どう連携するか がおそらく求めているものでしょう.

Overview

翻訳 toolchain の仕事は RPython プログラムを対象とする様々なプラットフォームの1つに対して効率的な形に翻訳することです. 一般的に Python よりも低レベルな形になります. その翻訳ではこの仕事をいくつかの段階に分けますが, この文書はそれを説明するのが目的です.

リリース 1.2 では, RPython プログラムは C/POSIX, CLI/.NET, Java/JVM という言語/プラットフォームで動く形に翻訳できます.

対象プラットフォームの選択は処理に多少の違いを生みますが, (デフォルトの, そしてオリジナルの対象である) C への RPython プログラムの翻訳処理の描写から始めます.

RPython の翻訳 toolchain は Python のソースコードや構文木を見ませんが, 入力として与えられる関数オブジェクトの動作の定義である code object から処理を始めます. bytecode evaluatorFlow Object Space はこれら code object に対し 抽象解釈 (abstract interpretation) を行い control flow graph を (1関数につき1つ) 生成します: 元となるプログラムの別の表現 (意味論) もありますが, 型推論の適用や翻訳技法にはこれが向いている上に, 多くの翻訳段階を実行する上での基本的なデータ構造なのです.

翻訳が次の段階から成っていると考えると理解しやすいでしょう (下の図も見てください):

  1. プログラム全体が読み込まれた時点で, 任意の実行時の初期化処理が行えます. この処理が終わると RPython の意味で “十分に静的” な形でメモリ上に存在していなくてはなりません.
  1. Annotator はある特定のエントリポイントから大域解析を開始し, 型とそれぞれの変数が実行時にどんな値を格納し得るかの情報を演繹していきます. それと同時に Flow Object Space を使って flow graph を構築していきます.
  1. RPython Typer (もしくは RTyper) は Annotator が推論して得た高レベルの情報を使い, control flow graph 上の演算を低レベルの演算に変換していきます.
  1. RTyping の次には2つのやや異なる処理 optional transformations が適用できます. “backend optimizations” は出力プログラムをより高速にするための処理です. “stackless transform” はプログラムを継続渡しスタイルに変換します. この変換によりコルーチンの実装やその他の標準的ではない control flow の実装が可能になります.
  1. 次の段階は preparing the graphs for source generation (ソース生成のためのグラフの準備) です. この段階は, プログラム中の様々な関数や型が最終的にどんな名前になるのかの計算と明示的な例外とメモリ管理操作を挿入する変換に関わります.
  1. C backend 処理 (所謂 “GenC”) は C のソースファイルを生成します. (上で書いたように, 今は他の backend は無視しておきます.)
  1. これらのソースファイルがコンパイルされ実行ファイルが作成されます.

(ここでの表現から想像するものと, 実際の処理はそう違いません.)

pypy/bin/translatorshell.py という翻訳過程へのインターフェース interactive interface があり, 処理を段階を追いながら対話的に実行していけます.

次の図は簡略化された全体図です (PDF color version):

_images/translation-greyscale-small.png

flow モデル

Flow Object Space の解説は document describing object spaces にあります. ここでは, それから生成される翻訳過程の基本的なデータ構造について説明します.

全ての型は pypy/objspace/flow/model.py に定義してあります. これは PyPy のソースコードの中でその特徴を強調するのに比較的重要なモジュールです.

関数の flow graph は FunctionGraph クラスで表現されます. これには Link で結合された Block の集まりへの参照が入っています.

BlockSpaceOperation のリストを持っています. それぞれの SpaceOperationopname, args リスト, result を持っていて, args の要素と resultVariable であるか Constant であるかです.

圧倒的に便利な PyGame viewer というツールがあり, 翻訳過程の様々な段階のグラフの様子を覗くことができます. (なんで上手く行かないのかを調べるのに非常に便利です.) 見た目は次のような感じです:

_images/bpnn_update.png

flow graph の構造について理解するために, いくつかの例に python bin/translatorshell.py を使用して遊んでみることをおすすめします. 以下では型とその属性について少し詳しく記述してあります:

FunctionGraph

グラフ1つを格納するのためのコンテナ (1つの関数に対応します).

startblock:最初のブロック. 関数が呼ばれたときに処理が移っていく箇所です. startblock の入力引数は関数の引数です. 関数が *args 引数を受け取る場合は, args タプルが startblock の最後の入力として渡されます.
returnblock:関数の return に相当する (唯一の) ブロック. このブロックは空で実際の return 操作を含んではいません; return 操作は暗黙的なものです. 返り値は returnblock 唯一の入力引数です.
exceptblock:例外を関数の外部へ送出する (唯一の) ブロック. 2つの入力変数があり, それぞれ例外クラスと例外値です. (関数が明示的に例外を送出しない場合, 実際には他のブロックは exceptblock へ接続しません.)
Block

基本ブロック. 処理のリストと, 処理の終了後にジャンプしていく他の基本ブロックのリストを持ちます. このブロックの実行中に “生きている” 値は全て Variable に保持されています. ブロックはそれぞれ自身の異なる Variable を持っています.

inputargs:どの変数とも異なる Variable のリスト. 任意の直前のブロックから来る全ての値を表しています.
operations:SpaceOperations のリスト.
exitswitch:下記を参照のこと.
exits:Link のリスト. この基本ブロックから他の基本ブロックへの取り得るジャンプを表しています.

それぞれの Block は次のうち, ある 1 つの終了の仕方をします:

  • 無条件ジャンプ: exitswitch が None で, exits に 1 つの Link しかない場合.
  • 条件付きジャンプ: exitswitch が Block に現れる Variable のうちの 1 つであり, exits が 1 つ以上 (たいてい 2 つ) の Link を含んでいる場合. それぞれの Link の exitcase が具体的な値です. これは “switch” と等価です: 実行時の exitswitch の Variable の値と一致する exitcase を持つ Link に従って処理は進んでいきます. Variable がどの exitcase とも一致しない場合は実行時エラーとなります.
  • 例外捕捉: exitswitch が Constant(last_exception) である場合. 先頭にある Link の exitcase は None になっており, 例外的でない処理の道を表しています. それに続く Link の exitcase は Exception の子クラスになっており, 基本ブロックの 最後の 処理が例外を送出した場合に, 対応する例外が選択されます. (従って基本ブロックは空であってはならず, 最後の処理だけが handler から保護されています.)
  • 復帰または例外: returnblock と exceptblock の operations が空タプル, exitswitch が None, exits が None である場合.
Link

基本ブロックから他の基本ブロックへの結線です.

prevblock:出口がこの Link に繋がっている Block.
target:この Link が指す対象の Block.
args:対象 Block の inputargs と同じサイズの Variable と Constant からなるリスト. このリストが次のブロックへ値を渡していきます. (prevblock で使われた各 Variable は args リストの中に, 0 回, 1 回もしくはそれ以上の回数出現し得ることに注意してください. )
exitcase:上記を参照のこと.
last_exception:None または Variable; 下記を参照のこと.
last_exc_value:None または Variable; 下記を参照のこと.

args は prevblock の Variable を使っていますが, タプル代入 (多重割り当て) や関数呼び出し (のときの引数) のように対象ブロックの inputargs と対応付けられます. リンクが例外捕捉のものだった場合, last_exceptionlast_exc_value には処理がリンクに入ったときに作られる既存のどれとも異なる Variable がそれぞれ設定されています; 実行時には例外クラスとその値がそれぞれ入っています. それら 2 つの新しい変数は同じリンクの args リストの中でのみ使われ, 次のブロックへ受け渡されていきます (通常は, args の中に全く現れなかったり数回現れたりします).

SpaceOperation

登録された (もしくは生成された) 基本演算です.

opname:演算の名前. Flow Space は pypy.interpreter.baseobjspace のリストにある演算しか生成しませんが, その名前は後で任意の名前に変更できます.
args:引数のリスト. 個々の要素は基本ブロックのところで見た Constant か Variable です.
result:結果が格納される 新しい Variable.

通常演算は, 実行時に暗黙的に例外を送出できないことに注意してください. なので例えば, リストの getitem 演算は安全であり境界判定をせずに実行できる, とコード生成器が仮定してしまうことも有り得ます. この規則の例外は: (1) もしその演算が exitswitch == Constant(last_exception) で終わるブロックの最後に現れた場合, 明示されていない例外をチェックし, 生成し, 適切に捕捉しなければいけません; (2) simple_callcall_args を通じた他の関数の呼び出しは, その関数が送出し得るどんな例外も常に送出する可能性があります — そしてそのような例外は前出のように捕捉されない限り, そのまま呼び出し元に渡さなければなりません.

Variable

実行時の値のプレースホルダー. 主にデバッグ用のものです.

name:name の一意性が保証されているとしても, 値を参照するのに name 属性ではなく Variable オブジェクトそのものを使うのは良い手法です.
Constant

SpaceOperation の引数や, ある Link の対象ブロックにおける入力 Variable を初期化するために渡す値として使う定数.

value:この Constant によって表現されている具体値
key:この値を表すハッシュ化可能なオブジェクト

Constant は変更可能な Python オブジェクトを格納することも可能です. これはそのオブジェクトの, 静的で初期化済みの読み取り専用版を表しています. flow graph はそのような Constant を実際に変更しようとすべきではありません.

注釈渡し

以下で, どのように control flow graph に “注釈” が付けられ, オブジェクトの型を知ることができるかについて簡単に説明します. この注釈渡しは型推論の一形式であり, Flow Object Space によって組み立てられる control flow graph に作用します.

より包括的な注釈処理の解説は, EU report about translation の対応する節を参照してください.

Annotator の主な目的は flow graph に現れる変数にそれぞれ “注釈を付ける” ことです. “注釈” はプログラム全体の全ての – 1つの関数につき1つの – flow graph の解析結果に基づき, 変数が実行時に格納し得る全ての Python オブジェクトを記述します.

“注釈” は SomeObject の子クラスのインスタンスです. それぞれの子クラスはオブジェクトの特定の族を表現しています.

以下が概要です (pypy/annotation/model/ も参照してください):

  • SomeObject は基底クラスです. SomeObject() のインスタンスは任意の Python オブジェクトを表現していて, たいていは入力プログラムが全てが RPython でないことを意味しています.
  • SomeInteger() は任意の整数を表現します. SomeInteger(nonneg=True) は非負整数 (>=0) を表しています.
  • SomeString() は任意の文字列を表現します. SomeChar() は長さが 1 の文字列です.
  • SomeTuple([s1,s2,...,sn]) は長さ n のタプルを表現します. タプルの要素は与えられた注釈のリストによって制限されています. 例えば, SomeTuple([SomeInteger(), SomeString()]) は 2 つの要素: 整数と文字列, を持つタプルを表現しています.

注釈渡しの結果得られるものは本質的には Variable から注釈への巨大な辞書です.

全ての SomeXXX インスタンスは変更不可能です. Annotator がある変数に対し何を格納し得るかの考えを改める必要がある場合には, 新しい注釈を作り, 既存のものは変更しません.

変更可能な値とコンテナ

変更可能なオブジェクトは注釈付けでは特別な扱いをする必要があります. というのは, 格納されている値の注釈は変更操作のために更新する必要があり, 従ってその注釈の情報は flow graph の関係する部分に伝わらなければなりません.

  • SomeList は均質な型のリストを表しています. (つまり, リストの全ての要素は単一の注釈 SomeXxx が付けられています.)
  • SomeDict は均質な辞書を表しています. (つまり, 全てのキーは同じ注釈 SomeXxx を持ち, 値に関しても同様です.)

ユーザ定義クラス, インスタンス

SomeInstance は与えられたクラスやその任意の子クラスのインスタンスを表現しています. Annotator が見ているユーザ定義クラスそれぞれに対し, そのクラスのインスタンスが持つ属性を表現する ClassDef (pypy.annotation.classdef) を用意しています; 本質的に ClassDef は全てのクラスレベルとインスタンスレベルの属性の集合と, それらに対応する注釈 SomeXxx を与えます.

インスタンスレベルの属性は, 注釈処理が進むのに従って漸進的に発見されていきます. 次のような代入:

inst.attr = value

は ClassDef を更新し, その属性が存在し, 代入したその値まで一般化できることを記録します.

全ての属性について ClassDef はその属性が 読まれる 全ての位置も記録しています. しばらく進んでから, その属性の注釈を一般化しなければならない代入に出会った場合は, それまでに属性を読み取っている全ての場所は無効と印を付けられ, Annotator はそこから解析をやり直します.

インスタンスレベルの属性とクラスレベルの属性の違いはわずかです; クラスレベルの属性は本質的にはインスタンスレベルの属性の初期値と考えらることができます. メソッドをインスタンスの初期値と看做すとすると, メソッドがインスタンス (例えば self = SomeInstance(cls)) に結び付けられている点を除けば, メソッドは特別なものではありません.

継承の規則は以下の通りです: 2 つの SomeInstance 注釈の併合は, 最も具体的な共通の祖先クラスの SomeInstance です. 親クラスの SomeInstance を通して属性が使われた (読み取り, もしくは書き込みされた) 場合, 全ての子クラスも同じ属性を持ち同じ注釈がそれら全てに適用されると考えます (なので, 親クラスのメソッドに return self.x のようなコードがあると, その親クラスと全ての子クラスが属性 x を持つように強制され, 全ての子クラスがその x に格納する可能性のある値全てを含む十分一般化された注釈が付けられます). しかし, 親クラスを通した普通の方法ではない方法で使われた場合, 子クラスは同じ名前の属性で異なった無関係の注釈を持つことができます.

RPython 型付け器

https://bitbucket.org/pypy/pypy/src/default/pypy/rpython/

RTyper はバックエンドの選択にごとに処理が異なる初めての場所です; 上で説明した概要は ANSI C を対象と仮定しています.

RPython 型付け器は Annotator とコード生成器の橋渡しをします. Annotator が計算した情報は, リストやユーザ定義クラスのインスタンスのような RPython の型について記述してあるという意味で高レベルな情報です.

これら高レベルな注釈を対象言語の低レベルなモデルで表現するのに必要なコードを出力すること; 低レベルなモデルとは C では構造体, ポインタ, 配列を意味します. 型付け器はそれぞれの注釈に対する適切な低レベルの型を決定し, control flow graph の高レベルな演算を 1 つもしくはいくつかの低レベルの演算に置き換えます. 低レベルの型と同じように, 構造体のフィールドの読み取りおよび書き込みの行に沿って、低レベルの演算のいくらか制限された集合があります.

理論的には, この処理段階はオプションです; コード生成器は直接高レベルな型を読めても構いません. しかし我々の経験上, これはあまり実践的ではないことを申し上げておきます. 高レベルな型を低レベルな型に “コンパイルする” ことは想像するよりも煩雑で, それがこの処理段階を明示的に1つの段階として分離する動機です. RTyping が終わったら, graph は対象言語のレベルにある演算しか含んでいない状態になり, これによってコード生成作業を非常に単純化されます..

もっと詳細な情報は RTyper のドキュメント を参照してください.

例: 整数演算

整数演算は簡単な例になります. graph が次のような演算を含んでいるとします:

v3 = add(v1, v2)

このように注釈付けされるとします:

v1 -> SomeInteger()
v2 -> SomeInteger()
v3 -> SomeInteger()

すると明らかに次のように型付けをして置換したくなります:

v3 = int_add(v1, v2)

– C の記法で書くと – 3つの変数 v1, v2, v3 は全て int と型が付けられています. これは concretetype 属性を (Variable もしくは Constant のインスタンスである) v1, v2, v3 に与えることで実現されます. 我々のモデルでは, この concretetypepypy.rpython.lltypesystem.lltype.Signed です. 当然, add という演算を int_add という演算に置き換える目的は, コード生成器は, この演算がどの種類の加算 (もしくは文字列結合?) を意味しているのかをこれ以上心配したくないからです.

オプションの変形

RTyping と C ソースコード生成の間に 2 つのオプションの変形処理があります: “backend optimizations” と “stackless transform” です. より詳しい情報は D07.1 Massive Parallelism and Translation Aspects を参照してください.

Backend Optimizations

backend optimizations のポイントはコンパイルされたプログラムがより高速に実行されることです. 伝統的なコンパイラとは大きく異なる PyPy 翻訳機のほとんどの部分と比較して, この最適化はコンパイラがどのように働くかを知ってる人にはそこそこ馴染み深いものでしょう.

関数インライン化

PyPy インタプリタを実行しているときに起こる関数呼び出しのオーバーヘッドを減らすために, 我々は関数インライン化を実装しました. これは flow graph と呼び出し箇所に対して, 変数を適切に変更しながら呼び出されている関数の flow graph を呼び出し側の flow graph に埋め込む最適化です. これはインライン化する前の関数が try: ... except: ... で囲われている場合に問題を引き起こします. このケースでは, インライン化は常にできるとは限りません. もっとも呼び出される関数が直接例外を送出しない (が, それ以降に呼び出される関数から例外が送出される可能性はある) 場合は, インライン化は安全です.

加えて言うと, どの関数をどこでインライン化するかの発見的手法を実装しました. この目的のために全ての関数に “size” (大きさ) という概念を追加しました. 大きさは大雑把には, ある場所に関数をインライン化したときのコードサイズの増加に対応します. これは 2 つの数字の和で評価されます: 1 つ目は全ての演算にある特定の重み (デフォルトは 1) を割り当てます. ある演算, 例えばメモリ割り当てや呼び出しなどは, 他の演算よりもコストが掛かると看做されます; 他のもの (キャスト...) は全くコストが掛からないと看做されます. 大きさの評価は 1 つは graph に現れる全ての演算の重みの合計です. これは “static instruction count” (静的インストラクション数) と呼ばれます. graph の大きさの評価のもう 1 つの要素は “median execution cost” (実行コストの中央値) です. これも graph の全ての演算の重みの和ですが, これはその演算がどのくらい頻繁に呼ばれるかを推測した値の分の重みが掛けられています. この推測を行うために, 我々は全ての条件分岐は同じ頻度で両方の分岐を通るという仮定を置いています. ただし例外として, ループの末尾の条件分岐はループを抜けるよりもループの先頭に戻る方が多いと看做しています. これによって等式系 (連立方程式) が導き出され, これを解くことで全ての演算のおおよその重みを得ることができます.

全ての関数の大きさの評価が確定した後に, 最も小さい関数から順に呼び出し箇所へインライン化されていきます. 関数が他の関数の中へインライン化されていくたびに, 外側の関数の大きさが再計算されます. この処理は残っている全ての関数が事前に決めた値より大きくなるまで続けられます.

malloc 除去

RPython は GC (garbage collection) 付きの言語なので, 常にヒープメモリの割り当てが行われています. これは伝統的な開発者が明示的に管理を行う言語では全く起きないか, 前持って分かっているタイミングで死に, 従って暗黙的に (訳注: 原文では explicitely とあるが implicitly の間違いか?) 解放されるオブジェクトという仕組みが作られたりします. 例えば以下のような形のループを考えてみます:

for i in range(n):
    ...

これは単純に 0 から n - 1 までの全ての整数を順に繰り返すループで, 以下の Python スクリプトと同等です:

l = range(n)
iterator = iter(l)
try:
    while 1:
        i = iterator.next()
        ...
except StopIteration:
    pass

これから 3 回メモリ割り当てが行われていることが分かります: range オブジェクト, range オブジェクトのイテレータ, ループを終了させる StopIteration インスタンスです.

少々のインライン化を行ってみると, これら 3 つのオブジェクトは他の関数に引数として渡されることも グローバルにアクセスできる場所に格納されることもありません. このような状況では (関数が終了したときにどちらにせよ死ぬので) オブジェクトは除去でき, オブジェクトが格納している値に置き換えることができます.

この (作成したオブジェクトが現在の関数の外に出ることが無く, 関数が終了すると死ぬ) パターンは非常に頻繁に起き, 特にインライン化をした後に起きやすいです. それ故, オブジェクトを “爆破” し, この単純 (かつ頻度の高い) 状況でのメモリ割り当てを 1 つ節約する最適化を実装しました.

エスケープ解析とスタック内メモリ割り当て

もう 1 つのメモリ割り当てのコストを減らす技術は, メモリ割り当てがなされたスタックフレームの中でしか生存しないことが証明されたオブジェクトに対し スタック上でメモリ割り当てを行ってしまうことです. これは現実には速度には寄与しないことが分かったので, 徐々に取り除かれました.

The Stackless Transform

stackless transform は関数を, 実行箇所と生きている変数をヒープに保存し, その箇所から実行を再開することのできる形式に変換します. これは RPython レベルの機能として coroutine を実装するために使われ, さらに coroutine, greenlet, tasklet を標準インタプリタのアプリケーションレベルの機能として実装するのに使われています.

stackless transformation は廃止予定となっていて, 今後は trunk では利用できなくなっています. これは既に continulets に置き換えられています.

ソース生成の準備

このステージは, やや漠然とした名前かもしれませんが, 最近個別の処理段階として分離されました. ここでの仕事はソース生成の前の最後の実装の判断を行います – 実際のソースコード生成と同時に どんなことでも 考えることはしたくないと, 経験的に思うはずです. C backend では, この処理段階は 3 つのことを行います:

  • 明示的な例外処理を挿入し,
  • 明示的なメモリ管理操作を挿入し,
  • 関数や型が最終的なソースでどんな名前になるかを決定します (このオブジェクトから名前への対応付けは, “low-level database” として何度か参照されます).

例外処理の明示化

RPython のコードは制限の無い Python と同じ方法で自由に例外が使えますが, 最終的な成果物は C プログラムであり, C には例外の考え方がありません. 例外変換器は CPython と似た方法で例外処理を実装します: 例外は特殊な返り値として表現され, 現在の例外はグローバルなデータ構造に格納されます.

ある意味では例外変換器への入力は例外付き lltypesystem のプログラムであり, 出力は生の lltypesystem のプログラムです.

メモリ管理の詳細

RPython は例外を特徴として持つ言語ですが, GC を持つ言語でもあります; そしてやはり C はそうではありません. この丸をなんとか四角にするために, メモリ管理に関する判断をしなければなりません. PyPy の柔軟さを保つために, その手法は自由に変更できます. 今日では 3 つの実装があります:

ほとんど全てのアプリケーションレベルの Python コードは非常に高い割合でオブジェクト割り当てを行っています; これはメモリ管理の実装は PyPy インタプリタのパフォーマンスに重大な影響があることを意味します.

どの GC 戦略を使うは :config:`translation.gc` で選べます.

The C Back-End

https://bitbucket.org/pypy/pypy/src/default/pypy/translator/c/

GenC は最も活発に保守されている backend であり – 1 つには, PyPy を開発している人は皆 C コンパイラを持っていることがあります –, たいていの新しい機能が最初に実装されます.

歴史についてのメモ

このドキュメントを読めば分かるように, 最初は 1 つだと思われていた翻訳の処理段階が分割されています. プロジェクトが開始したときの予想よりも確実に多くの処理段階に分割されています; 本当に最初のバージョンの GenC は高レベルな flow graph と Annotator の出力結果を処理していましたし, RTyper の概念はまだ存在すらしていませんでした. もっと最近では, ソース生成のためにグラフを前処理する (“databasing”) 段階と 実際にソース生成する段階を分けるのが最も良い, という事実がはっきりしました.

他の backend

:config:`translation.backend` オプションを使ってどの backend を使うかを選べます.

オブジェクト指向 backend

オブジェクト指向 backend は, C とは異なりクラスやインスタンスなどをサポートするプラットフォームを対象としています. このようなプラットフォームを対象とした場合は, RTyping の処理で OO type system が使われます. OO backend の gencli も genjava も Python インタプリタ全体を翻訳できます.

GenCLI

GenCLI は Microsoft の .NETMono の最も有名な実装である Common Language Infrastructure を対象としています.

これはオブジェクト指向 backend の中で最も開発が進んでいます – 標準的なベンチマークである RPyStone (CPython の PyStone ベンチマークを RPython 用に少し改造したもの) や 有名な Rechards のベンチマークの RPython 版にに加えて PyPy インタプリタもコンパイルできます.

これはほぼ全て Master’s thesis (修士論文のテーマ), Google の Summer of Code 2006 プログラムおよび Summer of PyPy プログラムの一部として, この backend の開発を始めた Antonio Cuni の働きによるものです.

GenJVM

GenJVM は Java 仮想マシンを対象にしています: GenCLI が行っているように RPython プログラムを直接 Java バイトコードに変換します.

現在のところ GenCLI に続いて 2 番目に成熟している高レベルな backend です: まだ標準インタプリタ全体をコンパイルすることはできませんが, Leysin sprint において RPyStone (訳注: 原文の rpytstone はおそらく typo) と Richards ベンチマークはコンパイルし, 実行することができるようになりました.

GenJVM はほぼ全て, Summer of PyPy プログラムの一部として取り組んだ Niko Matsakis の働きによるものです.

外部関数呼び出し

外部関数呼び出しへのアプローチについては rffi ドキュメントで解説されています.

どう連携するか

ここまでで明らかになったように, PyPy の translation toolchain は多くのコンポーネントが形成する柔軟性に富む複雑な獣です.

次の画像は 0.9 リリースの toolchain の様々な部品をまとめたもので, C へのデフォルトの翻訳処理に色が付けてあります:

_images/pypy-translation-0.9.png

ここまで強調されていなかった詳細部分は様々なコンポーネントの相互作用です. この図では Annotator が終わったら RTyper が graph の処理を行い, 次に例外処理が明示化され, などなど, ということが分かりやすくなっていますが, これは本当に正しいというわけではありません. 例えば, RTyper はまず注釈付けされなくてはならない多くの low-level helpers を呼んでいますし, GC 変換器はパフォーマンスを改善するために, そのヘルパー関数のインライン化 (これは backend optimizations の 1 つ) を使えます. 次の画像はデフォルトの翻訳過程のそれぞれの処理段階に関わるコンポーネントをまとめた図です:

_images/translation-detail-0.9.png

まだ言及していないコンポーネントは “MixLevelAnnotator” です; これは (RTyping より) “後の” 翻訳段階が (お互いに参照し合う相互再帰のような形になっているかもしれない) 関数群のそれぞれ関数の呼び出し, 注釈付け, 型付け (RTyping) をいっぺんにできる必要がある, と宣言するための便利なインターフェースを提供します.