【wasm】ブラウザでC++。Emscriptenを語ろう

■ このスレッドは過去ログ倉庫に格納されています
1L
垢版 |
2019/01/15(火) 19:50:48.94ID:cXSiB+ud
タイトル通り。

・canvas への描画が可能なことを確認。
・emscripten_sleep() でその場で停止できることを確認。
・付属の emrun や mongoose などで Local Server を作れば、local だけで
 wasm の起動が出来ることを確認。
・mongoose からは、cgi も起動でき、XmlHttpRequest()でローカルファイルを
JSから読み込め、cgi も自由に起動できることを確認。
・ローカル・ファイルアクセス、clipboard の読み書きの他、Local OS の
 全ての機能を自由にできる可能性有り。
・これを使えば、Java の JVM に変わる新たなローカル仮想環境ができる。
2019/01/19(土) 11:38:40.03ID:P/iwNPAz
【wasm を使う際に難しそうな事柄色々】
・async, await
・yield [ function generator ]
・setjmp(), longjmp()
・sleep() ; emscripten_sleep() で実装されてはいるが、とても複雑な方法で実装。バグ有り。
・Atomics, wait(), notify()

【ヒント】
1. wasm からは、JSの関数を呼び出せる。
2. JS からは、wasmの関数を呼び出せる。
3. JS からは、XmlHttpRequest() で WebServer 経由で外部ファイルを読み出せる。
4. XmlHttpRequest() の代わりに fetch() も使えるらしい。
5. JS では、eval(文字列); によって、文字列の中に書かれているJSコードを実行できる。
6. 1と5を使えば、wasmからJSの任意のコードを実行段階で変化する動的な引数を付けて
 呼び出されるように出来る。
7. Emscripten では、EM_ASM(), EM_ASM_INT() 文は、*.ll コードでは、
  それぞれに対応した関数を call するコードに置き換わる。
2019/01/19(土) 12:31:16.19ID:P/iwNPAz
【asm.js】

・asm.js は、JS のサブセット。だから、JS を超えることは出来ないらしい。

・Emscripten は、Em+Script+en という造語らしい。「Em+xxx+en」は「xxx化する」
 の意味なので、Emscripten は、「Script 化する」の意味となる。

・Emscripten は元々、C/C++ コードを wasm ではなく、JS コードのサブセットで
 あるところの asm.js に変換するシステムだったらしい。

・だから今でも、いったん asm.js に直してから binaryen で wasm に
 変換しているらしい(←推定)。

・asm.js の仮想マシンの主記憶は JS の HEAP32[] 配列が対応するらしい。

・仮想マシンのスタックポインタは JS の STACKTOP という名前の変数で、C/C++ の
 auto local な変数は、HEAP32[STACKTOP + ofs] の形式で参照されることが多い。

【wasm】

・wasm は、バイナリ形式だがテキスト形式も存在し、wast、wat と呼ばれ、
 LISP の S 式に近い人間が可読な形式になっている。
 
【LLVM】

・LLVM は、*.bc がバイナリ形式。*.ll が人間が可読な形式。Emscriptenでは、
 拡張子が bc の代わりに o とされている。

・llvm-as で、*.ll を *.bc に変換できる。
2019/01/19(土) 13:02:28.07ID:P/iwNPAz
誤: auto local な変数は、HEAP32[STACKTOP + ofs] の形式で参照されることが多い。
正: auto local な変数は、HEAP32[(STACKTOP<<2) + ofs] の形式で参照されることが多い。
2019/01/19(土) 15:43:48.91ID:P/iwNPAz
【VM (Virtual Machine) としての wasm】

・結論から言うと、wasm は、VM としては、JVM や、flash VMより劣っていると思う。
 例としては、「同期オブジェクト(Win32 のWaitForSingleObject()相当)」や、
 sleep() もほぼ、wasm でちゃんと実装できそうなのは、Chrome と、FireFoxに限られる事。

・ところが、Platform 会社の「思惑(Mac上の開発環境の使用の強制)」や
 「訴訟問題(GoogleとOracleの裁判)」により、上記の二つのVMは除外されて行く
 傾向にあり、スマフォでも使えるVMとして残っていくのは、wasm と .NET だけ
 になっていきそうな気がする。

・だから、言語のFRONT END開発者としては、スマフォでも使えるVMとしての選択肢は
 wasm が有力ではあるが、機能面で(かなりの)問題があるというジレンマが生じる。

・これは、GAFA プラットフォーム支配の問題点の一つ。

------------------
[WHY APPLE WON'T ALLOW ADOBE FLASH ON IPHONE]
https://www.wired.com/2008/11/adobe-flash-on/
2019/01/21(月) 19:24:23.00ID:x6DE2oRu
Browser と Windows で同じプログラムが動く Widget のサンプル
(作りかけ)

http://www.nowsmartsoft.shop/

ひきこもりの L より。
7L
垢版 |
2019/01/22(火) 15:48:18.25ID:6S+2YJAI
XmlHttpRequest() は、同期モードにすれば、CGI の動作が完了するまで
待てる気がする。もし、その CGI が、内部で Sleep( 1000 ) 相当の待機を
行えば、JS を、その場で 1秒間 待機させることが出来るかもしれない。
2019/01/22(火) 17:47:28.65ID:6S+2YJAI
Android の主要言語はJavaだが、VMは、JVM ではなく、Google 製の
ART (旧Dalvik) の VM を使っている。

だから、このVMは残っていくんだ・・・。
Google は、もっぱら wasm 路線だと思っていたんだが・・・。
2019/01/24(木) 22:53:00.98ID:/05KE7l4
>>3 >>4
どうやら、STACKTOP、HEAP32[] を使うのは、asm.js 流で、
wasm の stack はまた別系統になっているらしい。

前者は、global 領域に確保された HEAP32 配列を使うので、通常の
CPUアーキテクチャでのスタックの実装法に近い。そのため、CPUで
出来ることは何でも出来るといっても過言ではない。

一方、wasm の stack には、今のところ stack pointer が存在していない
らしい。だから、stack の値を検査したり、独自にコピーして保存して
何らかの効果を得たりすることも出来ないと思われる。
一方で、wasmでの標準的な作法なので、auto 変数もこのやり方
を使うことになっているため、ブラウザでJITなどは、CPUレジスタに
割り付けることによる速度向上が見られるかもしれない。

ここでジレンマが生じる。実は、wasm では、sleep() 機能を使う際には、
-s ASYNCIFY=1 を指定しなくてはならない。すると、前者の実装方に
なるだろう。すると、wasm での標準的な方法ではなくなるために、
CPUレジスタへの割付が行われない可能性がある。

なお、話が複雑になるが、ASYNCIFY=1 の指定は、現在の JS の WebAPI の仕様
と絡むと、emscripten_sleep() だけの問題ではなく、非常に重要と言っても過言ではない。

結論を言ってしまえば、OpenFileDialog() のようなもので、ユーザーがファイル名を
選択するのをその場で「待つ」事や、getch() や bat ファイルの pause 文のように
キー入力を待つことのような、便利な機能がはっきり言って実装不可能になってしまう。

これは長くて深い話なので、ちゃんと説明するのは難しい。
2019/01/24(木) 23:11:44.56ID:/05KE7l4
>>9
正しくは、「WebAPI」ではなく、ブラウザ上の JS で使える EventLoop などの
仕様のこと。

1. JS では、Win32 の GetMessage() や PeekMessage() のような、
 Event Queue の現在の中身を直接を調べるような関数が存在しないらしい事。
2. WebWorker で WorkerThread を作っても、結局、DOMには
 アクセスできない事。
3. さらに、WorkerThrad では、イベントを受け取ることも出来ない事。
4. Win32 の GetAsyncKeystate() のような、イベントによらないでキーの
  On/Off 状態を取得する方法が無いこと。
5. 以上の事は、OpenFileDialog() や getch()、pause() 文の模倣をそのままでは
 原理的に不可能にする可能性がとても高い。

[回避策]
a. C/C++ のステートメントの実行を一行実行するたびにイベントループに戻る
 ような「インタプリタ的な」実行法を採用する。これは、「Emterpreter」
 (「Emscripten」と似ているが違うので注意)なるやり方が相当。
b. 関数のコンパイルの仕方を大幅に変更する方法。これが、ASYNCIFY=1
 に相当すると思われる。具体的には、関数で「待つ」必要がある場合には、
 イベントループに戻る。このことは、結論的には、JSやwasmの色々な機能不足を
 一挙に解決できる。イベントループに戻ることで、キーやマウスのイベントを
 受け取ることも出来るようになるし、タイマーイベントが発生するまでCPUを
 hlt 状態にすることも出来るようになる。この事は実は似ているが、よく考えると
 別の事柄でもあったりするので、説明が難しいが、とにかく、一挙に色々な
 事を解決できるようになる。GetAsyncKeyState() 相当の関数が無いことと
 Atomics の wait(), wake(), notify() が、限られたブラウザでしか実装されて無い事
 の問題も解決する。誤解を招かずに説明するには話が長くなるのが・・・。
2019/01/24(木) 23:42:39.89ID:/05KE7l4
>>10
誤: 3. さらに、WorkerThrad では、イベントを受け取ることも出来ない事。
正: 3. さらに、WorkerThread では、(キーボードやマウスなどからの)
    「外部イベント」を受け取ることも出来ない事。

・WorkerThread でも、postMessage() によるソフトウェア的なイベントは受け取ることが
 できる。

・ EventTarget の dispatchEvent() を使えば、強制的にイベント・ハンドラを呼び出すことが
 できる。この時、ハンドラの実行が開始され、実行が完了するまで呼び出し元には帰って
 こないらしい。そのため、この呼び出し方は「同期的呼び出し(Synchronously invoke)」
 とされる。


色々考えてみたが、今ある全ての JS の関数、wasm の機能をどんな組み合わせで使っても、
C/C++ の関数を独特な特殊な形式までコンパイルしてやら無いと、getch(), pause(),
OpenFileDialog() のような機能は実現できないと思われる。

それが、「-s ASYNCIFY=1」だと思う。sleep() に関してだけは、Atomics の wait(),
wake() や、>>7 に書いた方法で実装できると思われる。

なお、JS の、async, await, yield などは、使える可能性はあるが、しかし、C/C++ を
wasm に直すことを考えたとき、使うのが難しいか、または、使えても効率はあまりよくない、
または、問題が生じてしまう、と考えている。例えば、wasm に直さずに、asm.js のように
js レベルまでのコンパイルでよいなら、async, await は、何とか利用できる気がする。
それでも、それらの仕様がどうも貧弱なので、それが問題。というのは、2段階以上の
深い関数では、await はエラーになってしまうらしいから、使いたい全ての関数に
async 修飾をしなくてはならない事になりそう。

また、説明するのが難しいが、結局、ASYNCIFY=1 でやっていることと余り変わらないという
か、>>9 のSTACKTOP, HEAP32[] を使うのと、async, await, yield は結局は本質的に同じような気がする。
2019/01/25(金) 00:01:31.69ID:58XK3b4v
>>9
>一方、wasm の stack には、今のところ stack pointer が存在していない
>らしい。

ここは、興味深い話として、

1. LLVM には、C の longjmp(), setjmp() の他、 C++ の throw, catch も、
 直接的なサポートがある。

2. LLVM には、仮想レジスタは有っても「CPUレジスタ」的なものは(ある意味では)
 「存在しない」が「スタックポインタ」は、一応、存在している。

3. LLVM には、(EIP のような)「命令ポインタ」的なものは存在していない(ハズ)。
 関数内には、ラベルを書くことが出来て、br 命令で比較的自由に飛ぶことも出来る。
 ところが、全く別の関数への大域的な、jmp 命令のようなものは多分無い。

4. 一番言いたいことは、上記の「2.」のこと。LLVM にはスタックポインタを直接
  触ることが出来る。それなのに、wasm では出来ない(らしい)。この事は、
  結構理不尽な感じがする。

5. 実CPUでは簡単かつ効率よく出来てしまう事が、wasmではできない。
6. Win32 では簡単に出来てしまうことが JS では出来ない。メッセージ・キューの読み出し。
 独自イベントループの記述。while ( GetMessage(&msg ) ) { DispathMessage( &msg); } みたいな。
7. 結果、BASIC でも、35年前の昔から簡単に出来ていた input "a="; a 見たいな事が、JS では出来ない。
2019/01/27(日) 02:20:20.81ID:UeSsBKpf
>>9
>どうやら、STACKTOP、HEAP32[] を使うのは、asm.js 流で、
>wasm の stack はまた別系統になっているらしい。

C/C++ の文字列のポインタを JS の関数に渡して、JSで
文字列として扱いたい場合、Emscripten が用意している
Pointer_stringify() という JS の関数を使うことになる。

この関数のソースを見てみたところ、例えば、
writeArrayToMemory: function(array, buffer) {
for (var i = 0; i < array.length; i++) {
HEAP8[ buffer++ ] = array[i];
}
}
のような関数を使っており、HEAP8[] 配列が使われている。
これは、asm.js 流の stack を恐らく「必ず」使っている事を
意味するのだと思う。言いたいことは、wasm の 「nativeな」
stack の仕組みを使わずに、JS の global 変数的に TypedArray
として HEAP8[] を確保して、それを「必ず」使っている、ということ。
そうでなければ、Pointer_stringify() 関数が使えなくなってしまうはずだから。

その結果、wasm の native stack を使って無いので、ブラウザの JIT が働いても、
CPUレジスタが効率よく使われる可能性は低くなる。

ただ、C/C++ と JS 間の文字列の受け渡しは大切で、上記のような実装以外は
現状、多分できそうにない。なら、結論的には、Emscripten がどんなに
改良されても、wasm 側に何らかの改良が施されない限り、wasm の native stack
を使用した wasm コードは根本的に生成できないと思われる。
2019/01/27(日) 02:29:37.09ID:UeSsBKpf
>>13
確認が必要ではあるが、ある意味では「良い」こととして
-s ASYNCIFY=1 を指定しても、指定しなかったときに比べた
速度低下は、native stack を使うかどうかに起因する事は
ない可能性がある。だから、-s ASYNCIFY=1 は、そんなに
気兼ねすることなく使ってもいい可能性が出てくる。

というか、-s ASYNCIFY=1 は、Emscriptenの作者の気持ちは
離れていっているかもしれないが、実際にはこれなしでは、
Windows を模倣したような汎用的な ToolKit は作れない
と思うが・・・。
2019/01/27(日) 02:56:58.37ID:UeSsBKpf
>>14
>というか、-s ASYNCIFY=1 は、Emscriptenの作者の気持ちは
>離れていっているかもしれないが、

実は、さらに効率が悪いと思われる「Emterpreter」という、C/C++
のソースをバイトコードにコンパイルして、インタプリタ的に実行して
しまう方式の方に、Emscripten の作者の心は向いているのかも知れない。
-s ASYNCIFY=1 は、「推奨しない」が、新しい方式として、「Emterpreter」
を挙げているようにも思える書き方がされているようなので。
■ このスレッドは過去ログ倉庫に格納されています