Skip to content

Instantly share code, notes, and snippets.

@taise
Last active January 29, 2018 10:17
Show Gist options
  • Select an option

  • Save taise/e89c7ba3bfa8aaa28fde959d93e72e5f to your computer and use it in GitHub Desktop.

Select an option

Save taise/e89c7ba3bfa8aaa28fde959d93e72e5f to your computer and use it in GitHub Desktop.

What is WebAssembly

ref: 20170924-html5conference-wasm by N. Shimizu

wasmでできること

  • 数値計算
  • 線形メモリへのアクセス
  • 関数呼び出し

WebAssemblyとはなにか

ref: WebAssembly のコンセプト

WebAssembly はモダンなウェブブラウザで動作して新たな機能と大幅なパフォーマンス向上を提供する新しい種類のコードです。基本的に直接記述ではなく、C、C++、Rust 等の低水準の言語にとって効果的なコンパイル対象となるように設計されています。

WebAssembly はどのようにウェブプラットフォームに適合するのか?

ウェブプラットフォームは 2 つの領域からなると考える事ができます:

  • ウェブアプリのコードを実行する仮想マシン (VM) 、例としてアプリを動作させる JavaScript コード。
  • ウェブブラウザ / デバイスの機能をコントロールして物事を実現するためにウェブアプリが呼ぶことのできる Web API のセット。

WebAssembly は JavaScript と異なる言語ですが、その置き換えを意図していません。その代わり、JavaScript の足りない所を補填して併用し、ウェブ開発者に双方の以下のような利益を提供する事を狙いとしています:

  • JavaScript は高水準の言語であり、ウェブアプリケーションを作る上で十分な柔軟性と表現力を持っています。そして多くの利点 — 動的型付け言語である、コンパイルが必須でない、パワフルなフレームワーク、ライブラリやツールを提供する豊富な土壌等を持っています。
  • WebAssembly はネイティブに近いパフォーマンスで動作して、 C++ や Rust のような低水準のメモリ管理を行う言語がウェブ上で動作するようコンパイルされる対象となる、コンパクトなバイナリ形式を持つ低水準なアセンブリに似た言語です ( WebAssembly は将来的にガベージコレクトによるメモリ管理を行う言語をサポートする 高レベルの目標 を持っている事に注意して下さい )。

必要に応じてこの異なったコードは互いを呼び出し合う事ができます — WebAssembly JavaScript API はエクスポートした WebAssembly のコードを普遍的に呼び出せる JavaScript 関数でラップし、WebAssembly のコードは通常の JavaScript 関数をインポートして同期的に呼び出せます。実際、WebAssembly のコードの基本単位はモジュールと呼ばれ、WebAssembly のモジュールは ES2015 のモジュールと多くの対になる概念を持っています。

WebAssembly のキーコンセプト

ブラウザ上で WebAssembly がどのように動作するかを理解するため必要となるキーコンセプトがいくつか存在します。これらのコンセプトはそれぞれが WebAssembly JavaScript API に一対一で対応しています。

  • Module: ブラウザによって実行可能な機械語にコンパイルされた WebAssembly のバイナリに対応します。モジュールはステートレスであるため、Blob のように、明示的に IndexedDB にキャッシュ できたり window やウェブワーカーと ( postMessage() を経由して ) 共有する事ができます。モジュールは ES2015 のモジュールのように import と export の宣言を行います。
  • Memory: WebAssembly の低水準なメモリアクセス命令によって読み込みおよび書き込みが行われるバイト列を一次元の配列として保持している、リサイズ可能な ArrayBuffer です。
  • Table: メモリ内に ( 安全性およびポータブル性を維持するため ) バイト列として保持することができなかった ( 関数等に対する ) 参照を保持しているリサイズ可能な型付き配列です。
  • Instance: メモリ、テーブル、インポートされた値を含む実行時に利用される全ての状態と対となるモジュールです。インスタンスは特定の import によって特定のグローバル環境にロードされた ES2015 におけるモジュールのような物です。

C/C++からのポーティング

Emscripten ツールは C/C++ ソースコードを取得し、それを .wasm モジュール、加えてモジュールを読みだして実行するために必要な JavaScript に "glue(グルー、接着する)" コードとコードの結果を表示するための HTML 文章にコンパイルおよび出力します。

emscripten

  1. Emscripten は最初に C/C++ を Clang + LLVM — 完成度の高いオープンソースの C/C++ コンパイラ・ツールチェインであり、OSX の XCode の一部として提供される等の利用例が有る、に注入します。
  2. Emscripten が Clang + LLVM によるコンパイル結果を .wasm バイナリに変換します。
  3. それ自体だけでは WebAssembly は現時点で DOM に直接アクセスできません; JavaScript を呼び出して、整数型もしくは浮動小数点型の基本データを渡せるだけです。そのため、ウェブ API にアクセスするためには、 WebAssembly は JavaScript を呼び出す必要が有り、この時点でウェブ API の呼び出しが行われます。そのため Emscripten は結果を得るための HTML と JavaScript のグルーコードを生成します。

gfxさん...!

なぜ WebAssembly が出てきたか

ref: 【2017年4月版】WebAssemblyとは?〜実際にC言語をブラウザで動かす〜

JavaScript

インタプリタ言語で 動的型付けをしている → 解析に時間がかかってしまう

asm.js

型を明確にして、ブラウザが事前コンパイルできるようにする

👍 asm.jsの良いところ

  • 数値演算系の実行速度が速くなる(通常のJavascriptの6〜7割の時間で処理が終わる)
  • asm.jsをサポートしない環境では通常のJavaScriptコードとして振舞う
  • 他言語(C/C++)からasm.jsコードを出力可(コンパイラを使う)
  • ゲームエンジンによるサポート(Unreal Engine, Unity)
    • UnityのコードをWebGLで動作させるときにasm.jsが使われている

👎 asm.jsの悪いところ

  • ファイルサイズが増大&通信量増加
    • それによるパージング(構文解析)の時間増加
  • データ構造の概念が存在しない
    • 数値計算しかできない。オブジェクト指向的なアプローチが通用しない
  • Web API 呼び出しが得意ではない(外部からfunctionを渡す必要がある)

WebAssembly

対応言語は現時点でC/C++, Rustなど can i use wasm

👍 WebAssemblyの良いところ

  • asm.jsに比べてファイルサイズが小さくなり、ロード時間が短くなる ※実行時間がはやくなるわけでない
  • 将来的には、JavaScriptを書かずにGC, DOM, Web API操作を目標としている

👎 WebAssemblyの悪いところ

  • どの言語で書こうが事前コンパイルが必須
    • 現時点でコンパイルがめちゃめちゃめんどくさい
  • 現時点ではDOMを操作する必要があり、DOMからは解放されない

WebAssemblyを使ってみる

WASMを生成するツール

wasmを生成するツール

TypeScriptからWASMを生成する

AssemblyScript NEXT

竹内関数を実装してみる

function tarai(x: u32, y: u32, z: u32): u32 {
  if (x <= y) {
    return y;
  }
  return tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y));
}

export { tarai };
const fs = require("fs");
const assert = require("assert");

const takeuchiWasmPath = __dirname + "/../" + "takeuchi.optimized.wasm";
const takeuchi = new WebAssembly.Instance(
  WebAssembly.Module(fs.readFileSync(takeuchiWasmPath))
).exports;

assert.strictEqual(2, takeuchi.tarai(1, 2, 3));
assert.strictEqual(3, takeuchi.tarai(2, 1, 3));
assert.strictEqual(12, takeuchi.tarai(12, 6, 0))
//=> RangeError: Maximum call stack size exceeded

RangeError

ref: An overview of WebAssembly; how it is used, created, and applied? by N. Shimizu

ジョン・マッカーシーtak関数にしてみる

tarai(12, 6, 0) では 12,604,860 回 tarai が呼ばれるのに対し、 tak(12, 6, 0) では tak は 63,608 回しか呼ばれない

らしい。

x <= yのときにyではなくzを返す。

Wikipedia:竹内関数#マッカーシー版

function tak(x: u32, y: u32, z: u32): u32 {
  if (x <= y) {
    return z;
  }
  return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
}

export { tak };
const fs = require("fs");
const assert = require("assert");

const takeuchiWasmPath = __dirname + "/../" + "takeuchi.optimized.wasm";
const takeuchi = new WebAssembly.Instance(
  WebAssembly.Module(fs.readFileSync(takeuchiWasmPath))
).exports;

assert.strictEqual(3, takeuchi.tak(1, 2, 3));
assert.strictEqual(2, takeuchi.tak(2, 1, 3));
assert.strictEqual(1, takeuchi.tak(12, 6, 0));
//=> RangeError: Maximum call stack size exceeded

変わらず…。

u32からi32に試しに変えてみるか

function tarai(x: i32, y: i32, z: i32): i32 {
  if (x <= y) {
    return y;
  }
  return tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y));
}

function tak(x: i32, y: i32, z: i32): i32 {
  if (x <= y) {
    return z;
  }
  return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
}

export { tarai, tak };
const fs = require("fs");
const assert = require("assert");

const takeuchiWasmPath = __dirname + "/../" + "takeuchi.optimized.wasm";
const takeuchi = new WebAssembly.Instance(
  WebAssembly.Module(fs.readFileSync(takeuchiWasmPath))
).exports;

assert.strictEqual(2, takeuchi.tarai(1, 2, 3));
assert.strictEqual(3, takeuchi.tarai(2, 1, 3));
assert.strictEqual(12, takeuchi.tarai(12, 6, 0))
//=> RangeError: Maximum call stack size exceeded

assert.strictEqual(3, takeuchi.tak(1, 2, 3));
assert.strictEqual(2, takeuchi.tak(2, 1, 3));
assert.strictEqual(1, takeuchi.tak(12, 6, 0));

通った�

通った…!

AssemblyScript: u32/i32 limits

普通のunsigned intっぽい

�原因は追いきれずここまでで時間切れ…

メモリをとても意識することになります

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment