第1回では、第4のWeb言語とされ、W3Cで標準化されているWebAssemblyの概要を紹介します。WebAssemblyの仕組みと用途、Webブラウザをはじめとしたランタイム、非ブラウザAPIやプログラミング言語のサポートなどを紹介します。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。
WebAssemblyは、第4のWeb言語と称される、WebAssemblyアプリケーションにおけるフロントエンド高速化の仕組みです。WASMと略されることもあります。まずは、WebAssembly登場の背景から、WebAssemblyが何者であるのか紹介していきます。
Webブラウザにおけるプログラミング言語といえば、やはりJavaScriptです。JavaScriptの登場当時からしばらくの間は、Webページにギミック的な機能を盛り込むといった用途が主で、利用はあくまでもHTMLの補助という位置付けでした。しかも、ブラウザによる動作の差異も大きく、どのようなブラウザでも実行できるスクリプトを記述するのは骨の折れる作業でした。
これが、2000年代中盤以降、以下のように状況が変化します。
これらを受けて、JavaScriptの利用幅が大きく広がりました。それは、ビジネスアプリケーション、2Dと3Dのゲーム、仮想・拡張現実(VR・AR)、画像や動画の編集、DTMといった領域にまで及びました。
ただし、Webアプリケーションにおけるフロントエンドの比重が高まるにつれ、インタープリタ型のスクリプト言語であるJavaScriptの不利な面が目立ってきました。プログラミング言語としてのJavaScriptは汎用(はんよう)性が高いものですが、上記の3Dゲームのような用途で使用しようとすると、パフォーマンスの問題に直面したのです。JIT(Just In Time)という、JavaScriptスクリプトを実行時にコンパイルする仕組みも導入されましたが、コンパイルのための時間が必要なため即時性に劣るなど、根本的な解決手段とはなっていません。
このような背景を受けて、より高速なWebブラウザの実行環境が模索されてきました。そこで登場したのが、asm.jsでありWebAssemblyです。
ここで、asm.jsを簡単に取り上げておきます。asm.jsは、WebAssemblyの登場以前に、高速なWebブラウザの実行環境を目的に開発されました。MozillaとGoogleが中心となって、2013年から開発が進められました。現在でも、主要なWebブラウザでasm.jsがサポートされています。ただし、仕様を定めた下記の公式サイトでも更新は停止したままになっており、Webブラウザごとの実装も微妙に異なることで、中途半端さは否めない状況です。
asm.jsでは、AOT(Ahead of Time)と呼ばれる事前コンパイル技術によって高速化を達成しています。JavaScriptのスクリプトは、最終的にネイティブコードに変換され実行されます。冒頭でも触れた、JavaScriptプログラムにおけるJITによる実行時のコンパイルにおける問題点を克服するために開発されました。実行に先立ちコンパイルされることで、即時性を向上させようというものです。
さらにasm.jsは、効率的なネイティブコードの生成のために、言語仕様をかなり制限したJavaScriptのサブセットとなっています。例えば、以下のような制約があります。
これらの制約は、実際にJavaScriptプログラムを記述する上で、かなり深刻なものです。変数や式の型を静的に解析できるというのはまだしも、数値計算に特化する、オブジェクトが使えない、コレクションが事実上配列のみという制約は、利用の場をかなり狭めるものでした。
WebAssemblyを一言で表現すると、Webブラウザにおけるプログラム実行のための新しい仕組みであり、拡張子が.wasmであるファイルフォーマットの一つです。Webブラウザは、この.wasmファイルを読み込んでプログラムとして実行します。JavaScriptが、拡張子が.jsであるファイルであり、それを読み込んでプログラムとして実行するのと、同様の位置付けということができます。
WebAssemblyは、2015年にオープンソースプロジェクトとして初めて公開されました。唐突に登場したわけではなく、前述したasm.jsの試行錯誤を経て、そのバイナリフォーマット版のような位置付けで登場しました。WebAssemblyは、独自の命令セットを持つ仮想マシンの1方式であり、専用のアセンブリ言語で記述するほかに、C++などのプログラミング言語のコンパイルターゲットとしてバイナリを生成できます。そのバイナリをWebブラウザ内の仮想マシンで動作させることで、ネイティブコードに匹敵する高速なプログラムの実行を可能にしています。
なお、WebAssemblyという名称は、Webブラウザで動作するマシンコード(機械語)と、そのソースであるアセンブリ言語の関係から来ているようです。
動作イメージとしては、上の図のようにJava言語とJava仮想マシン(JVM)の関係に似ています。ただしWebAssemblyでは、仮想マシンコードは効率的なネイティブコードの生成のために単純化され、スタックマシンとして構成されています。仮想マシンは、シンプルな仮想マシンコードのネイティブコードへのコンパイルに専念すればよく、優れたパフォーマンスを発揮します。
WebAssemblyでは、バイナリファイルをブラウザがダウンロードし、ネイティブコードにコンパイルすることで高速な実行を可能にしています。また、ChromeのV8、FirefoxのChakraといったJavaScriptエンジンでは、Streaming Compilationによってダウンロードと並行してコンパイルすることで、さらなる高速化を実現しています。
スタックマシンは、演算のための作業領域がスタック構造になっている計算モデルをいいます。演算対象はあらかじめスタックに積まれており、演算命令はそれを取り出して(POP)演算処理を実行し、演算結果を再びスタックに置きます(PUSH)。この演算結果は次の命令でそのまま使用できます。
メリットは、演算命令がシンプルでコンパクトなことですが、演算をメモリ対象に実行するため速度の面では若干不利になります。このため、高速なアクセスが可能なレジスタをスタック構造にしたスタックマシンが、Android OSの仮想マシンであるDalvikなどで使われました。
WebAssemblyは、初公開の2年後となる2017年には、Firefox 52.0を皮切りにGoogle Chrome、Apple Safari、Microsoft Edgeの主要なブラウザエンジンでサポートされました。これによって、WebAssemblyが動作する基盤が早くも整ったといえます。そして2019年には、W3Cの勧告「WebAssembly Core Specification」「WebAssembly JavaScript Interface」「WebAssembly Web API」が策定され、WebAssemblyは正式なWeb標準WebAssembly 1.0に認定されました。本連載は、このWebAssembly 1.0に準じています。現在も、W3CのWebAssembly Working Groupによって標準化が進められており、本稿作成時点ではWebAssembly 2.0のワーキングドラフトが公開されています。
仕様の策定をはじめとした開発は、Google、Mozilla、Apple、MicrosoftなどのWebブラウザのデベロッパーによって進められています。
WebAssemblyの基本ポリシーは、「WebAssembly Core Specification」によると以下のようになっています。
高速で効率がよいということは、WebAssembly登場の背景で求められたことであり、言うまでもないことです。スタックマシンによるコンパクトな命令セットによって、ネイティブアプリケーション並みの高速な実行が可能となっています。また、仮想マシン環境にてプラットフォーム間の差異を吸収するので、WebAssemblyのバイナリはポータビリティの高いものになっています。
また、WebAssemblyのバイナリフォーマットは人間が読んで理解できるようにはなっていませんが、アセンブリ言語に相当するソースファイルにより読み書きが可能になっています。WebAssemblyのプログラムはサンドボックス環境での実行、また同一生成元ポリシー(Same-Origin Policy)の確認を強制するなど、安全な実行が配慮されています。
最後に、非常に重要なことですが後方互換性を保持しているということです。つまり、既存の技術であるHTML、CSS、JavaScriptなどと完全に共存でき、JavaScriptからWebAssemblyの関数を呼び出すこと、またはその逆も可能となっています。
WebAssemblyは、Webブラウザにおける高速な実行環境としてスタートしましたが、現在ではWebブラウザの外、すなわちスタンドアロンでの実行環境も整備されはじめています。これは、コンパクトかつ高速で、しかもポータブルであるというWebAssemblyのメリットに注目した動きと言えます。
しかしながら、WebAssemblyはWebブラウザでの動作を想定したものであったので、そのままではWebブラウザでない実行環境でOSリソースへのアクセスができないなどの制約がありました。そこで、WASI(WebAssembly System Interface)というOSリソースなどのアクセスを規定したAPIセットが定められました。これにより、Webブラウザの垣根を跳び越えて、WebAssemblyの活用の領域が広がりました。
インテルやRed Hat、CDN(Content Delivery Network)事業者であるFastly、それにMozillaは非Webブラウザ環境でもWebAssemblyを動作させること(outside-the-browser)を目指した団体「Bytecode Alliance」を2019年に立ち上げています。WASIの仕様は、このBytecode Allianceが主体となって作成されています。
なお、Bytecode Allianceは、PCデスクトップ、サーバ、IoTデバイス上でWebAssemblyを実行させることを目的としていますが、そのために推進するモデルがナノプロセス(nanoprocess)です。
WASIの実装には、Wasmer、WasmEdge、Wasmtimeなどがあります。中でもBytecode AllianceによるWasmtimeはRustで実装されており軽量、コンパクト、高速と評価されています。最新版は2022年9月にリリースされたバージョン1.0です。
Webブラウザにおける、Debian GNU/LinuxといったOS、PostgreSQLやSQLiteなどのDB、Pythonなどのプログラミング言語の実行も試みられています。FastlyのLucetというWebAssemblyを用いた高速な実行環境では、同社のCDNサイトのパフォーマンス向上に寄与しているということです。
WebAssemblyプログラムを開発する際の方針としては、主に以下の3パターンとなります。
1.は、C/C++で書かれたネイティブアプリケーションをWebに移植するパターンです。例えばゲームです。PCあるいはモバイル端末向けに作成されたゲームを移植して、Webブラウザ上でゲームを実行し、楽しむことができます。ゲームの実行にアプリのインストールが不要ですので、ユーザーの利便性向上に役立ちます。
2.は、JavaScriptで組まれていたロジックをWebAssemblyで記述し、フォームなどのコンポーネントはそのまま用いることで、フロントエンド全体の動作速度を向上させるというものです。可能であれば、ロジックを全てWebAssemblyで記述するのが望ましいのですが、従来のJavaScriptを単純に置き換えられるわけでもないので、この方針をとれるかというのはどのようなロジックを採用するかということにも依存します。
3.は、本当に速度が重要視される部分だけをWebAssemblyで記述し、その他の部分は従来のJavaScriptを用いるなどして適材適所を目指すという考え方です。フロントエンド全体で常に最速が求められているケースということは少ないと思われるので、例えば既述のグラフィックス処理やメディア関連の処理などはWebAssemblyで速度を向上させ、UIの構築やイベントの処理などは従来技術を用います。
1.はフロントエンドのエンジニアにはハードルが高く、2.はWebAssemblyがJavaScriptを全て置き換えられるわけではないことから、3.の選択肢が当面は現実的かと思われます。
WebAssemblyをサポートするプログラミング言語は増えてきており、いずれかを選択してプログラムを開発できます。このときも、JavaScriptプログラムとの連携が可能です。WebAssemblyプログラムの開発には、本稿作成時点で主に以下の選択肢があります。
このほか、コンパイラ基盤(LLVM)もWebAssemblyをターゲットにした出力に対応しています。
LLVMとは、C言語系、FORTRANなど、さまざまなプログラミング言語に対応できる共通のコンパイラ基盤です。以前は、Low Level Virtual Machine(低水準仮想機械)の略とされていましたが、現在は単に(何の頭文字でもない)LLVMとされています。
WebAssemblyのメリットを最大限に生かすなら、後述するWebAssemblyのアセンブリ言語(WAST)で直接記述するか、C/C++やRust、Goといった静的型付けのプログラミング言語が適しています。ただし、これらの言語はJavaScriptといったスクリプト言語に比べると習得の難易度が一般的に高くなるため、WebAssemblyにはじめて取り組むという場合には学習コストも高いものになってしまいます。ただし、すでにC/C++などによる開発資産があり、それをWebAssemblyに移行させるという場合には、言語のメリットを最大限に生かせるでしょう(前述の、ゲームの移植のようなケースです)。
なお、同じく静的な型付け言語であるJavaなどの、ガベージコレクタ(GC)を用いるプログラミング言語のサポートも将来的には計画されているようですが、現時点では選択肢にはなりません。.NETにおけるC#はあくまでもWebアプリケーションの開発という制限付きですが、Blazor WebAssemblyというフレームワークでWebAssemblyの開発を可能にしています。もし、ターゲットがWebアプリケーションのみということなら、C#は有力な選択肢になるでしょう。ただし、C#および.NETの学習コストはC/C++やRustなどと比べると低いのですが、JavaScriptやPythonと比べると若干高めになります。
WebAssemblyのメリットを十分に生かせないまでも、従来使用してきたJavaScriptの資産やプログラミング言語の学習コストを考慮するなら、JavaScriptやTypeScript、Pythonなどのスクリプト型のプログラミング言語を選択できます。特にJavaScriptとTypeScriptでは、AssemblyScriptやDenoといったツールを使うことでWebAssemblyプログラムを作成できます。若干の制約はあるにしろJavaScriptの経験、資産を生かせます。
また、Pythonでは将来のバージョンでWebAssemblyのサポートが予定されているほか、PyScriptというPythonの実行環境をそのままWebAssemblyプログラムにしたものを使う選択肢もあります。いずれにしろ、なじみのあるスクリプト型プログラミング言語を使いたいとなれば、これらが有効な選択肢になるでしょう。
WebAssemblyのための開発環境として、MozillaによるWebAssembly Studioがありましたが、現在は開発が止まっているようでGitHubのリポジトリも読み取り専用になっています。検索エンジンで見つかるサイト、後述するExplorerのサイトに配置されたリンクは、その先はドメイン転売のためのダミーサイトになっているようなので要注意です。
WebAssemblyにおける開発には、ドキュメントが各所で整備されていますので参考になるでしょう。特にMDN(Mozilla Developer Network)では、C++、Rust、WASMによるチュートリアルが紹介されています。
WebAssemblyプログラムは、C/C++やTypeScriptといったプログラミング言語を用いることで、その内部構成を特に意識しなくても開発が可能です(もちろん、おのおのの言語におけるルールの理解は必要です)。しかし、内部構成を簡単ながらも押さえておくことは、実際の開発においては一助となることもあるでしょう。ここでは、WebAssemblyプログラムの構成を簡単にまとめておきます。
「WebAssembly JavaScript API」によると、WebAssemblyプログラムを開発する上で知っておくべき主な概念は以下のようになっています。
モジュールは、実行可能なWebAssemblyのバイナリであり、WebAssemblyプログラムの本体です。Webブラウザでは、このモジュールを読み込み、実行します。JavaScriptとのインタフェースとして、呼び出しのためのimport宣言とexport宣言を含んでいます。
メモリは、サイズ変更が可能なメモリ領域であり、バイト列からなる1次元の配列(ArrayBuffer)です。WebAssemblyモジュール内のメモリアクセス命令から読み書きされる領域です。
テーブルは、メモリと同様にサイズ変更が可能なメモリ領域ですが、型付き配列であり、WebAssemblyの関数などに対する参照などが格納される領域です。
最後のインスタンスは、モジュール、メモリ、テーブルを実体化したものです。<script>タグによって読み込まれたESモジュールと同じ位置付けとなります。インスタンスを通じて、モジュール内のエクスポートされた関数を呼び出したり、メモリとテーブルにアクセスしたりできます。
これらは、JavaScriptのAPIに対応しており、以下の図のようにWebAssemblyオブジェクトのメソッドを使って生成、アクセスします。
WebAssemblyプログラムはバイナリファイルであるため、これを直接作成するのは困難です。もちろん、プログラミング言語を用いればWebAssemblyプログラムのバイナリファイルを作成できますが、より直接的にテキスト形式でWebAssemblyプログラムを記述できます。いわば、ネイティブな機械語とアセンブリ言語の関係と同じです。
これはWASTと呼ばれます。WASTのASTはAbstract Syntax Treeの略で、抽象構文木と呼ばれます。抽象構文木とは、プログラムの構文を木構造で表したもののうち、コードの生成に無関係な部分をそぎ落として抽象化したものをいいます。つまり、WASTはASTのWebAssembly版というわけです。
テキスト形式のWebAssemblyプログラムは、この抽象構文木(WAST)を用いて記述していきます。ファイルの拡張子は、.wat(.wast)が一般的です。WASTでは、抽象構文木をS式で記述します。S式とは、木構造を入れ子になったカッコで表したもので、Lispなどの関数型プログラミング言語で用いられる形式です。例えば、以下の式があったとします。
a = b * ( c + 4 );
これを抽象構文木と、それに対応するS式にしてみたものが以下の図です。
図は、抽象構文木とS式をイメージしていただくために単純化していますが、実際には「=」や「*」の箇所にはそれぞれの演算を実行する関数などを記述していきます。実行するときには、構文木の末端部分の左側の枝から、すなわちS式の最も内側のカッコから評価していきます。
「int a = 10;」という単純なC/C++の構文を、WASTのS式で表現してみたのが以下です(後述のThe WASM Explorerでコンパイルしたもの)。先ほど取り上げた、モジュール、テーブル、メモリ、エクスポートといった命令が見てとれるでしょう。
(module (table 0 anyfunc) (memory $0 1) (data (i32.const 12) "\n\00\00\00") (export "memory" (memory $0)) )
WASTをバイナリ形式に変換するには専用のツール(The WebAssembly Binary Toolkit)を使用します。以下から入手できます。
WebAssembly/wabt: The WebAssembly Binary Toolkit
なお、C/C++言語のプログラムを入力し、その場でWASTのコードに変換したり、LLVM形式のx86バイナリに変換したりできるWASM Explorerというサイトがあります。このサイトを利用すると、C/C++のコードがどのようなS式に変換されるのか確認でき、学習や検証に便利です。
最後に、WebAssemblyプログラムの読み込みと実行に触れておきます。WebAssemblyプログラムは、ESモジュールのような<script>タグやimport文などによる読み込みが、本稿の作成時点においてはサポートされていません(将来的には可能になる見込みです)。そこで、Fetch APIを使用してバイナリファイル(.wasm)を読み込み、それをWebAssembly.instantiateStreamingメソッドでバイト列のオブジェクトに変換します。あとは、そのオブジェクトのインスタンスにアクセスし、エクスポートされているメモリ、テーブル、関数にアクセスするだけです。以下は、この一連の処理を示したJavaScriptコードの例です。
WebAssembly.instantiateStreaming(fetch('sample.wasm'), importObject) .then((obj) => { // エクスポートされた関数の呼び出し obj.instance.exports.function(); // メモリの読み込み const i32 = new Uint32Array(obj.instance.exports.memory.buffer); // テーブルの読み込み const table = obj.instance.exports.table; })
また、既述の通りWebAssemblyプログラムの実行には同一生成元ポリシーの確認が強制されているので、一般にはHTTPサーバが必要になります。ローカルにWebAssemblyプログラムファイル(.wasm)を置いて、fileスキームでアクセスする方法は使えません。HTTPサーバは、C++におけるEmscriptenではemrunコマンドが使えるなど、各プログラミング言語におけるサポートの違いがあるので、以降の各回で触れることにします。
今回は、連載第1回として、WebAssemblyの仕組みと用途、開発手法の選択肢、プログラミング言語のサポートなどを紹介しました。
次回は、フロントエンド開発で実績のある定番のJavaScriptとTypeScriptを用いたWebAssemblyプログラムの開発事例を紹介します。
WINGSプロジェクト
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
・サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/)
・RSS(https://wings.msn.to/contents/rss.php)
・Twitter: @yyamada(https://twitter.com/yyamada)
・Facebook(https://www.facebook.com/WINGSProject)
Copyright © ITmedia, Inc. All Rights Reserved.