core の設計

設計の目標

ルートパッケージは、ほとんどのプログラムがインポートするファサードです。1 つのインポートで、ポリシー、プラン、ワークフローを作り、カーネルを実行し、ワークフローを投入でき、妥当な既定値を備えていることが求められます。一方で本当の定義は、それを所有するパッケージに置いたままにします。バックエンドを選ぶのもここなので、backend/native 以外で C ランタイムに依存する唯一のパッケージです。

数学的背景

ファサードは新しい概念を加えません。それは自分の関数と下位パッケージの関数の間の等式の集まりです。既定のポリシー (Native,Synchronous,1,1,PreserveInputOrder)(\mathrm{Native}, \mathrm{Synchronous}, 1, 1, \mathrm{PreserveInputOrder}) を d\mathbf{d} とすると、

@luna_thread.map(ℓ,t,n)=@plan.map(ℓ,t,n,d,PreserveInputOrder),@luna_thread.submit_workflow(w,Native)=@workflow.submit(w,Native),@luna_thread.execute_map_i32(x,w,c)=@native.execute_map_i32(x,w,c),\begin{aligned} \texttt{@luna\_thread.map}(\ell, t, n) &= \texttt{@plan.map}(\ell, t, n, \mathbf{d}, \mathrm{PreserveInputOrder}), \\ \texttt{@luna\_thread.submit\_workflow}(w, \mathrm{Native}) &= \texttt{@workflow.submit}(w, \mathrm{Native}), \\ \texttt{@luna\_thread.execute\_map\_i32}(x, w, c) &= \texttt{@native.execute\_map\_i32}(x, w, c), \end{aligned}

が成り立ち、ほかの関数も同様です。ファサードに固有の事実は既定値だけです。カーネルの既定値は w=c=1w = c = 1 で、ネイティブの被覆条件 w≥⌈n/c⌉w \ge \lceil n / c \rceil は次のようになります。

1≥⌈n1⌉=n,1 \ge \left\lceil \frac{n}{1} \right\rceil = n ,

したがって既定の引数では、カーネルは長さ 1 の入力しか受け付けません。

設計上の決定

既定値付きの自由関数

ファサードは、インポートした型のメソッドではなく map、workflow、spawn_task のような自由関数を公開します。型はほかのパッケージに属しており、ここでメソッドを追加できないからです。省略可能な引数が既定値を持つので、よくある呼び出しは短くて済みます。@luna_thread.map("double", @luna_thread.i32_type(), 16) はそれだけで完全かつ有効なプランです。ケイパビリティのヘルパーは各種類を唯一有効なアクセスモードに結び付けるので(channel_capability は MoveOnly、mutex_capability は SynchronizeOnly、shared_read_capability は ReadOnly)、InvalidCapabilityAccess の問題がまとめてなくなります。

バックエンドの振り分けを 1 か所で

submit_workflow はバックエンドで振り分けます。Native は @native.submit_workflow へ、それ以外は @workflow.submit へ行きます。現在はどちらも同じ検証済みの投入を返しますが、バックエンドパッケージを経由する部分が将来実行をつなぐ場所です。直接実行と非同期の関数は無条件に backend/native を呼びます。実行できるバックエンドはそれだけだからです。

ファサードはネイティブ専用

外部関数がネイティブターゲットにしか存在しない backend/native をインポートしているので、ファサードは supported_targets = "native" を宣言しています。どのターゲットでもビルドしなければならないコードは plan、shared、workflow を直接インポートします。これらは実行以外のすべてを提供します。

元のインターフェースから引き継いだカーネルの既定値

カーネルの既定値 w=c=1w = c = 1 は make_policy と同じく最小の有効なポリシー値ですが、上で導いたとおり、被覆条件を満たすのは要素が 1 つのときだけです。これを変えると既存の呼び出しの意味が変わるので、API とチュートリアルのページで両方の引数を渡すよう案内しています。

正しさ / 不変条件

  • 忠実な転送。 どのファサード関数も、同じ引数と既定値に対して下位の関数が返すものをそのまま返します。ファサードは状態を持ちません。
  • 既定値は有効なポリシー。 default_policy() は v1 のポリシー部分集合に属し、引数なしの make_policy は Ok(default_policy()) を返します。
  • C への依存は一部だけ。 C ランタイムに届くのは execute_*、submit_workflow_async、poll_workflow、wait_workflow、drop_workflow だけです。

採用しなかった案

  • パッケージ全体を再エクスポートする。 plan、shared、workflow のすべての名前を再エクスポートすると、それらの API ページと重複します。ファサードは典型的なプログラムに必要な入口だけを残し、パッケージは引き続き直接インポートできます。
  • コンパイル時にターゲットでバックエンドを選ぶ。 そうすればファサードはどのターゲットでもビルドできますが、ターゲットによって振る舞いが黙って変わってしまいます。v1 では代わりにネイティブの要件を明示しています。

境界

ファサードは型を定義せず、自分では何も検証せず、プランを実行せず、スレッドプールを管理せず、JavaScript バックエンドもサポートしません。それらを行う、または行う予定のパッケージに転送するだけです。