WebAssemblyは現在、特定の(通常はJS)インタープリターから任意の言語で記述されたコードを実行するのに非常に優れていますが、複数の任意の言語を
これらの機能の1つは、言語に依存しない型システムです。 そのようなシステムを1つまたは複数WebAssemblyに追加することを提案したいと思います。
余談ですが、以前の機能の議論で、一部の寄稿者は、言語相互運用性をWebAssemblyの設計目標にすべきではないと述べています。 必ずしも優先度の高い目標である必要はないということには同意しますが、長期的に目指す目標だと思います。 したがって、設計目標に入る前に、言語の相互運用性が努力する価値があると思う理由を説明します。
言語間の障壁が低いことの利点は次のとおりです。
wasmユーザー向けのライブラリの増加:これは言うまでもありませんが、言語の相互運用性が向上すると、ライブラリが使用している言語とは異なる言語で記述されている場合でも、ユーザーは既存のライブラリをより頻繁に使用できるようになります。
小さな言語のより簡単な採用:現在の市場では、企業のサポートがない言語が牽引力を得るのは難しいことがよくあります。 新しい言語(そして何年にもわたって洗練されたDのような言語でさえ)は、大規模なエコシステムを持つ言語と競争しなければならず、独自のライブラリの欠如に苦しんでいます。 言語の相互運用性により、PythonやJavaなどの既存のエコシステムを使用できるようになります。
より優れた言語に依存しないツールチェーン:現在、ほとんどの言語には独自のライブラリロードスキームとパッケージマネージャー(または、C / C ++の場合はいくつかの非公式のもの)があります。 言語に依存しないプロジェクトビルダーを作成するのは困難です。これらの言語には微妙な依存関係があり、ABIの非互換性があり、解決するにはプロジェクト全体のモノリシックソリューションが必要になるためです。 robut言語間型システムを使用すると、プロジェクトをより小さなモジュールに分割しやすくなり、npmのようなソリューションで処理できます。
全体として、最初のポイントが最も重要だと思います。 より良い型システムは、他の言語へのより良いアクセスを意味します。つまり、コードを最初から書くのではなく、コードを再利用する機会が増えます。 それがどれほど重要かを誇張することはできません。
そのことを念頭に置いて、言語間型システムが通過する必要のある要件の概要を説明したいと思います。
私は、型システムがモジュール間で渡される関数に注釈を付けるために厳密に使用され、言語が独自の線形メモリまたはマネージメモリをどのように使用するかをチェックしないことを前提に書いています。
ワズム設定で本当に役立つためには、そのような型システムは以下を必要とします:
私が提案するのは、 @ kentonvによるCap'n'ProtoIDLへのバインディングを
これらはWebIDLバインディングと同じように機能します。wasmモジュールは関数をエクスポートし、特別な命令を使用してそれらを型付き署名にバインドします。 他のモジュールはこれらの署名をインポートし、それらを独自の関数にバインドします。
次の疑似構文は、これらのバインディングがどのように見えるかを示すことを目的としています。 これは概算であり、WebIDLの提案に大きく影響を受けており、完全な手順のリストを提供することよりも、技術的な課題に重点を置いています。
Capnprotoバインディング命令はすべて、新しいCap'n'protoバインディングセクションに格納され
この標準では、 capnprotoのスキーマ言語の内部表現が必要になり
`` `Cap'n Proto
struct Person {
名前@ 0 :テキスト;
生年月日@ 3 :日付;
メール@ 1 :テキスト;
電話@ 2 :List(PhoneNumber);
struct PhoneNumber {
番号@ 0 :テキスト;
タイプ@ 1 :タイプ;
enum Type {
mobile @0;
home @1;
work @2;
}
}
}
構造体日付{
年@ 0 :Int16;
月@ 1 :UInt8;
2日目:UInt8;
}
might be represented as
```wasm
(<strong i="32">@capnproto</strong> type $Date (struct
(field "year" Int16)
(field "month" UInt8)
(field "day" UInt8)
))
(<strong i="33">@capnproto</strong> type $Person_PhoneNumber_Type (enum 0 1 2))
(<strong i="34">@capnproto</strong> type $Person_PhoneNumber (struct
(field "number" Text)
(field "type" $Person_PhoneNumber_Type)
))
(<strong i="35">@capnproto</strong> type $Person (struct
(field "name" Text)
(field "email" Text)
(field "phones" (generic List $Person_PhoneNumber))
(field "birthdate" $Data)
))
Capnprotoメッセージは、セグメント(rawバイト)と機能の2種類のデータを渡します。
これらは、WebAssemblyの線形メモリとテーブルに大まかにマップされます。 そのため、webassemblyがcapnprotoメッセージを作成するための最も簡単な方法は、オフセットと長さをセグメントの線形メモリに渡し、オフセットと長さを機能のテーブルに渡すことです。
(ランタイムタイプチェックを回避するために、機能に対してより良いアプローチを考案することができます。)
実際のシリアル化の計算は、グルーコードで行われることに注意してください(グルーコードの生成を参照)。
| 演算子| 即時| 子供| 説明|
| :--- | :--- | :--- | :--- |
| セグメント| off‑idx
len‑idx | | ソースタプルのoff-idx
'番目とlen-idx
'番目のwasm値を取得します。これは、線形メモリのスライスのオフセットと長さとして、両方ともi32
である必要があります。どのセグメントが保存されているか。 |
| captable | off‑idx
len‑idx | | ソースタプルのoff-idx
'番目とlen-idx
'番目のwasm値を取得します。これらは両方ともi32
である必要があり、テーブルのスライスのオフセットと長さとして使用されます。機能テーブルが保存されます。 |
| メッセージ| capnproto-type
機能テーブル| セグメント| 提供された機能テーブルとセグメントを使用して、 capnproto-type
の形式でcapnprotoメッセージを作成します。 |
GCの提案が出る前に、特定の動作を特定することは困難です。 ただし、一般的な実装では、capnprotoバインディングは単一の変換演算子を使用してGCタイプからcapnprotoタイプを取得します。
低レベル型の変換ルールはかなり単純です。i8はInt8、UInt8、boolに変換され、i16はInt16に変換されます。高レベル型は同等のcapnprotoに変換されます。構造体と配列の参照はポインター、不透明な参照に変換されます。機能に変換します。
より完全な提案では、列挙型と共用体の戦略を定義する必要があります。
| 演算子| 即時| 子供| 説明|
| :--- | :--- | :--- | :--- |
| として| capnproto-type
idx | | ソースタプルのidx
'番目のwasm値を取得します。これは参照である必要があり、 capnproto-type
capnproto値を生成します。 |
リニアメモリへのデシリアライズは、そこからのシリアライズとほとんど同じですが、注意点が1つ追加されています。wasmコードは、capnprotoタイプが必要とするメモリの量を事前に知らないことが多く、ホストに何らかの動的メモリ管理方法を提供する必要があります。 。
WebIDLバインディングの提案では、提案された解決策は、アロケーターコールバックをホスト関数に渡すことです。 capnprotoバインディングの場合、動的割り当ては呼び出し側と呼び出し先側の両方で発生する必要があるため、この方法では不十分です。
別の解決策は、着信バインディングマップが2つの着信バインディング式(したがって2つの関数)にバインドできるようにすることです。1つはcapnprotoデータにメモリを割り当て、もう1つは実際にデータを取得します。
管理対象メモリへの逆シリアル化では、反対方向と同じ種類の変換演算子が使用されます。
2つのwasmモジュールを(静的または動的に)リンクする場合、エンベッダーは、両方のモジュールに共通するすべてのcapnprotoタイプ、関数タイプとcapnprotoタイプ間のバインディングを一覧表示し、関数タイプの異なるペアごとにグルーコードを生成する必要があります。
グルーコードは、バインドされたデータのタイプによって異なります。 線形メモリバインディング間のグルーコードは、memcpy呼び出しに要約されます。 管理されたメモリバインディング間のグルーコードは、参照を渡すことに要約されます。 一方、線形メモリとマネージメモリ間のグルーコードには、より複雑なネストされた変換操作が含まれます。
たとえば、Javaモジュールは、引数をGC型として取得して関数をエクスポートし、その関数を型付き署名にバインドできます。 インタプリタは、PythonモジュールとC ++がその型署名をインポートできるようにする必要があります。 C ++バインディングは線形メモリからデータを渡しますが、PythonバインディングはGCメモリからデータを渡します。 必要な変換は、Java、Python、およびC ++コンパイラ
このセクションでは、データを交換する別の方法と、要件セクションで定義されたメトリックでそれらがどのように評価されるかを調べます。
それは力ずくの解決策です。 その欠陥はかなり明白なので、私はそれに多くの時間を費やすつもりはありません。 要件2、4、および6を満たしていません。
それは部分的な解決策です。 wasmモジュールが線形メモリとテーブルのスライスを他のモジュールに渡す方法を定義すると、モジュールの作成者はシリアル化形式(capnproto、protobuffなど)を使用して、構造化グラフをバイトのシーケンスにエンコードし、バイトを渡し、同じフォーマットを使用してデコードします。
1と3を渡し、微調整を加えて2と4を渡すことができます(たとえば、参照をインデックスとしてテーブルに渡します)。 ユーザーがシリアル化タイプを呼び出し元の言語のタイプ定義にエクスポートすることを確認した場合、6を渡すことができます。
ただし、要件5と7では失敗します。2つのGC実装間でバインドする場合は実用的ではありません。 たとえば、Protobufを介してJavaライブラリを呼び出すPythonモジュールは、辞書を線形メモリとしてシリアル化し、そのメモリスライスを渡し、Javaオブジェクトとして逆シリアル化する必要があります。代わりに、で最適化できるいくつかのハッシュテーブルルックアップを作成します。 JITの実装。
また、各ライブラリライターが独自のシリアル化形式(JSON、Protobuf、FlatBuffer、Cap'n Proto、SBE)を使用することを推奨していますが、これは相互運用性には理想的ではありません。 ただし、ツールの慣例で正規のシリアル化形式を定義することで軽減できます。
ただし、線形メモリの任意のスライスを渡す可能性を追加することは、良い最初のステップです。
モジュールが互いにGCオブジェクトを送信することに依存することは可能
このソリューションにはいくつかの利点があります。GCの提案はすでに進行中です。 1、3、4、および7を渡します。GCで収集されたデータは、割り当てるのに費用がかかりますが、渡すのに費用がかかります。
ただし、そのソリューションはCのような言語には理想的ではありません。 たとえば、データをRustモジュールに渡すDモジュールは、データをGCグラフにシリアル化し、グラフをRust関数に渡す必要があります。これにより、データが線形メモリに逆シリアル化されます。 このプロセスは、多くの不要なオーバーヘッドのために、すぐに破棄されるGCノードを割り当てます。
それはさておき、現在のGC提案には、列挙型と共用体のサポートが組み込まれていません。 コンパイラがwasmGCタイプを読み取って理解できない限り、エラー処理はコンパイル時ではなくリンク時または実行時に行われます。
型システムを定義する任意のシリアル化ライブラリは、wasmで機能する可能性があります。
Capnprotoは、ゼロコピーに重点を置いており、参照型に適切にマップされる組み込みのオブジェクト機能があるため、最も適切と思われます。
この最低限の提案をコミュニティグループに提出できるドキュメントに変えるには、次の概念を具体化する必要があります。
それまでの間、私がすでに書いたことについてのフィードバックは大歓迎です。 ここでの範囲はかなり広いので、この提案が答える必要のある質問を絞り込むのを手伝っていただければ幸いです。
これは本当に面白いです! 私はすぐに読んだだけで、最初の考えがいくつかありますが、私の最初のそして最も重要な質問は、ほとんどの言語がすでに提供/使用している既存のFFIメカニズムがWebAssemblyに十分でない理由を尋ねることです。 私が精通している事実上すべての言語には、何らかの形式のC FFIがあり、したがって、今日すでに相互運用することができます。 これらの言語の多くは、これらのバインディングに基づいて静的型チェックを実行することもできます。 さらに、これらのインターフェースにはすでに多くのツールがあります(たとえば、Rustの場合はbindgen
クレート、Erlang / BEAMの場合はerl_nif
など)。 C FFIはすでに最も重要な要件に対応しており、すでに実証され、実際に広く使用されているという重要な利点があります。
5-メモリレイアウト間のブリッジ
理想的な型システムは、セマンティック型を表現し、言語がそれらをメモリ内でどのように解釈するかを決定できるようにする必要があります。 互換性のないメモリレイアウトを持つ言語間でデータを渡すと、常にある程度のオーバーヘッドが発生しますが、同様の言語間でデータを渡すことは理想的には安価である必要があります(たとえば、memcpyが同じジョブを実行できる場合、エンベッダーはシリアル化-逆シリアル化の手順を回避する必要があります)。
セマンティックタイプに互換性がある限り、2つのモジュール間でデータを渡すときの変換作業は、開発者に対して透過的である必要があります。
FFIバリアを越えてデータを渡すときに、あるレイアウトを別のレイアウトに透過的に変換することは、コンパイラのバックエンドや言語ランタイムの仕事のように思えます。C/ C ++ / Rustなどのパフォーマンスに敏感な言語では、まったく望ましくない可能性があります。 特に、FFI間を行き来する予定の場合は、変換に高額な費用がかかる可能性があるため、あらゆる種類の変換を行うよりも、共通のABIを使用する方が常に望ましいと思われます。 プラットフォームの一般的なABI以外のレイアウトを選択することの利点は、それだけの価値があるとは考えられませんが、代替レイアウトの意味を誤解している可能性があることは容易に認めます。
余談ですが、コンパイラ/ランタイムに堅実なFFIツールの負担をかけることには、追加の利点があります。Wasm以外のプラットフォームのFFIの改善は、Wasmに利益をもたらすため、行われた改善は他のプラットフォームにも適用できます。 本質的に正方形から始めて、新しいFFIメカニズムを構築するには、議論は本当に説得力のあるものでなければならないと思います。
先に述べたように、提案の目的を誤解したり、重要なことを見逃したりした場合は、もう一度注意深く読む必要がありますが、しばらくしてから最初の質問をする必要があると感じました。
Apache Arrowはこれにも存在しますが、より高性能なアプリケーションに重点を置いています。
私はここでの一般的な動機に同意すると思います。それは基本的に、 WebIDLバインディングを将来どのように一般化できるかについて私たちが行った議論と一致しています。 実際、説明者の以前のドラフトには、この言語間のユースケースに言及するFAQエントリが含まれていました。
私の主な関心事(およびそのFAQエントリを省略する理由)は範囲です:N言語をバインドする一般的な問題は、特に誰もまだそれを行っていないことを考えると、多くの自由形式の(そしておそらく終了しない)議論を生み出す可能性があります(もちろん、これは鶏と卵の問題です)。 対照的に、Web IDLバインディングによって対処される問題はかなり具体的であり、今日のRust / C ++ですぐに実証できるため、提案されたソリューションを標準化/実装し、熱心にプロトタイプ化/検証するための(重要な)取り組みを動機付けることができます。
しかし、私の希望は、Web IDLバインディングによって、この鶏が先か卵が先かという問題を解決し、次の拡張の波やWebIDL固有ではない新しい何かを動機付ける可能性のある言語間バインディングの経験を積むことができるようになることです。 (現在提案されているように、互換性のあるWeb IDLバインディングを使用する2つのwasmモジュールが相互に呼び出す場合、最適化implは、Cap'n Protoの完全な表現度がなくても、ここで言及している最適化を実行できます。)
私はまだ提案を完全に理解する時間がなかったことを前もって述べなければなりません。
その理由は、その仕事は不可能だと私は信じているからです。 これには2つの基本的な理由があります。
NS。 言語が異なればセマンティクスも異なりますが、必ずしも型注釈に取り込まれるとは限りません。 適切な例として、Prologの評価はC ++の評価とは根本的に異なります。つまり、言語が本質的に相互運用できないという点です。 (Prologの場合、他の多くの言語に置き換えることができます)
NS。 定義上、型システムのLCDは、特定の言語の型言語のすべてをキャプチャすることが保証されているわけではありません。 そのため、言語の実装者は非常に不快な選択をすることになります。つまり、自分の言語をサポートするか、自分の言語の型システムの利点を放棄するかです。 適切な例:Haskellには「型クラス」があります。 型クラスをサポートしないことを含むHaskellの実装は、事実上それを根絶し、使用できなくします。
別の例:C ++でジェネリックをサポートするには、コンパイル時にジェネリックを削除する必要があります。 一方、ML、Java(および他の多くの言語)は、C ++で採用されているアプローチと互換性のないユニバーサル表現の形式を使用します。
一方、エクスポート/インポートされたタイプの2つの式があると、独自の問題が発生するようです。言語システムは、2つの式が何らかの意味で一貫していることを確認することになっていますか? この仕事をするのは誰の責任ですか?
@lukewagnerリンクをありがとう! 私がその文書を読む機会を得たことは間違いなくうれしいです!
この特定の議論では、2つのことが混ざり合っているように思えます。以下の内容のいくつかは、理解を再確認できるように書き出されているので、誤解したり見逃したりした可能性のあるものを自由に指摘してください。
source
およびその他のdest
ソース- > DESTからの呼び出しおよび/またはdest->ソース、それらの間の種類を共有し、source type -> common type -> dest type
。source
からdest
直接行うことができますが、それでも変換のオーバーヘッドが発生します。source
とdest
がABI(C ABIなど)を共有している場合、 source
とdest
は互いに直接呼び出すことができます。 FFIを介して、オーバーヘッドはまったくありません。 これはおそらく実際に最も可能性の高いシナリオです。したがって、私の考えでは、WebIDLまたはそのようなもの(つまり、より広範なホストAPI /環境のセットをサポートするスーパーセット)を活用することには間違いなく利点がありますが、実際には1で概説した問題とサブセットの解決策にすぎません。 FFIが利用できない言語間バインディングを扱う2の。 FFIが利用可能な2のサブセットは、それ自体がオーバーヘッドを発生させないため、代替案よりも明らかに好ましいです。
FFIがオプションである場合でも、IDLを使用する正当な理由はありますか? 明確にするために、私は言及された他のユースケースにIDLを使用することに間違いなく同意しますが、ホストバインディングではなく、言語の相互運用性のコンテキストで具体的に質問しています。
C FFI(例として、最も一般的であるため)とIDLの両方が同時に使用/存在する場合、私が持っているいくつかの追加の質問:
source
言語とdest
言語の両方が、共通のABI(たとえば、可変長配列の共通表現)に従って、同じ基になるメモリ内表現を持つ共有型に異なる型定義を提供する場合)-ホストエンジンは、標準のFFIを使用して相互に安全に呼び出すことができたとしても、IDLディレクティブが存在するという理由だけで、これらのタイプ間の変換を実行しようとしますか?add/2
関数を定義する別の言語で記述された共有ライブラリを呼び出していて、 add/2
size_t
型の2つの引数を予期しているとします。 私の言語は、名目上size_t
必ずしも認識していません。マシン幅の符号なし整数の独自のABI互換表現、 usize
を持っているため、私の言語でのその関数のFFIバインディングはmyを使用します。言語タイプ。 それを考えると、コンパイラはusize
をsize_t
マップするIDLを生成することをどのように知ることができますか。 WebIDLとその前身の詳細、これらすべてがさまざまなホスト(ブラウザーと非ブラウザー)にどのように適合するかなどをまだ掘り下げようとしていることを認めます。見落としている場合は必ずお知らせください。なにか。
@bitwalker
これは本当に面白いです!
気に入ってくれてうれしいです!
しかし、私の最初のそして最も重要な質問は、ほとんどの言語がすでに提供/使用している既存のFFIメカニズムがWebAssemblyに十分でない理由を尋ねることです。
C型システムには、言語間IDLとしていくつかの問題があります。
これは、共有アドレス空間を想定して動作します。共有アドレス空間は安全ではなく、意図的にWebAssemblyに保持されません。 (JS-to-C FFIでの私自身の経験は、実装は安全性と速度を交換する傾向があることを示唆しています)
動的長さ配列、タグ付き共用体、デフォルト値、ジェネリックスなどのネイティブサポートはありません。
参照型に直接相当するものはありません。
C ++は、これらの問題のいくつか(最大の問題ではなく、共有アドレス空間)を解決しますが、IPCではあまり役に立たない概念を多数追加します。 もちろん、いつでもCのスーパーセットまたはC ++のサブセットをIDLとして使用し、その周りのバインディングルールを考案することができますが、その時点では既存のコードからほとんどメリットが得られないため、既存のコードを使用することもできます。 IDL。
特に、FFIを行き来することを計画しているものについては
私はあなたがそれをどのように意味するのかよくわかりませんが、明確にするために:一般的な場合、モジュール間で可変データをやり取りすることは不可能だと思います。 この提案では、他のモジュールがデータを格納する方法に関する情報を持たないモジュール間で、不変のデータを送信し、その見返りに不変のデータを取得する方法の概要を説明します。
プラットフォームの一般的なABI以外のレイアウトを選択することの利点は、それだけの価値があるとは考えられませんが、代替レイアウトの意味を誤解している可能性があることは容易に認めます。
現在のところ、一般的なABIは、線形メモリに格納されているバイトのスライスです。 しかし、将来、GCの提案が実装されると、一部の言語(Java、C#、Python)は線形メモリにほとんどまたはまったく格納されなくなります。 代わりに、すべてのデータをGC構造に保存します。 これらの言語の2つが通信しようとすると、これらの構造をバイトストリームにシリアル化して、すぐに逆シリアル化するだけで、不要なオーバーヘッドが発生します。
@KronicDethありがとう、調べてみます。
ドキュメントをざっと読むと、これはFlatbuffersのスーパーセットのようですが、特にパフォーマンスを向上させることを目的としていますか? いずれにせよ、FlatbuffersやCapnprotoと比較して、WebAssemblyモジュールの相互運用性を独自に支援できる品質は何ですか?
@lukewagner
しかし、私の希望は、Web IDLバインディングによって、この鶏が先か卵が先かという問題を解決し、次の拡張の波やWebIDL固有ではない新しい何かを動機付ける可能性のある言語間バインディングの経験を積むことができるようになることです。
同意しました。 この提案を書いたときの私の仮定は、capnprotoバインディングの実装は、WebIDL提案の実装からのフィードバックに基づいているというものでした。
私の主な関心事(およびそのFAQエントリを省略する理由)は範囲です:N言語をバインドする一般的な問題は、特に誰もまだそれを行っていないことを考えると、多くの自由形式の(そしておそらく終了しない)議論を生み出す可能性があります(もちろん、これは鶏と卵の問題です)。
しかし、capnprotoの実装について議論することには、これほど早い段階でも価値があると思います。
特に、実装が満たすべき/満たそうとする可能性のある要件の概要を説明しようとしました。 言語間型システムが対処しようとする可能性のある一般的なユースケースをリストすることも役立つと思います。
N-to-N問題に関して、私はこれらの解決策に焦点を合わせています:
RPCスタイルのデータ転送についてのみ心配してください。 「ベクトルには「x」、「y」、「z」の3つのフィールドがあり、すべて浮動小数点数である」よりも複雑な情報について、共有の可変データ、クラス、ポインターの有効期間、またはその他のタイプを渡そうとしないでください。
言語とユースケースをデータ処理戦略の「クラスター」にグループ化してみてください。 これらのクラスターの中心に戦略を確立します。 言語コンパイラは特定の戦略にバインドし、インタプリタは残りのNxN作業を行います。
@fgmccabe
その理由は、その仕事は不可能だと私は信じているからです。 これには2つの基本的な理由があります。
NS。 言語が異なればセマンティクスも異なりますが、必ずしも型注釈に取り込まれるとは限りません。 適切な例として、Prologの評価はC ++の評価とは根本的に異なります。つまり、言語が本質的に相互運用できないという点です。 (Prologの場合、他の多くの言語に置き換えることができます)
型クラスをサポートしないことを含むHaskellの実装は、事実上それを根絶し、使用できなくします。
ええ、その考えは完全な「すべての言語と簡単に互換性のある」抽象化を定義することではありません。
とはいえ、ほとんどの言語には、データの構造にいくつかの類似点があると思います(たとえば、「すべての人には名前、メールアドレス、年齢があります」、「すべてのグループには人のリストがあります」と言う方法があります。任意のサイズの」)。
これらの類似点を利用して、モジュール間の摩擦を大幅に減らすことができると思います。 (lukewagnerへの私の答えも参照してください)
NS。 定義上、型システムのLCDは、特定の言語の型言語のすべてをキャプチャすることが保証されているわけではありません。 そのため、言語の実装者は非常に不快な選択をすることになります。つまり、自分の言語をサポートするか、自分の言語の型システムの利点を放棄するかです。
うん。 ここでの経験則は、「共有ライブラリの境界である場合は、capnproto型にし、そうでない場合は、ネイティブ型を使用する」と思います。
一方、エクスポート/インポートされたタイプの2つの式があると、独自の問題が発生するようです。言語システムは、2つの式が何らかの意味で一貫していることを確認することになっていますか? この仕事をするのは誰の責任ですか?
ええ、最初は不変チェックに関するセクションと型の互換性に関するセクションを含めたかったのですが、勇気を失いました。
「誰の責任か」に対する答えは通常「呼び出し先」です(受信したデータが疑わしいと想定する必要があるため)が、呼び出し元が型の不変条件を尊重していることをインタプリタが証明できる場合は、チェックを省略できます。
C型システムには、言語間IDLとしていくつかの問題があります。
明確にするために、私はそれをIDLとして提案していません。 むしろ、バイナリインターフェイス(C ABI)がすでに存在し、明確に定義されており、広範な言語サポートがすでにあることを示唆しています。 つまり、解決される問題が言語間の相互運用を超えない限り、WebAssemblyは別のソリューションを提供する必要がないということです。
これは、共有アドレス空間を想定して動作します。共有アドレス空間は安全ではなく、意図的にWebAssemblyに保持されません。
ですから、ここで誤解の一部が見られると思います。 ここで説明しているFFIには、線形メモリの共有を伴うもの(従来の共有メモリFFI)とそうでないもの(従来のIPC / RPC)の2つのクラスがあります。 私は前者について話してきましたが、あなたは後者にもっと焦点を合わせていると思います。
モジュールを制御しているとき(アプリケーション全体の一部として複数の独立したモジュールをリンクしている場合など)にモジュール間でメモリを共有することは、効率のために望ましいですが、セキュリティを犠牲にします。 一方、FFI専用に指定されたリニアメモリを共有することは可能ですが、それが今日のデフォルトのツールでどれほど実用的かはわかりません。
共有メモリFFI、つまりIPC / RPCを使用しないモジュール間相互運用は、WebIDL、capnproto、またはその他の提案の1つにぴったりのように思われます。これは、それらの基本的な機能だからです。
どちらを選択するかはユースケースに大きく依存するため、どちらの利点も犠牲にしないように2つのカテゴリをブレンドする方法がわかりません。 少なくとも述べたように、どちらか一方しか持てないようですが、両方をサポートできれば理想的だと思います。
動的長さ配列、タグ付き共用体、デフォルト値、ジェネリックスなどのネイティブサポートはありません。
2つの異なることについて話していたことがわかったので、これはおそらく関係ないと思いますが、後世のために:ABIには確かに可変長配列とタグ付き共用体の_表現_がありますが、Cには確かに弱い型システムですが、それは実際には重要ではありません。言語は、C型システムのCFFIを対象としていません。 C ABIが役立つ理由は、言語が相互作用する型システムの概念を持たない可能性のある他の言語と通信するために使用できる共通の分母を提供するためです。 高レベルの型システム機能の欠如は理想的ではなく、FFIを介して表現できるものの種類を制限しますが、制限は、それが何をするかで非常に成功する理由の一部でもあり、ほとんどすべての言語が方法を見つけることができますそのインターフェースを介してそれにさらされているものを表すため、およびその逆。
C ++は、これらの問題のいくつか(最大の問題ではなく、共有アドレス空間)を解決しますが、IPCではあまり役に立たない概念を多数追加します。 もちろん、いつでもCのスーパーセットまたはC ++のサブセットをIDLとして使用し、その周りのバインディングルールを考案することができますが、その時点では既存のコードからほとんどメリットが得られないため、既存のコードを使用することもできます。 IDL。
同意しました。IPC/ RPCの場合、Cはインターフェイスを定義するためのひどい言語です。
現在のところ、一般的なABIは、線形メモリに格納されているバイトのスライスです。
それは確かに私たちが取り組んでいる原始的なものですが、CABIはそれに加えて多くのことを定義しています。
しかし、将来、GCの提案が実装されると、一部の言語(Java、C#、Python)は線形メモリにほとんどまたはまったく格納されなくなります。 代わりに、すべてのデータをGC構造に保存します。 これらの言語の2つが通信しようとすると、これらの構造をバイトストリームにシリアル化して、すぐに逆シリアル化するだけで、不要なオーバーヘッドが発生します。
これらの言語がGCをホストに延期することに飛びつくとは確信していませんが、それは私の側の推測にすぎません。 いずれにせよ、ホストGC管理構造を理解する言語は、capnprotoを使用して表現できるのと同じくらい簡単に、C ABIを使用してそれらの構造の共通表現を決定できます。唯一の違いは、その表現の仕様がどこにあるかです。 とは言うものの、私はGC提案の詳細と、それがホストバインディング提案とどのように関連しているかについては非常に微妙にしか理解していないので、ここでマークから外れている場合は、無視してかまいません。
TL; DR:共有リニアメモリが機能していないモジュールの相互運用については同意すると思います。 しかし、共有メモリはサポートすることが重要だと思います。既存の言語がサポートされているため、CABIはそのユースケースに最も適した選択肢です。 私の希望は、この提案が発展するにつれて、両方をサポートすることです。
必要なのは、バイトのバッファを交換するための最大限に効率的な方法と、言語が形式について合意する方法です。 これを特定のシリアル化システムに修正する必要はありません。 Cap'n Protoがこの目的に最も適している場合、wasmによって義務付けられるのではなく、一般的なデフォルトとして有機的に発生する可能性があります。
もちろん、 FlatBuffersを作成した偏見があります
特定のユースケースを考えると、これら2つよりも好ましい可能性のある他の多くの形式があります。
Cap'n ProtoとFlatBuffersはどちらもゼロコピー、ランダムアクセスであり、フォーマットのネストに効率的であることに注意してください(つまり、別のフォーマットにラップされると、ラップされないよりも効率が悪くなりません)。これは、言語間で考慮すべき実際のプロパティです。コミュニケーション。 「次のバイトはCap'nProtoスキーマXです」など、バッファの非常に正確なバイトレイアウトを指定できるIDLを想像できます。
私は微妙に自己宣伝していますが、スキーマのないFlatBufferのようなFlexBufferを
@aardappel
必要なのは、バイトのバッファを交換するための最大限に効率的な方法と、言語が形式について合意する方法です。 これを特定のシリアル化システムに修正する必要はありません。 Cap'n Protoがこの目的に最も適している場合、wasmによって義務付けられるのではなく、一般的なデフォルトとして有機的に発生する可能性があります。
私は暗黙のポイントを理解しています。そのwasmは、競合他社に1つの標準を課す方法として使用されるべきではなく、IDLが選択されることに個人的に無関心です。
とは言うものの、すべてが言われ終わったとき、ゴムはある時点で道路に出会う必要があります。 wasmが言語間通信を容易にしたい場合(当然のことながら、誰もが共有する仮定ではありません)、「これらのバイトが数値を構成する」以上のことを表現できる標準形式が必要です。 その形式は、capnproto、C構造体、フラットバッファー、またはwasmに固有のものでもかまいませんが、 @ fgmccabeで概説されている理由により、これらすべてのサブセットにすることはできません。
私は微妙に自己宣伝していますが、スキーマのないFlatBufferのようなFlexBufferを紹介するかもしれません。 これは、同じ望ましいゼロコピー、ランダムアクセス、および安価なネストプロパティを備えていますが、JSONの使用方法と同様に、スキーマに同意せずに、codegenを実行せずに言語が通信できるようにします。
私はその魅力を理解しています。図書館を書くとき、これはあなたがほとんどの場合望んでいることではないと思います。 JSONの問題(ひどい解析時間は別として)は、コードのどこかにJSONオブジェクトをインポートして書き込むと、データを使用する前に大量のサニタイズコードを記述してしまうことです。
assert(myObj.foo);
assert(isJsonObject(myObj.foo));
assert(myObj.foo.bar);
assert(isString(myObj.foo.bar));
loadUrl(myObj.foo.bar);
そうしないと、潜在的なセキュリティの脆弱性が発生します。
上記の6-コンパイル時エラー処理も参照してください。
@bitwalker
そうです、私は共有リニアメモリの可能性についてはあまり考えていませんでした。 WebAssemblyの設計に精通している人( @lukewagner ?)が、それがどれほど実現可能であるか、そしてそれがモジュール間呼び出しを実現するための良い方法であるかどうかを議論する必要があります。 また、wasmのメモリレイアウトによって無効にされるFFIが依存する仮定の数にも依存します。
たとえば、FFIは、ホスト言語がCライブラリを使用し、ネイティブライブラリにmalloc関数への直接アクセスを許可するという事実に依存することがよくあります。 相互に疑わしい2つのモジュールのコンテキストで、その戦略をwasmにどれだけうまく変換できますか?
Cap'n Protoの作成者として、このスレッドで何かを言うべきだと思いますが、奇妙なことに、私は多くの意見を持っていることに気づいていません。 興味深いかもしれないし、そうでないかもしれないいくつかの隣接する考えを表現させてください。
私は、JavaScriptとWASMを実行する「サーバーレス」環境であるCloudflareWorkersの技術リーダーでもあります。
ワーカーが相互に通信するためのプロトコルとして、Cap'n ProtoRPCのサポートを検討してきました。 現在、HTTPに制限されているため、バーはかなり低く設定されています。 :)
ワーカーでは、あるワーカーが別のワーカーを呼び出すと、同じプロセスであっても、両方が同じマシンで実行されることがよくあります。 そのため、Cap'n Protoのようなゼロコピーシリアル化は、理論的には物理的に共有できる線形メモリで動作するため、特にWASMワーカーにとっては明らかに非常に理にかなっています。
これが適切であると私たちが考える2つ目の、あまり知られていない理由は、RPCシステムです。 Cap'n Protoは、CapTPをモデルにした、Promiseパイプラインを備えた完全なオブジェクト機能RPCプロトコルを備えています。 これにより、オブジェクト指向のリッチなインタラクションを安全でパフォーマンスの高い方法で簡単に表現できます。 Cap'n Proto RPCは、単なるポイントツーポイントプロトコルではなく、ネットワーク化された任意の数のパーティ間の相互作用をモデル化します。これはかなり大きな問題になると思われます。
一方、WASMの土地では、WASIは機能ベースのAPIを導入しています。 ここには興味深い「相乗効果」があるようです。
とはいえ、Cap'n Protoのいくつかの設計目標は、FFIの特定のユースケースには意味がない場合があります。
要するに、互いに通信している別々のサンドボックスに独立してデプロイされたモジュールがある場合、Cap'nProtoは非常に理にかなっていると思います。 しかし、単一のサンドボックスに同時にデプロイされたモジュールの場合、それはおそらくやり過ぎです。
フィードバックをお寄せいただきありがとうございます!
ポインタは相対的であり、メッセージ内のすべてのオブジェクトは、連続したメモリ、または少なくとも少数のセグメントに割り当てる必要があります。 これにより、ネイティブオブジェクトと比較して使用モデルが大幅に複雑になります。 同じ線形メモリ空間内でFFIを使用する場合、ヒープオブジェクトを失うためにネイティブポインタを問題なく渡すことができるため、このオーバーヘッドは無駄になります。
共有線形メモリアプローチがwasmに対してどれほど実行可能かはわかりません(上記を参照)。
とはいえ、いずれにせよ、相対ポインタからのオーバーヘッドはそれほど悪くないと思います。 WebAssemblyはすでに線形メモリの開始に関連するオフセットを使用しており、実装にはほとんどの場合(私が思うに) ADD
命令を最適化するトリックがあるため、相対ポインターを使用するオーバーヘッドもおそらく最適化できます。
Cap'n Protoメッセージは、スキーマを知らなくてもオブジェクトとサブツリーをロスレスでコピーする機能など、スキーマバージョン間で下位互換性と下位互換性があるように設計されています。 [...] FFIを介して通信する2つのモジュールが同時にコンパイルされる場合、このメタデータは必要ありません。
私はそれが本当だとは思いません。 モジュールが境界で下位互換性のある型を定義する方法があると、wasmは依存関係ツリーモデルを使用できますが、Haskellの依存関係ダイヤモンドの問題はほとんど回避されます。
無意味なオーバーヘッドのより大きな原因は、capnproto xor
がデフォルト値に対して変数を設定する方法です。これは、ゼロバイトが圧縮されている場合に役立ちますが、ゼロコピーワークフローでは逆効果です。
共有線形メモリアプローチがwasmに対してどれほど実行可能かはわかりません(上記を参照)。
ああ、TBH私は議論のその部分に従うのに十分な文脈を持っていないと思います。 共有アドレス空間がない場合は、そうです、Cap'nProtoは非常に理にかなっています。
このようなフォーマットの設計方法についてアドバイスを提供させていただきます。 FWIW今日すでに存在するアプリとの互換性を気にしない場合、Cap'n Protoで変更することがいくつかあります...ただし、ほとんどの場合、低レベルのポインターエンコーディングの詳細です。
無意味なオーバーヘッドのより大きな原因は、capnprotoがその変数をデフォルト値に対して排他的論理和する方法です。これは、ゼロバイトが圧縮されている場合に役立ちますが、ゼロコピーワークフローでは逆効果です。
少し話題から外れていますが、XORは最適化であり、ゼロコピーの場合でもオーバーヘッドではありません。 これにより、すべての構造がゼロで初期化されることが保証されます。つまり、バッファがすでにゼロになっている場合は、オブジェクトの割り当てで初期化を行う必要はありません(とにかくそうなることがよくあります)。 コンパイル時定数に対するXORのコストはおそらく1サイクルですが、あらゆる種類のメモリアクセスのコストははるかに高くなります。
@lukewagner 「線形メモリの共有」の部分について何か考えはありますか?
リニアメモリを共有する場合と共有しない場合の両方のユースケースがあり、最終的にツールは両方をサポートする必要があると思います。
共有は、今日のネイティブアプリが静的リンクまたは動的リンクを使用する場所で意味があります。結合されるすべてのコードが完全に信頼され、その組み合わせがすべて同じツールチェーンを使用するか、厳密に定義されたABIを使用する場合です。 ただし、これはより脆弱なソフトウェア構成モデルです。
メモリを共有しないことは、モジュールのより緩く結合されたコレクションにとって意味があります。古典的なUnixスタイルの設計では、パイプで接続された個別のプロセスにコードが配置されます。 個人的には、これはより構成的なソフトウェアエコシステムにとってよりエキサイティングで未来的な方向性だと思うので、ESM統合を介してESM / npmエコシステムに参加することを目的としたツールチェーンのデフォルトになることを提唱しました(実際、今日のRustのwasm-pack / wasm-bindgenの場合です)。 Web IDLバインディングまたは提案した拡張機能の一般的な近くでメカニズムを使用することは、効率的で人間工学に基づいた型付き(同期または非同期)RPCの形式として私には非常に理にかなっています。
ついにこれを完全に読んだので、この分野での私の考えによく似ています(このコメントボックスは短すぎて含めることができませんか?)。
特に、モジュール間の通信の問題は、スキーマで最もよく説明されていると考えてきました。 つまり、Cap'nProtoシリアル化形式は必要なく、スキーマを使用するだけで済みます。 現時点では、特にCap'nProtoのスキーマ言語については意見がありません。
WASI / ESM + npmの観点から、この形式のソリューションは私にとって最も理にかなっています。 これは、共有ABIに依存せずに、ABIを抽象化したものです。 これにより、基本的に、スキーマ言語APIを使用してインターフェースを記述し、両端にネイティブのように見えるABIを使用してこれらの言語境界を越えて呼び出し、ホストに翻訳を処理させることができます。
特に、これは別のモジュールとの調整を強化するためのユースケースを包含していません。ABIを共有できることが確実にわかっている場合は、実際にはABI、CまたはHaskellのいずれのABIでも使用できます。 問題のすべてのwasmを制御およびコンパイルする場合、それははるかに簡単に解決できる問題です。 モジュール間でスキーマレベルの相互運用を行うようなものが非常に魅力的になるのは、任意の不明なコードをロードしていて、そのソース言語がわからないnpmの場合にのみ発生します。 wasm自体のLCDを使用できるため(ネイティブライブラリと同様のアークをたどり、C ABIを使用すると予測しています)、スキーマ言語でエンコードされた言語のLCDを使用できます。 また、要件2)をソフト要件にすることで、スキーマをより柔軟にすることができます。たとえば、CからRust、Nimに効率的に変換できるはずですが、オーバーヘッドが大きいCからHaskellは大したことではありません。
特に、モジュール間の通信の問題は、スキーマで最もよく説明されていると考えてきました。 つまり、[a]シリアル化形式は必要なく、スキーマを使用するだけで済みます。
私は前者に同意する傾向がありますが、後者が続くかどうかはわかりません。 誰がスキーマを実装しますか? ホストがトランスポートを実行する場合でも、ある時点で、両端で実際に消費/生成されるWasm値/バイトを定義する必要があり、各モジュールは、ホストが理解できる形式に独自のデータを取り込む必要があります。 複数の形式が利用できる場合もありますが、それでもシリアル化形式と同じで、少しだけ高レベルです。
CからRust、Nimに効率的に変換できるはずですが、オーバーヘッドが大きいCからHaskellは大したことではありません。
おそらくそうではありませんが、その影響に注意する必要があります。 Cのような言語を特権化するということは、オーバーヘッドが発生するため、HaskellがHaskellモジュールにこの抽象化を使用しないことを意味します。 つまり、独自のライブラリの同じ「npm」エコシステムに参加しないということです。
そして、ここでの「Haskell」は、ほとんどすべての高級言語の代用にすぎません。 大多数の言語はCのようなものではありません。
より良い解決策があるとは言いませんが、通常のFFIスタイルの一方向の相互運用性を超えて、単一のABIまたはスキーマの抽象化が一般的な言語集団にとってどれほど効率的で魅力的であるかについて現実的にとどまる必要があると思います。 特に、汎言語パッケージエコシステムが過度に現実的な結果であるとは確信していません。
Cのような言語を特権化するということは、オーバーヘッドが発生するため、HaskellがHaskellモジュールにこの抽象化を使用しないことを意味します。 つまり、独自のライブラリの同じ「npm」エコシステムに参加しないということです。
そして、ここでの「Haskell」は、ほとんどすべての高級言語の代用にすぎません。 大多数の言語はCのようなものではありません。
いくつかの特定のユースケースを与えることができますか? 理想的には、Haskellまたはシリアル化スキーマに翻訳するのが難しい他の言語の既存のライブラリ?
それは主にユーティリティライブラリとビジネスライブラリに帰着すると思います。 たとえば、コンテナ、ソートアルゴリズム、および言語のジェネリックに依存するその他のユーティリティは、wasmに適切に変換されませんが、パーサー、GUIウィジェット、およびファイルシステムツールは変換されます。
@PoignardAzur 、それらを翻訳することは難しくありませんが、各クロスモジュール呼び出しの両端ですべての引数/結果をコピー(シリアル化/逆シリアル化)する必要があります。 明らかに、言語内部のライブラリ呼び出しごとにそのコストを支払う必要はありません。
特にHaskellでは、コピーが怠惰のセマンティクスと互換性がないという追加の問題もあります。 他の言語では、ステートフルデータと互換性がない場合があります。
誰がスキーマを実装しますか? ホストがトランスポートを実行する場合でも、ある時点で、両端で実際に消費/生成されるWasm値/バイトを定義する必要があり、各モジュールは、ホストが理解できる形式に独自のデータを取り込む必要があります。 複数の形式が利用できる場合もありますが、それでもシリアル化形式と同じで、少しだけ高レベルです。
ホストはスキーマを実装します。 スキーマはバイトをまったく記述しておらず、それを実装の詳細にします。 これは、WebIDLバインディングの提案の設計から借用しています。興味深いビットは、C構造体からWebIDLタイプへの変換にあります。 この種の設計では、WebIDLタイプの代わりにWasm抽象インターフェイスタイプ(頭字語:WAITをお勧めします)を使用します。 WebIDLの提案では、データが「WebIDLに変換」されたときに、データのバイナリ表現を必須にする必要はありません。これは、途中で停止することなく、wasmからブラウザーAPIに直接移行できるようにするためです。
Cのような言語を特権化するということは、オーバーヘッドが発生するため、HaskellがHaskellモジュールにこの抽象化を使用しないことを意味します。
ああ、100%同意します。 それをより明確にするために例を終えるべきでした:一方、HaskellからElm、C#は同様に効率的です(wasm gcタイプを使用すると仮定)が、C#からRustにはオーバーヘッドがある可能性があります。 言語パラダイムを飛び越えるときにオーバーヘッドを回避する方法はないと思います。
特定の言語に対して十分な人間工学とパフォーマンスを発揮できない場合、インターフェースを使用する価値が低くなり、エコシステムに参加できないため、言語の特権を回避する必要があるというあなたの観察は正しいと思います。 。
タイプを抽象化し、ワイヤー形式を指定しないことで、ホストに最適化の余地を与えることができると思います。 「Cスタイルの文字列は効率的」というのは目標ではないと思いますが、「Cスタイルの文字列について推論したい言語が効率的にできる」というのが目標です。 または、1つの形式が祝福されるべきではありませんが、特定の互換性のあるコールチェーンが効率的であり、すべてのコールチェーンが可能である必要があります。
コールチェーンとは、次のことを意味します。
そして、ここでの「Haskell」は、ほとんどすべての高級言語の代用にすぎません。 大多数の言語はCのようなものではありません。
はい、それがHaskellを具体的な言語として使用することの背後にある私の意図でした。 (NimはGCも多用しているため、おそらくCのような言語の悪い例でしたが)
-
私が抽象型について考えてきたもう1つの方法は、IRとしてです。 LLVMが多対1対多の関係(多くの言語-> 1つのIR->多くのターゲット)を記述するのと同じように、wasm抽象型は言語+ホストの多対多のマッピングを仲介できます->言語+ホスト。 この設計空間の何かがN ^ 2マッピングの問題を取り上げ、それをN + Nの問題に変えます。
ホストはスキーマを実装します。
まあ、それだけでは十分ではありません。ホストがデータを見つけられるように、各モジュールは何かを実装
それを行ったとしても、たとえばネットワークやファイルベースの永続性を介して単一のエンジン間でデータを転送する必要があるアプリケーションの場合など、シリアル化形式を定義することは依然として有用です。
まあ、それだけでは十分ではありません。ホストがデータを見つけられるように、各モジュールは何かを実装する必要があります。 ホストがCレイアウトを期待している場合は、このCレイアウトを定義する必要があります
ホストは何も期待するべきではありませんサポートする必要があります。 より具体的には、説明的な例としてwebidl-bindings提案を使用すると、 utf8-cstr
とutf8-str
があり、それぞれi32 (ptr)
とi32 (ptr), i32 (len)
を取ります。 それらの間を具体的にマッピングできるようにするために、仕様で「ホストはこれを内部的にC文字列として表す」ことを義務付ける必要はありません。
したがって、各モジュールは何かを実装しますが、データの表現を抽象データ/スキーマレイヤーで表現する必要はありません。これにより、そのデータレイアウトを抽象化するプロパティが得られます。
さらに、これは、具象wasmタイプと抽象中間タイプの間でマッピングされるバインディングレイヤーで拡張可能です。 Haskellサポート(文字列をcharsのconsリストとcharsの配列の両方としてモデル化する)を追加するために、 utf8-cons-str
とutf8-array-str
バインディングを追加できます。 gcプロポーザル構文) (type $haskellString (struct (field i8) (field (ref $haskellString))))
および(type $haskellText (array i8))
。
つまり、各モジュールがデータの発信方法を決定します。 抽象型+バインディングにより、モジュールが同じデータを表示する方法間の変換が可能になります。単一の表現を何らかの形で標準的なものとして祝福することはありません。
抽象型(のサブセット)のシリアル化形式は便利ですが、スキーマ形式のコンシューマーとして実装できます。これは直交する懸念事項だと思います。 FIDLは、ネットワークを介して転送できるタイプのサブセットのシリアル化形式を持っていると思います。不透明なハンドルの実体化を禁止し、システム内での不透明なハンドルの転送を許可します(IPCはい、RPCいいえ)。
あなたが説明していることは、私が考えていたものにかなり近いものですが、大きな注意点が1つあります。スキーマには、可能な表現の数が少なく、固定されている必要があります。 異なる表現間のブリッジングはN * Nの問題です。つまり、VMライターに過度の負担をかけないように、表現の数を少なくする必要があります。
したがって、Haskellサポートを追加するには、カスタムバインディングを追加するのではなく、既存のバインディングを使用する必要があります。
いくつかの可能な表現:
各言語は異なり、極端な外れ値がいくつかありますが、かなり少数のカテゴリにかなり多数の言語を当てはめることができるという考えです。
したがって、Haskellサポートを追加するには、カスタムバインディングを追加するのではなく、既存のバインディングを使用する必要があります。
考えている既存のバインディングの粒度のレベルによって異なります。 可能な各バインディングをエンコードするN <-> N言語は2 * N * Nですが、N <-> IRは2 * Nであり、さらにN <-> [一般的なバインディングスタイル] <-> IRと言えば、一般的な形式の数はkで、2 * kを話します。ここで、k <Nです。
特に、私が説明するスキームでは、Schemeを無料で入手できます( utf8-cons-str
を再利用します)。 Javaが文字列をchar配列としてもモデル化する場合、それはutf8-array-str
バインディングです。 Nimが内部でstring_viewsを使用する場合、 utf8-str
。 ZigがCABIに準拠している場合、 utf8-cstr
。 (Java / Nim / ZigのABIを知らないので、以前は具体的な例として言及しませんでした)
したがって、可能性のある言語ごとにバインディングを追加する必要はありませんが、IRタイプごとにいくつかのバインディングを追加して、大多数の言語をカバーすることができます。 ここでの意見の相違の余地は、「少数」のバインディングの数、スイートスポット、言語のABIをサポートするかどうかの基準をどの程度厳しくするかということだと思います。
これらの質問に対する具体的な回答はありません。 デザインスペースをわかりやすく説明するために、具体的な例をたくさん挙げようとしています。
また、1つのスタイルのデータの特権を回避するために、抽象型ごとに複数のバインディングを絶対に指定する必要があると断言します。 文字列に公開する唯一のバインディングがutf8-cstr
である場合、C-ABIを持たないすべての言語はその不一致に対処する必要があります。 VMの書き込みの複雑さを増しても、少なからず問題はありません。
エコシステムでの総作業量はO(VMの労力+言語の実装の労力)であり、これらの用語は両方とも、N =言語の数で何らかの形でスケーリングされます。 M =エンベッダーの数、k =バインディングの数、a =特定の言語が実装する必要のあるバインディングの平均数(a <= k)とします。 少なくとも、M + Nの個別のwasm実装があります。
すべてのN言語が他のすべてのN言語でABIFFIを独立して実装するという単純なアプローチは、O(M + N * N)です。 これは、ネイティブシステムでの結果であり、O(N * N)がネイティブシステムと変わらない結果につながるという強いシグナルです。
すべてのVMがすべてのN * Nバインディングを実装する必要がある2番目の素朴なアプローチ:O(M * N * N + N)、これは明らかにさらに悪いです。
私たちが提案しようとしているのは、抽象的な言語間でマップされ、すべての言語にマップされるk個のバインディングがあるということです。 これは、VMごとにk個の作業を意味します。 言語ごとに、バインディングのサブセットを実装するだけで済みます。 総仕事量はM * k + N * a、つまりO(M * k + N * k)です。 k = Nの場合、VM側は「唯一の」M * Nであるため、特定のVMについては、言語数が「唯一」線形であることに注意してください。 明らかに、k << Nが必要ですが、それ以外の場合はO(N * N)であり、最初の解よりも優れているわけではありません。
それでも、O(M * k + N * k)ははるかに口当たりが良いです。 kがO(1)の場合、実装の数でエコシステム全体が線形になります。これは、関連する作業量の下限です。 より可能性の高い限界は、kがO(log(N))であるということですが、これはまだかなり満足しています。
これは長い言い方ですが、この機能のVMの複雑さを一定の要因で増やしてもまったく問題ありません。
大多数の言語をカバーするために、IRタイプごとにいくつかのバインディングを追加できます。
これは、私が信じている重要な根本的な仮定であり、単に真実ではないと私は信じています。 私の経験では、(少なくとも!)言語の実装と同じ数の表現の選択肢があります。 そして、それらは任意に複雑にすることができます。
さまざまなエンコーディング、異種ロープなどを含む、文字列の表現が数十(!)あるV8を取り上げます。
Haskellのリストは怠惰であるため、Haskellのケースはあなたが説明するよりもはるかに複雑です。つまり、文字列内のすべての文字に対して、サンクを呼び出す必要があるかもしれません。
他の言語では、文字列の長さに面白い表現を使用するか、明示的に保存せずに計算する必要があります。
これらの2つの例は、宣言型データレイアウトがそれをカットしないことをすでに示しています。多くの場合、ランタイムコードを呼び出すことができる必要があり、ランタイムコードには独自の呼び出し規約がある場合があります。
そして、それは単なる文字列であり、概念的にはかなり単純なデータ型です。 言語が製品タイプ(タプル/構造体/オブジェクト)を表す方法が無数にあることについても考えたくありません。
そして、これらすべてのデータ構造を作成できる必要がある受信側があります。
ですから、「大多数の言語」のサポートに少しでも近づくことは、まったく非現実的だと思います。 代わりに、すでに任意のものの大規模な動物園を成長させながら、私たちはいくつかの特権を開始します。 それは複数のレベルで致命的なようです。
私の経験では、(少なくとも!)言語の実装と同じ数の表現の選択肢があります。 そして、それらは任意に複雑にすることができます。
同意します。 ほとんどの言語のデータの内部表現を何らかの形でカバーするタイプを設計しようとすると、単純に扱いにくくなり、エコシステムが過度に複雑になると思います。
結局のところ、データに関しては、言語間の最小公分母は1つだけです。それは、「バッファー」の分母です。 すべての言語がこれらを読み取って構築できます。 それらは効率的でシンプルです。 はい、彼らは彼らのコンテンツに直接対処できる言語を好みますが、それは(怠惰な)consセルを何らかの形で同じレベルのサポートに昇格させることによって解決される不平等ではないと思います。
実際、ポインタとlenのペアという1つのデータ型だけで非常に遠くまで行くことができます。 次に、それらのバイトに何が含まれているかを示す「スキーマ」が必要です。 UTF-8に準拠することを約束しますか? 最後のバイトは常に0が保証されていますか? 最初の4/8バイトの長さ/容量フィールドはありますか? これらのバイトはすべて、WebGLに直接送信できるリトルエンディアンフロートですか? これらのバイトは、おそらく既存のシリアル化形式のスキーマXですか? NS?
これらすべての質問に答えることができる非常に単純なスキーマ仕様を提案します(既存のシリアル化形式ではなく、より低レベルで、より単純で、wasmに固有のもの)。 これらのバッファを指定された形式で効率的に読み書きすることは、各言語の負担になります。 中間のレイヤーは、コピーによって、または可能な場合は参照/ビューによって、処理せずに盲目的にバッファーを通過できます。
これは、私が信じている重要な根本的な仮定であり、単に真実ではないと私は信じています。 私の経験では、(少なくとも!)言語の実装と同じ数の表現の選択肢があります。 そして、それらは任意に複雑にすることができます。
私はこれが重要な根底にある仮定であることに同意します。 私はそれが真実ではないことに同意しませんが、私は明確にしたとは思わない意味的なニュアンスのためだと思います。
バインディングは、すべての言語表現に完全にマップすることを意図したものではなく、すべての言語に十分にマップする必要がある
これは、エンコーディングに関係なく、これをまったく扱いやすくする重要な基本的な仮定です。 @aardappelが反対方向に進み、実際にバイトをデコード可能なバッファに再圧縮するという提案も、特定のプログラムのセマンティクスの不可逆エンコーディングであり、他のプログラムよりも不可逆であるという前提に基づいています。
Haskellのリストは怠惰であるため、Haskellのケースはあなたが説明するよりもはるかに複雑です。つまり、文字列内のすべての文字に対して、サンクを呼び出す必要があるかもしれません。
私は実際にそれを忘れていましたが、それは重要ではないと思います。 目標は、モジュールの境界を越えてすべてのセマンティックなニュアンスを維持しながら、Haskell文字列を表すことではありません。 目標は、Haskell文字列を値によってIR文字列に変換することです。 これには、必然的に文字列全体の計算が含まれます。
これらの2つの例は、宣言型データレイアウトがそれをカットしないことをすでに示しています。多くの場合、ランタイムコードを呼び出すことができる必要があり、ランタイムコードには独自の呼び出し規約がある場合があります。
それをモデル化する方法は、バインディングをどのように指定するかに関係なく(または、バインディングに何かを指定する場合でも)、ユーザーランドでそれを処理することです。 言語の型の表現がバインディングに直接マップされていない場合は、マップされている表現に変換する必要があります。 たとえば、Haskellの文字列が実際に(type $haskellString (struct (field i8) (field (func (result (ref $haskellString))))))
として表されている場合は、厳密な文字列に変換してSchemeのようなバインディングを使用するか、Text配列に変換してJavaのようなバインディングを使用するか、またはCFFIStringを使用し、Cのようなバインディングを使用します。 複数の不完全なバインディングタイプを持つことの価値提案は、それらのいくつかは他のものよりもHaskellにとって扱いにくいということであり、コンパイラーを変更することなくWasm-FFIタイプを構築することが可能です。
そして、それは単なる文字列であり、概念的にはかなり単純なデータ型です。 言語が製品タイプ(タプル/構造体/オブジェクト)を表す方法が無数にあることについても考えたくありません。
そして、これらすべてのデータ構造を作成できる必要がある受信側があります。
混乱しているのですが、「言語間の束縛は完全に不可能なので、絶対にやるべきではない」と言っているようですが、あなたの言っていることは、「信じられない」という言葉に沿っていると思います。ここで説明するアプローチは扱いやすいです」と、はるかに合理的に思えます。 特に、この議論の行に対する私の異議は、それが前進する道を記述していないということです。 この問題が「非常に難しい」ことを考えると、私たちは何をしますか?
代わりに、私たちはいくつかの特権を開始します
ほぼ確実に。 問題は、最適にサポートされているいくつかの言語にどの程度の特権があるかということです。 人間工学に基づいた解決策を見つけるには、恵まれない言語にはどのくらいの余裕がありますか?
すでに恣意的なものの大きな動物園を育てている間。
それが何を意味するのかわかりません。 私の恣意的な解釈は「どの言語をサポートするか」ですが、それは「少数の特権」と同じであり、二重にカウントされます。 したがって、これは複数ではなく、その1つのレベルでのみ致命的な欠陥があります:D
@aardappelの短いバージョンは、宣言型の抽象的アプローチが失敗した場合のバックアップ計画です。まったく逆の方向に進み、シリアル化形式を記述します。 Web自体は非常に低い最小公分母であるため、ほぼ完全にテキスト上に構築されていることが観察されています。 テキストはすべてのツールで簡単に理解できるので、その上にWebのようなものが可能です。
そのアプローチを扱いにくくする可能性のあるバッファ内のデータに関して私が抱えている最大の懸念は、参照型をどのように処理できるかということです。 私が持っている最善のアイデアは、テーブルを共有し、それらにインデックスをシリアル化することですが、それが実際にどれだけうまく機能するかについての全体像はわかりません。
@ jgravelle-google多分参照型は別々に保つべきですか? したがって、特定の関数の生の署名はref ref i32 i32 i32 i32
ある可能性があります
(余談として、私はHaskellのに慣れていないんだけど、文字列のアイデアは、文字の怠惰なリストは私の心を吹くと。今までに何かをするための最も効率的または便利な方法をバイトのリストにリンクされている場合は?私はHaskellはにすべてのものを必要としていることを取得します不変であり、リンクリストを使用すると安価な先頭に追加できますが、リンクリストを使用せずに不変の文字列を取得および操作できます。)
そのアプローチを扱いにくくする可能性のあるバッファ内のデータに関して私が抱えている最大の懸念は、参照型をどのように処理できるかということです。 私が持っている最善のアイデアは、テーブルを共有し、それらにインデックスをシリアル化することですが、それが実際にどれだけうまく機能するかについての全体像はわかりません。
それが私がcapnprotoをエンコーディングとして提案した理由の1つです。 参照テーブルは多かれ少なかれ組み込まれています。
いずれにせよ、データグラフのどこにでも配置できる、第一級市民としての参照型を持つように選択した形式が必要です。 (例:オプション、配列、バリアントなど)
皆様からのフィードバックに感謝します。
ほぼ同じ議論をほとんど変更せずに何度も読み直し始めていると思うので、提案を更新して、みんなの懸念に対処しようと思います。 更新された提案を書き終えたら、おそらく新しい問題からやり直すでしょう。
これまでのフィードバックを要約すると、次のようになります。
どのシリアル化フォーマットがwasmに最適であるかについては、コンセンサスがほとんどありません。 代替手段には、FlatBuffers、生のポインターを使用したC ABI構造体グラフ、オーダーメイドのwasm IDL形式、または上記の組み合わせが含まれます。
提案には、より強力なネガティブスペースが必要です。 提案の範囲によって混乱し、促進することを意図した複数のリーダー(静的リンクと動的リンク、モジュールからホスト、ホストからホスト、可変データの共有と不変メッセージの受け渡し)。
@lukewagnerは、ESM統合と組み合わせた場合に、相互に信頼できないモジュールを接続するモジュールシステムの可能性にいくらかの熱意を表明しています。 提案の次の反復は、その可能性を拡張する必要があります。 特に、下位互換性のある型システムを使用すると、依存関係の菱形継承問題の矢面に立たされることを避けながら、wasmがnpmのような依存関係ツリーモデルを使用できるようになると思います。
機能、つまり、返されたり渡されたりすることはできるが、生データからは作成されない不透明な値については、ほとんどフィードバックがありません。 私はそれを、次のイテレーションがそれらにもっと重点を置くべきであることのしるしとしてとらえています。
何人かの読者は、言語間型システムの実現可能性について懸念を表明しました。 これらの懸念はやや曖昧で定義が困難です。これは、主題が非常に抽象的であるため、これまでの提案自体がかなり曖昧であり、 @ lukewagnerの鶏が先か卵が先かという問題を反映しているためです。 具体的な障害状態は次のとおりです。
次の提案の反復では、これらの懸念に何らかの方法で対処する必要があります。
特に、議論はいくつかのストローマンの例から多くの利益を得ると思います。 これらの例には、n * nの問題を説明するために、異なる言語(C ++やJavaなど)で異なる言語で記述され、異なる言語(RustやPythonなど)の少なくとも2つの最小限のプログラムによって消費される少なくとも2つのライブラリが含まれます。それに対処するための戦略。
また、読者が指摘しているように、この提案は現在、型スキーマのアイデアとその表現のアイデアを絡ませています。 提案は表現形式の要件をレイアウトするという公正な仕事をしますが、最初に抽象型スキーマの要件をレイアウトする必要があります。
とにかく、これまでこの議論に参加してくれたすべての人に改めて感謝します。 私はできるだけ早くより徹底的な提案を考え出すように努めます。 ここの誰かが私がそれを書くのを手伝うことに興味があるなら、必ず私に電子メールを送ってください!
@ jgravelle-google:
それをモデル化する方法は、バインディングをどのように指定するかに関係なく(または、バインディングに何かを指定する場合でも)、ユーザーランドでそれを処理することです。
はい、同意します。私の主張は@aardappelの主張と似ています。とにかくそれが一般的に行われなければならないことである場合は、単にそれを受け入れ、いくつかの奇妙なケースを改善するためにその場限りのことを試みないでください。 Userlandは、Wasmの他のすべての精神において、変換が属する場所です。
混乱しているのですが、「言語間のバインドは完全に不可能なので、まったく試してはいけません」と言っているようです。
言語間のデータ相互運用のためにDDL(タイプスキーム)を定義することが完全に望ましいと思います。 変換をWasmに組み込むのは扱いにくいと思います。 変換はユーザーランドで実装する必要があります。 バインディングレイヤーは、ユーザーコードが生成/消費する必要のあるフォーマットを規定するだけです。
すでに恣意的なものの大きな動物園を育てている間。
それが何を意味するのかわかりません。 私の恣意的な解釈は「どの言語をサポートするか」ですが、それは「少数の特権」と同じであり、二重にカウントされます。
申し訳ありませんが、私はこれらの変換についてひどく標準的なものは何もないと思うことを意味しました。 したがって、それらの選択は「任意」であり、個々のセマンティクスも同じです。
参照型をどのように処理できますか?
ああ、それは良い質問です。 FWIW、私たちは現在、DfinityプラットフォームのIDL / DDLについてこの問題に対処しようとしています。 anyrefしかない限り、解決策はかなり単純です。シリアル化形式では、透過データを投影するメモリスライスと、含まれる参照を投影するテーブルスライスの2つの部分が定義されます。 複数の参照型は、それに応じて複数のテーブルスライスを必要とします。 トリッキーな質問は、ref型のセットが有限でなくなったらどうするかです(たとえば、型付き関数参照の場合)。
GCタイプを取得したら、GC値としてデータを提供する別の方法があるはずです。 その場合、参照は自由に混在できるため、問題にはなりません。
@PoignardAzur :
余談ですが、私はHaskellに慣れていませんが、文字列を文字の怠惰なリストとして考えると、頭がおかしくなります。
ええ、最近は間違いだと広く考えられていると思います。 しかし、それは「単純な」データ型に対してさえどれほどの多様性があるかを示しています。
@rossberg
言語間のデータ相互運用のためにDDL(タイプスキーム)を定義することが完全に望ましいと思います。 変換をWasmに組み込むのは扱いにくいと思います。 変換はユーザーランドで実装する必要があります。
私は同意し、それに加えて、wasmが他のプラットフォームよりも言語間ソリューションの必要性が高いとは思わないので、wasm仕様に何かを追加することに懐疑的です。このようなソリューションを実装する能力は、他のプラットフォームよりも優れています。 ここでのwasmについて私にとって明らかに特別なことは何もないので、 @ aardappelが述べたように、この領域で標準ソリューションよりも優れたソリューションを実行できる理由がわかりません。 (しかし、すべてのプラットフォームでそうであるように、ユーザースペースでの実験は非常に興味深いと思います!)
少なくともWeb上でwasmが持っている特別なことの1つは、文字列や配列などのJavaScript / WebAPIタイプです。 それらと対話できることは明らかに重要です。
wasmには他のプラットフォームよりも言語間ソリューションの必要性が高いとは思いません
そうだと思います。 デフォルトでWebで使用されるということは、コードがさまざまなコンテキストで実行できること、および実行されることを意味します。 <script src="http://some.other.site/jquery.js">
と同じように、wasmライブラリをクロスオリジンの方法で組み合わせる人々を見てみたいと思います。 Webが提供する一時性と構成可能性の特性により、外部モジュールとインターフェイスできることの付加価値は、ネイティブシステムでこれまでにないほど高くなっています。
そして、wasmが他のプラットフォームよりもそのようなソリューションを実装する能力が高いとは思いません。
そして、私はそうだと思います。 wasmは埋め込み/ホストで実行されるため、コード生成は効果的に抽象化されます。 そのため、VMには、ネイティブシステムでは不可能な高レベルの構造をサポートするためのツールと余裕がはるかに多くあります。
したがって、このスペースの何かは他のシステムよりも価値があり、可能性が高いと思います。そのため、このコンテキストではwasmが特別です。 私にとって、JS相互運用機能は、wasmモジュールが非常に異なる世界観を持つ外部のものと通信できる必要があるというより一般的な概念の特殊なケースです。
このための前進の道は、今のところこれを完全にツールレベルの相互運用にプッシュし、勝利のフォーマットが得られるまで標準化を延期することです。 したがって、特定のインターフェイス形式を使用して主要なwasmパッケージマネージャーのエコシステムを実現することが目標である場合(そして、それはNPMまたはWAPMですか、それともまだ作成されているパッケージマネージャーですか?)、標準化とは関係なく発生する可能性があります。 理論的には、パフォーマンスを向上させるために、人々がすでに行っていることを標準化できますが、人間工学はユーザーランドに実装できます。 勝った言語間フォーマットが最適化に役立たないというリスクがあり、最終的には次善のデファクトスタンダードになります。 後で標準化することを意図してフォーマットを設計できる場合(カスタムセクションの宣言型スタイルでほとんど十分ですか?)、そのリスクを取り除きますが、パフォーマンスの向上も遅らせます。 私にとって、パフォーマンスはこの種のことをするのにそれほど刺激的ではない動機の1つなので、他の人は同意しないかもしれませんが、私はそれでかなり大丈夫です。
(そして、それはNPMまたはWAPMですか、それともまだ作成されていないパッケージマネージャーですか?)
WAPMが実行可能なパッケージマネージャーになるのは時期尚早だと思います。 wasmパッケージマネージャーが実現可能になる前に、ESM統合、WASI、および何らかの形式の言語間バインディングなどの機能を標準化する必要があります。
現状では、WAPMにも依存関係管理はないと思います。
wasmには他のプラットフォームよりも言語間ソリューションの必要性が高いとは思いません
そうだと思います。 デフォルトでWebで使用されるということは、コードがさまざまなコンテキストで実行できること、および実行されることを意味します。 同じように
変換はユーザーランドで実装する必要があります。 バインディングレイヤーは、ユーザーコードが生成/消費する必要のあるフォーマットを規定するだけです。
それは私にとって良い要約です。
新しいドラフトを書いている間、このスレッドのすべての人に未解決の質問があります。
WebAssemblyにコンパイルされた場合、どの言語からでも使用できるようにしたい既存のライブラリはありますか?
私は基本的に、設計の基礎となる潜在的なユースケースを探しています。 私は自分のもの(具体的には、React、Bulletエンジン、プラグインシステム)を持っていますが、もっと多くの例を使って作業したいと思います。
@PoignardAzur Cの多くの言語は、同じPerl互換正規表現(PCRE)ライブラリを使用しますが、ブラウザの埋め込みでは、おそらくJSのRegexAPIを使用する必要があります。
@PoignardAzurBoringSSLとlibsodiumが思い浮かびます。
また、Cap'n Proto RPCの実装ですが、これは奇妙なものです。Cap'nProtoの_serialization_レイヤーは、ほとんどが慣用的でインラインである必要がある幅が広いが浅いAPIレイヤーであるため、実際には各言語で個別に実装する必要があります。 -フレンドリー。 RPC層であるOTOHは、狭いですが深いです。 原則として、capnpでエンコードされたバイト配列参照をFFI境界を越えて渡すことにより、任意の言語のシリアル化実装の背後でC ++ RPC実装を使用できるはずです。
提案されていることを実行するには、Web Assembly自体がすでに存在しているため、かなり侵襲的な変更が必要になると思いますが、おそらくそれだけの価値があるかもしれません。
SmallTalkの世界では、あらゆるタイプのあらゆるサイズをかなり効率的に表すことができる効率的なシリアル化プロトコルである状態レプリケーションプロトコル(SRP)の開発に役立つ可能性のある、このような取り組みで前向きな経験があったことに注意してください。 VMまたはFPGAのネイティブメモリレイアウトにすることを検討しましたが、試してみませんでした。 私はそれが少なくとも1つの他の言語であるSqueakに移植され、良い結果が得られたことを知っています。 確かに、この提案の問題、課題、経験と強く重複していると読んでおくべきことがあります。
Web IDLがバインディング言語としてのデフォルトの提案であった理由を理解しています。これは、Webの歴史的で、なんとなく成熟したバインディング言語です。 私はその決定を大いに支持します、そして私が同じことをしたであろうことは非常にありそうです。 それにもかかわらず、それが他のコンテキスト(理解、他のホスト/言語)にほとんど適合しないことを認識するかもしれません。 Wasmは、ホストに依存しない、または言語/プラットフォームに依存しないように設計されています。 私は成熟したWebテクノロジーを使用し、Web以外のシナリオのユースケースを見つけるというアイデアが好きですが、Web IDLの場合、それは本当にWebに結びついているようです。 これが、私がここでそれらの会話を非常に密接にフォローしている理由です。
https://github.com/WebAssembly/webidl-bindings/issues/40を開いたのですが、言及がない(または見逃した)ので、ここで質問することになりました。
バインディングストーリー全体では、バインディングを生成する責任がある「誰」が明確ではありません。
どちらも有効だと思います。 また、Web IDLの場合、いくつかの制限が表示されているようです(上記のリンクを参照)。 たぶん、私はプロセスの重要なステップを逃したばかりなので、私のメッセージを忘れることを検討してください。
Web IDLを「再フォーカス」してWeb中心ではないようにすることが目的であっても、現時点では非常にWeb中心です。 そして、代替案を提案する提案が出てくるので、このスレッド。 したがって、私は潜在的な断片化を懸念しています。 理想的には(これがWasmのこれまでの設計方法です)、バインディングを含むWasmモジュールがあれば、どこでもそのまま実行できます。 Web IDL、Cap'n'Proto、FlatBuffersなどで記述されたバインディングでは、すべてのコンパイラーまたはプログラム作成者が同じバインディングを異なる構文で記述して、真にクロスプラットフォームになるとは限りません。 おかしなことに、これは手書きのバインディングを支持する議論です。人々はプラットフォームPのバインディングを書くことでプログラムに貢献できます。しかし、これはまったく理想的ではないことを認めましょう。
要約すると、Webバインディングと非Webバインディングの間で断片化が発生する可能性が懸念されます。 非Webバインディング言語が保持されている場合、それはWebブラウザーによって現実的に実装されますか? 彼らはバインディング「Wasm⟶バインディング言語B⟶WebIDL」を書かなければならないでしょう。 これはすべてのホストで同じシナリオであることに注意してください:Wasm⟶バインディング言語B⟶ホストAPI。
好奇心旺盛な人のために、私はWasmerで働いており、 PHP 、 Python 、 Ruby 、 Go -Wasmの統合の著者です。 非常に異なるホストのさまざまなバインディングをハックするための素晴らしい遊び場ができ始めました。 誰かが私にさまざまなソリューションを統合したり、フィードバックを収集したり、実験したりすることを望んでいる場合、私たちは皆オープンであり、協力してより多くのリソースを投入する準備ができています。
'webIDLバインディング'の現在の方向はおそらくwebIDLから離れています
自体。 ただし、ジレンマは次のとおりです。
モジュール間およびモジュールホストを表現するための「自然」言語
相互運用性は、WASMの自然言語よりもはるかに豊富です。 この
使用可能なIDLと同等のものは、からかなり恣意的に見えることを意味します
WASMのハメ撮り。
一方、C / C ++のレンズから世界を見る人々のために
(およびRustとその同類)WASMのモデルよりも豊富なものはすべて
使用できません。 refを統合することの難しさでこれをすでに見ることができます
ツールチェーンに入力します。
さらに、一般をサポートすることはWASMの直接の義務ではありません
言語間の相互運用性。 また、IMOであってはなりません。
(私が言語間相互運用性のより限定されたバージョンがあります
信じるのはサポートできるだけでなく重要でもあります:それは私たちのすべてにあります
機能の提供者と機能のユーザーが満たすことができる関心
摩擦を最小限に抑えます。 (機能は管理者が話すことですが、私は持っています
より良い用語は見つかりませんでした。)これには、異なるスタイルのIDLと1つのスタイルが必要です。
これは、完全な言語間で必要とされるよりも実装が簡単です。
相互運用)
結論:IDLに相当するものがある場合があり、それが必要です
所有権の境界を越えた相互運用をサポートします。 最終的には
現時点では存在は明確ではありません。
7:02イワンEnderlinで月、2019年6月24日には[email protected]
書きました:
WebIDLがバインディング言語としてのデフォルトの提案であった理由を理解しています。
これは、Webの歴史的で、なんとなく成熟したバインディング言語です。 私
その決定を大いに支持します、そしてそれは私がしたであろう可能性が非常に高いです
同じ。 それにもかかわらず、私たちはそれが他の文脈にほとんど適合しないことを認識するかもしれません
(理解してください、他のホスト/言語)。 Wasmは、ホストに依存しないように設計されています。
または言語/プラットフォームに依存しません。 成熟したWebを使用するというアイデアが好きです
テクノロジーとWeb以外のシナリオのユースケースを見つけるために、
Web IDLの場合、それは本当にWebに結びついているようです。 それが私がとても
ここでそれらの会話を綿密に追跡します。WebAssembly / webidl-bindings#40を開きました
https://github.com/WebAssembly/webidl-bindings/issues/40 、それは私を導いた
私はそれについての言及を見たことがないので(または私はそれを逃したので)ここで質問をする。拘束力のある話全体では、「誰が」責任があるのかは明確ではありません
バインディングを生成します。
- それはコンパイラー(プログラムをWasmモジュールに変換する)ですか?
- それはプログラムの作者ですか(そして、バインディングは手書きですか)?
どちらも有効だと思います。 そして、Web IDLの場合、それはいくつかを示しているようです
制限(上記のリンクを参照)。 たぶん私はちょうど重要なステップを逃した
プロセスなど、私のメッセージを忘れることを検討してください。Web IDLを「再フォーカス」してWeb中心ではないようにすることが目的であっても、現時点では、
それは非常にWeb中心です。 そして、代替案を提案するための提案が生じます。
したがって、このスレッド。 したがって、私は潜在的な断片化を懸念しています。
理想的には(そしてこれがWasmがこれまでに設計された方法です)、Wasmモジュールが与えられた場合
バインディングを含め、どこでもそのまま実行できます。 と
Web IDL、Cap'n'Proto、FlatBuffersなどで記述されたバインディング
すべてのコンパイラまたはプログラム作成者が同じように書くわけではないことはかなり確実です
真にクロスプラットフォームになるためのさまざまな構文のバインディング。 おかしなことに、これは
手書きのバインディングを支持する議論:人々は貢献することができます
プラットフォームPのバインディングを記述してプログラムします。しかし、これはそうではないことを認めましょう。
まったく理想的です。要約すると、私はWebと
Web以外のバインディング。 非Webバインディング言語が保持されている場合、それは
Webブラウザによって現実的に実装されていますか? 彼らは書かなければならないでしょう
バインディング「Wasm⟶バインディング言語B⟶WebIDL」。 これは同じであることに注意してください
すべてのホストのシナリオ:Wasm⟶バインディング言語B⟶ホストAPI。好奇心旺盛な人のために、私はWasmerで働いており、PHPの作者です-
https://github.com/wasmerio/php-ext-wasm、Python-
https://github.com/wasmerio/python-ext-wasm、Ruby-
https://github.com/wasmerio/ruby-ext-wasm and Go-
https://github.com/wasmerio/go-ext-wasmWasm統合。 始めます
非常に異なるために異なるバインディングをハックするための素晴らしい遊び場を持っている
ホスト。 誰かが私にさまざまなソリューションを統合してほしいと思ったら、収集する
フィードバック、または実験してみてください、私たちは皆オープンで協力する準備ができています
それにもっと多くのリソースを置きます。—
あなたが言及されたのであなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/WebAssembly/design/issues/1274?email_source=notifications&email_token=AAQAXUD6WA22DDUS7PYQ6F3P4DHYRA5CNFSM4HJUHVG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AAQAXUGM66AWN7ZCIVBTXVTP4DHYRANCNFSM4HJUHVGQ
。
-
フランシス・マッケイブ
SWE
refタイプをツールチェーンに統合することの難しさでこれをすでに見ることができます。
他の言語について話すことはできませんが、Rust + wasm-bindgenはすでに以下をサポートしています。
WebIDLから自動生成され、後で公式のWebIDL参照タイプに簡単にアップグレードできるように注意深く設計された
だから私は興味があります:あなたはどのような困難を指しているのですか?
難しさについての私の理解は、C ++側にあります。 Rustには、言語側でこれをより合理的にするのに十分強力なメタプログラミングがありますが、ユーザーランドC ++は、たとえば、anyrefについて推論するのに苦労しています。
ここでC ++固有の問題についてもっと知りたいと思います。 (それらはC ++固有ですか、それともLLVM固有ですか?)
C ++はref型が何であるかを知りません。 だからあなたはそれを中に入れることはできません
任意のオブジェクト。 実際には言語の一部ではありません。 ファイルのようなもの
ディスクリプタ。 文字列のための面白い場所。
15:07アロンZakaiのでは月、2019年6月24日に[email protected]書きました:
ここでC ++固有の問題についてもっと知りたいと思います。 (彼らは
C ++固有またはLLVM固有?)—
あなたが言及されたのであなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/WebAssembly/design/issues/1274?email_source=notifications&email_token=AAQAXUDW237MUBBUUJLKS6LP4FARJA5CNFSM4HJUHVG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AAQAXUB4O3ZX4LRQSQRL763P4FARJANCNFSM4HJUHVGQ
。
-
フランシス・マッケイブ
SWE
オフラインで@fgmccabeと話すと、構造体は線形メモリに格納されるため、C ++とRustの両方が構造体にref型を直接格納できないのは事実です。 もちろん、C ++とRustはどちらも、ファイル記述子やOpenGLテクスチャなどを処理するのと同じ方法で、整数ハンドルを使用してref型を間接的に処理できます。 彼の主張は、これら2つの言語のどちらも、私が同意するrefタイプを「うまく」/「自然に」(間違っている場合は訂正してください!)処理できないということだと思います。これらの言語は、refタイプでは常に不利になります。 GC言語と比較した操作。
ここにC ++に固有の何かがあるかどうかについてはまだ興味があります。 ないと思いますか?
ここでC ++を難しくしている理由についての私の理解は、次のように言っている場合です。
struct Anyref; // opaque declaration
void console_log(Anyref* item); // declaration of ref-taking external JS API
Anyref* document_getElementById(const char* str);
void wellBehaved() {
// This should work
Anyref* elem = document_getElementById("foo");
console_log(elem);
}
void notSoWellBehaved() {
// ????
Anyref* elem = document_getElementById("bar");
Anyref* what = (Anyref*)((unsigned int)elem + 1);
console_log(what);
}
幸いなことに、後者の例は私が信じているUBです(無効なポインターは作成されるとすぐにUBになります)が、LLVM IRでそれをモデル化するにはどうすればよいですか?
@ jgravelle-google struct Anyref;
さえ、線形メモリで意味のあるものだと思います。 代わりに、OpenGLテクスチャ、ファイルハンドルなど、前述の整数ハンドルを使用してモデル化してみませんか?
using Anyref = uint32_t; // handle declaration
void console_log(Anyref item); // declaration of ref-taking external JS API
Anyref document_getElementById(const char* str);
void wellBehaved() {
// This should work
Anyref elem = document_getElementById("foo");
console_log(elem);
}
整数ハンドルは、使用するときにテーブルで検索する必要があります。これも、C ++やRustなどの線形メモリを使用する言語の欠点にすぎません。 ただし、少なくともローカルで確実に最適化できます。LLVMを使用しない場合は、wasmレベルで最適化できます。
それは機能しますが、必ずtable_free(elem)
を呼び出す必要があります。そうしないと、永久に参照を保持します。 これは、C ++にとっては奇妙なことではありません。
うまくレイヤー化されていないので、奇妙なマッピングのようなものだと思いますか? OpenGLのライブラリのように感じますが、コンパイラの魔法に依存して提供します-個別の宣言に依存している場合は、インラインwasmを使用してもC ++でanyref.h
を構築できるとは思いませんテーブル。
とにかく、それはすべて実行可能/扱いやすいと思いますが、単純ではありません。
@kripken 「適切な」ネイティブanyref
サポートにはLLVM(およびrustc)への変更が必要になるのは事実ですが、それは実際には障害ではありません。
wasm-bindgenは、本物のwasm anyref
wasmテーブルに格納し、線形メモリに整数インデックスをテーブルに格納します。 したがって、wasm table.get
命令を使用して、 anyref
アクセスできます。
wasm-gcが実装されるまで、GC言語はまったく同じ戦略を使用する必要があるため、Rust(et al)を見逃すことはありません。
では、LLVMでのネイティブのanyref
サポートは私たちに何をもたらすでしょうか? そうですね、wasmテーブルを介してanyref
を間接的に渡す必要はなく、関数からanyref
を直接渡す/返すことができます。 それは便利ですが、それは単なるパフォーマンスの最適化であり、実際にはanyref
使用を妨げるものではありません。
@Pauan
wasm-bindgenは、本物のwasmanyrefsをwasmテーブルに格納し、線形メモリに整数インデックスをテーブルに格納します。 そのため、wasmtable.get命令を使用してanyrefにアクセスできます。
まさに、はい、それは私が言及していたモデルです。
wasm-gcが実装されるまで、GC言語はまったく同じ戦略を使用する必要があるため、Rust(et al)を見逃すことはありません。
はい、現時点では、ネイティブのwasm GCがないため、GC言語には利点がありません。 しかし、うまくいけば、それは変わるでしょう! :)最終的には、少なくともGCを適切に実行すれば、GC言語には明らかな利点があると思います。
では、LLVMでのネイティブanyrefサポートは私たちに何をもたらしますか? まあ、それは、wasmテーブルを介してanyrefを間接的にする必要はなく、関数からanyrefを直接渡す/返すことを可能にします。 それは便利ですが、それは単なるパフォーマンスの最適化であり、実際にはanyrefの使用を妨げるものではありません。
同意しました。はい、これはC ++やRustなどに対するGC言語の(最終的には)パフォーマンス上の利点にすぎません。使用を妨げることはありません。
ただし、テーブルがルート化されるため、C ++とRustではサイクルがより大きな問題になります。 たぶん、トレースAPIまたは「シャドウオブジェクト」を使用できます。基本的には、C ++ / Rust内のGCリンクの構造をマッピングして、外部のGCがそれらを理解できるようにする方法です。 しかし、それらのいずれについても実際の提案はまだないと思います。
最終的には、少なくともGCを適切に実行すれば、GC言語には明らかな利点があると思います。
私は間違っている可能性がありますが、その場合は驚きます。GC言語はwasm GC構造体を割り当てる必要があり、wasmエンジンはプログラムを流れるときにそれを追跡する必要があります。
比較すると、Rustは割り当てを必要とせず(テーブルに割り当てるだけ)、整数を格納するだけでよく、wasmエンジンはGCの目的で1つの静的な移動しないテーブルを追跡するだけで済みます。
table.get
を使用する必要がないため、 anyref
アクセスがGC言語用に最適化される可能性があると思いtable.get
は非常に高速であると思います。
では、wasmテーブルを使用するプログラムよりもwasm-gcプログラムのパフォーマンスがどのように向上するかについて詳しく説明していただけますか?
PSこれはかなり話題から外れ始めているので、この議論を新しいスレッドに移すべきでしょうか?
本当にそれだけです: table.get/table.set
を避けます。 GCを使用すると、生のポインターがすぐそこにあり、間接参照を保存する必要があります。 しかし、ええ、RustとC ++は整数を格納するだけでよく、全体的にかなり高速なので、GCの利点が問題にならない可能性があります。
ええ、私たちは話題から外れるかもしれないことに同意します。 話題になっているのは、 @ fgmccabeが、線形メモリを使用する言語ではref型が自然に適合しないという点だと思います。 それはバインディングで特定の方法で私たちに影響を与える可能性があります(特にC ++とRustはそれらを処理できないため、サイクルは心配ですが、バインディングはそれを無視できる可能性がありますか?)できるだけ多くの言語で機能するようにし、特定の言語の制限に過度に影響されないようにします。
@kentonv
Cap'n Protoのシリアル化レイヤーは、現実的には各言語で個別に実装する必要があります。これは、そのほとんどが、慣用的でインライン対応である必要がある、広くて浅いAPIレイヤーであるためです。 RPCレイヤーOTOHは狭いですが、深いです
それはどのフォルダですか?
@PoignardAzur申し訳ありませんが、あなたの質問がわかりません。
@kentonv capnprotoGithubリポジトリを調べています。 シリアル化レイヤーはどこにありますか?
@PoignardAzurだから、これは私のポイントに戻ります。 「それがシリアル化レイヤーです」と指摘して言うことができる場所は、実際には1つではありません。 ほとんどの場合、Cap'n Protoの「シリアル化」は、基になるバッファーへのロード/ストアに関する単なるポインター演算です。 スキーマファイルを指定すると、コードジェネレーターを使用して、スキーマで定義された特定のフィールドに対して正しいポインター演算を実行するインラインメソッドを定義するヘッダーファイルを生成します。 アプリケーションコードは、フィールドの読み取りまたは書き込みを行うたびに、これらの生成されたアクセサーを呼び出す必要があります。
これが、別の言語で書かれた実装を呼び出そうとしても意味がない理由です。 すべてのフィールドアクセスに生のFFIを使用するのは非常に面倒なので、FFIをよりきれいな(そしてスキーマに固有の)ものにラップするコードジェネレーターを作成することになることは間違いありません。 しかし、その生成されたコードは、少なくともCap'n Protoがすでに実装しているコードと同じくらい複雑であり、おそらくもっと複雑です(そしてはるかに遅いです!)。 したがって、ターゲット言語のコードジェネレーターを直接作成する方が理にかなっています。
Cap'n Proto実装内には、共有できる内部ヘルパー関数がいくつかある可能性があります。 具体的には、 layout.c++
/ layout.h
は、capnpのポインターエンコーディングを解釈したり、境界チェックを実行したりするすべてのコードが含まれています。生成されたコードアクセサーは、ポインターフィールドの読み取り/書き込み時にそのコードを呼び出します。 したがって、その部分をFFIでラップして、複数の言語から呼び出されることを想像できます。 しかし、それでも、各ターゲット言語でコードジェネレーターとある程度のランタイムサポートライブラリを作成することを期待しています。
ええ、すみません、私は反対のことを意味しました^^(RPCレイヤー)
@PoignardAzurああ、そしてあなたはインターフェイスをFFIでラップする方法を考えているので、インターフェイスを見ることに特に興味があると思います。 だからあなたは欲しい:
capability.h
:機能とRPC呼び出しを表すための抽象的なインターフェース。理論的には、さまざまな実装によって裏付けられる可能性があります。 (これが最も重要な部分です。)rpc.h
:ネットワークを介したRPCの実装。rpc-twoparty.h
:単純な接続を介したRPC用のトランスポートアダプター。この提案は現在、#1291:OCAPバインディングに取って代わられています。
最も参考になるコメント
これは、私が信じている重要な根本的な仮定であり、単に真実ではないと私は信じています。 私の経験では、(少なくとも!)言語の実装と同じ数の表現の選択肢があります。 そして、それらは任意に複雑にすることができます。
さまざまなエンコーディング、異種ロープなどを含む、文字列の表現が数十(!)あるV8を取り上げます。
Haskellのリストは怠惰であるため、Haskellのケースはあなたが説明するよりもはるかに複雑です。つまり、文字列内のすべての文字に対して、サンクを呼び出す必要があるかもしれません。
他の言語では、文字列の長さに面白い表現を使用するか、明示的に保存せずに計算する必要があります。
これらの2つの例は、宣言型データレイアウトがそれをカットしないことをすでに示しています。多くの場合、ランタイムコードを呼び出すことができる必要があり、ランタイムコードには独自の呼び出し規約がある場合があります。
そして、それは単なる文字列であり、概念的にはかなり単純なデータ型です。 言語が製品タイプ(タプル/構造体/オブジェクト)を表す方法が無数にあることについても考えたくありません。
そして、これらすべてのデータ構造を作成できる必要がある受信側があります。
ですから、「大多数の言語」のサポートに少しでも近づくことは、まったく非現実的だと思います。 代わりに、すでに任意のものの大規模な動物園を成長させながら、私たちはいくつかの特権を開始します。 それは複数のレベルで致命的なようです。