「Wasm初めの一歩」WebAssemblyとは ~クラウドエンジニアの目線で

記事タイトルとURLをコピーする

はじめに

皆さん初めまして、サーバーワークス25新卒のYiと申します。

本日は、WebAssembly(以下、Wasm)について、基本的な仕組みから、なぜ注目されているか、 そしてクラウドにどのような意味を持つのかまで、新卒エンジニアの目線で見ていきたいと思います。

Wasm のロゴです。シンプルなデザインですが、内容は...

なぜ、WebAssemblyか

はじめに、私が Wasm をご紹介したい背景からご説明します。

私自身、以前から Docker のようなコンテナ技術をはじめとする仮想化の仕組みに強い関心を持っていました。

そのため、下記の Solomon Hykes 氏の言葉には特に強い興味を惹かれ、Wasm という技術の可能性について深く考えるきっかけとなりました。

If Wasm+WASI existed in 2008, we wouldn't have needed to created Docker. That's how important it is. - Solomon Hykes, author of Docker

では、実際に Wasm を採用しているツールにはどのようなものがあるのでしょうか。

FigmaはWasmで動いている

FigmaはUI/UXデザインができる、非常に人気の高いツールです。

デザイナー、開発者、プロダクトマネジャーがリアルタイムでファイルを同時に編集・閲覧し、スムーズに連携しながらコラボレーションできます。

そのために、Figmaは大量のコンピューティングリソースを必要とするアプリケーションです。

同時に、Figmaがローカル環境ではなくWebアプリケーションとして提供されていることを考えると、非効率な方法で実行することは避けるべきでしょう。

WebAssembly cut Figma's load time by 3x
- Evan WallaceCo-founder, Figma

上記の一文が示す通り、Figma は Wasm を採用*1することで、複雑な処理を伴うWebアプリケーションのパフォーマンスを劇的に向上させています。

では、一体 Wasm とは何なのか、そしてどうしてこのような改善が可能になったのか、その理由を見ていきましょう。

WebAssenbly とは

言語の水準とWebAssemblyの位置づけ

まずは、Wasm がプログラミング言語の中でどのような位置づけにあるかを見ていきましょう。

プログラミング言語は、人間とコンピューター、どっちか分かりやすいかで高水準言語と低水準言語で分類しています。

Wasm はその中で低水準言語に近いです。つまり、人間にとっては理解しづらいですが、コンピューターに分かりやすい言語です。

Webのためのバイナリコード

では改めてWasmはなんでしょうか。

WebAssemblyは、その名の通り「Web」と「Assembly」を組み合わせた言葉です。Web上で、アセンブリ言語のように効率的に動作する言語、という意味合いを持っています。

今まで、Webブラウザは JavaScript のみ理解し、実行することができました。しかし、最近の段々性能の問題が発生し、JavaScript のみでは問題を解決することができない状態になりました。

このような問題を解決するために、新しく設計されたのが Wasm です。

Wasm は 0 と 1 で構成された(バイナリ)コードで、コンピューターが読みやすい形式になっています。

Wasmの特徴は?

ではWasmを使うことで得られる利点はなにがあるでしょうか?

  • 高い実行性能
  • 高い安全性
  • 高い移植性
  • 多様な言語で記述可能

これらの特徴は非常に魅力的ですが、なぜWebAssemblyがこれらの特性を持つのか、理解しにくいかもしれません。

その理由を理解するために、まずはWebAssemblyの仕組みについて詳しく見ていきましょう。

Wasmの生成から実行まで

まず、Wasmはどのように生成されるか?

前述の通り、Wasm は人間が直接記述するには適していません。

そこで、C/C++ や Rust などの高水準言語で書かれたソースコードを、Wasm バイナリ(.wasm ファイル)にコンパイルして生成するのが一般的です。

そうすることで、高水準言語を使用して効率的に低水準言語のWasmのコードを書くことができます。

高水準言語がWasmになるまで

ここでは具体例として、高水準言語であるRustのコードがWasmに変換される流れを見ていきましょう。

Wasmへの変換方法は、使用するプログラミング言語によって異なりますが、多くの言語でLLVMのような既存のコンパイラ基盤が利用されています。

(LLVMは、コンパイラの中間表現(以下、IR)を扱い、様々なプログラミング言語のコンパイルや最適化を行うためのツール群・フレームワークです。)

Rust言語の場合、rustc というRust専用のコンパイラがソースコードを解析し、LLVMが理解できるIRに変換します。

LLVMはこのIRに対して様々な最適化を施した後、Wasm専用のバックエンドが最終的な .wasm バイナリコードを生成します。

(このプロセスでは、必要に応じてリンカーなども働き、依存関係の解決などが行われます。)

JavaScriptとWasmインスタンスの関係

WebブラウザなどのJavaScript環境でWebAssemblyを利用する場合、通常はwasm-bindgenのようなツールを用いて、WebAssemblyとJavaScriptの間で連携するための「グルーコード」と呼ばれるJavaScriptコードも生成されます。

(WebAssembly単体では、まだWebブラウザのDOM操作などの機能に直接アクセスできないため、JavaScriptの助けが必要です!)

グルーコードについては、後ほど詳しく説明します。

Rust以外の言語でも、それぞれ独自のコンパイラやツールチェーンを使ってWebAssemblyを生成します。

しかし、最終的に.wasmバイナリコードと、必要に応じてJavaScriptとの連携のためのグルーコードが生成されるという点は共通しています。

これで、高水準言語からコンパイラツールチェーンを経て、.wasmファイルとグルーコードが生成される一連の流れをご理解いただけたかと思います。

ここで理解できるWebAssemblyの移植性

WebAssemblyバイナリは、特定のCPUアーキテクチャ(x86, ARMなど)やOSに直接依存しない中間的なコードです。これは、Javaのバイトコードに似ていますね。

そのため、一度WebAssembly形式にコンパイルすれば、様々なCPUアーキテクチャやOS上で動作させることが可能です。

これがWebAssemblyの持つ高い移植性、つまりクロスプラットフォーム性です。

Wasmはどのように動くか?

では次に、生成された.wasmファイルがブラウザ上でどのように実行されるのかを見ていきましょう。

従来のWebブラウザでのコード実行

WebブラウザがWebページを表示し、インタラクティブな機能を提供するために、HTML、CSS、JavaScriptといったファイルが使われます。

まず、HTMLとCSSはページの構造や見た目を定義するマークアップ言語・スタイルシート言語です。

次に、JavaScriptは、Webページに動きや対話性をもたらすプログラミング言語です。

ユーザーの操作に応じた処理や、表示内容の動的な変更などを行います。

これまで、Webブラウザが直接実行できるプログラミング言語はJavaScriptだけでした。

これは、WebブラウザにJavaScriptエンジン(一般的に「VM」や「仮想マシン」と呼ばれることもあります)が搭載されているからです。

JavaScriptエンジンは、JavaScriptコードを読み込み、コンピューターが理解できる機械語に変換して実行します。

初期のJavaScript エンジンは、コードを一行ずつ読み込み、その場で機械語に変換して実行する「インタプリタ方式」が主流でした。

しかし、この方式では実行速度に限界があり、複雑化する現代のWebアプリケーションの要求を満たすのが難しくなってきました。

このような課題を克服するために、JavaScriptエンジンには「Just-In-Timeコンパイラ(以下、JITコンパイラ)」が導入されました。

JITコンパイラは早い(昔よりは)

実際、JITコンパイラが実行されるとどのような作業が行われるでしょうか。

JITコンパイラが行う作業を見ていきましょう。

1.パーシング

コードを実行できる形式に変換します。

2.コンパイル・最適化

コードをコンパイルし、最適化を行います。

3.再最適化

JITが予測した最適化が失敗した場合、再度最適化をするために必要な時間です。

4.実行

コードを実行する時間です。

5.ガーベジコレクション

メモリを解放に必要な時間です。

上記で触れたように、JITコンパイラは、まずコード全体を迅速に(または、基本的なレベルで)コンパイルします。

そして、プログラムの実行中に頻繁に使用される「ホットスポット」と呼ばれる箇所を特定し、そこを重点的に高度な最適化を施して再コンパイルします。

この段階的なコンパイルと最適化過程により、JITコンパイラが導入される以前のJavaScriptエンジンに比べて、プログラムの実行速度を大幅に向上させました。

Wasmの実行速度は?

JITの導入前、JIT導入後、そしてWasmの場合を考えるとしたのような図になります。

Wasm と JavaScript エンジン

Wasmエンジンは、.wasmバイナリコードを直接読み込み、JavaScriptのJITコンパイルよりも効率的かつ高速に機械語にコンパイルします。

このコンパイルされたコードは「インスタンス」としてメモリ上に生成されます。

インスタンスは、外部から隔離された独自のメモリ空間を持ちます。

これにより、WebAssemblyコードがブラウザや他のコードのメモリ領域に意図せずアクセスしたり、影響を与えたりするリスクが最小限に抑えられます。

つまり、許可された範囲内でのみ動作するため、既存のJavaScript実行モデルと比較してセキュリティが強化されています。

JavaScript は Wasm インスタンスを呼び出しできる

JavaScriptコードは、必要に応じてこのWasmインスタンスを呼び出し、その機能を利用することができます。

Wasmインスタンスを呼び出しに使われるJavaScriptコードをJavaScriptグルーコードと言います。

必要に応じて、Wasmインスタンスを呼び出すことで、性能が必要なときに最適化されたWasmインスタンスを活用することができますね。

例えば、複雑な計算処理や動画・音声処理、ゲームエンジンなど、高いパフォーマンスが求められる部分をWasmで実装し、UI操作などはJavaScriptで行う、といった使い分けが可能です。

これにより、同じ処理でもJavaScriptで実行するよりWebAssemblyインスタンスを呼び出して実行する方が、はるかに高速になるわけです。

これで、先ほど挙げたWasmの「高速性」という特徴がなぜ実現されるのか、ご理解いただけたかと思います。

サンドボックス環境としてのWasm

上記のように、Wasm のコンパイルされたコードが独自のメモリ空間を持つことでサンドボックス環境をなしています。

(サンドボックスとは、外部のシステムと隔離された環境のことを指します。)

特にWeb環境ではサンドボックスモデルを適用することで、ダウンロードされたコードがシステムを損傷することを防ぎます。

ではWasmのサンドボックスとしての特性を見ていきます。

1.メモリの隔離

前述の通り、Wasmインスタンスは生成される時に決められたメモリ領域があるため、隔離された独自のメモリ空間を持つことが分かります。

つまり、Wasmが実行されているコンピューティングリソースのメモリへ直接触れることができないということです。

これはオーバーフローのような、メモリ関連セキュリティー問題を防ぐことができます。

2.システムへのアクセス制限

Wasm インスタンスをシステムへアクセスさせるためには、明示的に JavaScript などで宣言する必要があります。

(この特徴は後述するWebAssembly System Interfaceで詳しく見ていきます。)

この特徴は「最小権限」を与えることが容易になりますね。

3.明視的に決められたインタフェース

Wasm は後述する WASI という決められたインタフェースで外部と通信します。

上記の特性から Wasmの「安全性」という特徴がなぜ実現されるのか、ご理解いただけたかと思います。

しかし、WebAssemblyが注目されている理由はこれだけではありません。

Wasmはブラウザで終わらない。ブラウザの外へ

ここまでのWasmはブラウザ上で動くバイナリコードでした。

WebAssembly System Interfaceの登場

WebAssemblyをブラウザの外、例えばサーバーサイドやデスクトップアプリケーションなどで活用することはできないでしょうか?

このようなニーズに応えるために開発されたのが、WebAssembly System Interface(以下、WASI)です。

WASI はブラウザ外部で Wasm が実行することができるように定義されたインタフェースです。

WASIは、ファイルシステムへのアクセス、ネットワーク通信、環境変数など、OSが提供する機能へのアクセス方法を抽象化された形で提供します。

これにより、Wasm コードはWASIという共通のインターフェースを介して、実行される環境(Windows, Linux, macOS など)の違いを意識することなくこれらの機能を利用できます。

WASIもまた、サンドボックスモデルを採用しており、Wasmコードがシステムのリソースに無制限にアクセスすることを防ぎ、高いセキュリティを保ちます。

開発者はWASIに準拠してWasmコードを作成すれば、様々な環境での動作が期待できるため、開発効率の向上にもつながります。

WASIの「世界」

実際、WASIが提供している機能は下記のようなものがあります

1.wasi-http

WasmモジュールがHTTPプロトコルを用いた通信を行うためのインターフェースです。

2.wasi-cli

Wasmモジュールがコマンドラインインターフェース(CLI)として機能するために必要なインターフェースです。

環境変数やコマンドライン引数へのアクセス、標準入出力(stdin/stdout/stderr)の操作などが可能になります。

3.wasi-sockets

TCP/UDPソケットを利用した、低レベルなネットワーク通信機能を提供するインターフェースです。

4.wasi-filesystem

ファイルやディレクトリへのアクセス、読み書き、管理といったファイルシステム操作を実現するインターフェースです。

5.wasi-keyvalue

キーバリューストアへのアクセスを抽象化するインターフェースです。

アトミック操作やバッチ処理といった、トランザクションに類する機能も定義されています。

6.wasi-clocks

単調増加時間(経過時間の測定用)や現在時刻といった、時間情報へのアクセスを提供するインターフェースです。

7.wasi-random

(暗号学的に安全な)乱数を生成するためのインターフェースです。

以上が、現在のWASIにおける主要なインターフェース(またはそのグループ)の概要です。

現在(2025年5月)今年中にWASI 0.3 公開が予定されています。

今後もさらに多くの機能が追加され、WASIがますます進化していくのが本当に楽しみです。

Wasmの未来、WebAssembly Component Model

高水準言語からWasmへの変換プロセスをご覧になる中で、「異なる言語で作られたWasmモジュール間で、データをやり取りできないのだろうか?」と疑問に思われた方もいらっしゃるかもしれません。

なぜ、言語間のやり取りは難しいのか

現状のWasmでは、異なるプログラミング言語で作成されたモジュール間で、特に複雑なデータ(文字列やリスト、構造体など)を直接やり取りすることは困難でした。

これは、Wasmがインターフェースを通じて直接扱えるデータ型が、基本的に数値型(整数や浮動小数点数)に限られているためです。

例えば、RustとC言語でそれぞれ記述されたWasmモジュール間で、文字列やリストのようなデータを交換しようとすると、各言語のメモリ管理やデータ表現の違いを吸収するための「グルーコード」を開発者が手作業で記述する必要がありました。

さらに、複数のWasmモジュールを組み合わせて大規模なアプリケーションを開発する際には、各モジュールのインターフェース定義に一貫性を持たせることが難しく、連携時に不整合が生じやすいという課題もありました。

このような課題を解決するために提案されたのが、WebAssembly Component Modelです。

WebAssembly Component Modelの目標

WebAssembly Component Modelは次のような目標を持っています。

  • 言語中立性
  • 容易なモジュール構成
  • インターフェースの抽象化

これらの目標を達成するために、Component Modelは「コンポーネント」、「インターフェースタイプ(WIT)」、そしてこれら異なる抽象度を持つ要素間を繋ぐ「アダプター」という主要な概念を元に、設計・開発が進められています。

クラウドにおいて Wasm はどのような意味を持つか?

WASIの登場により、WebAssemblyはWebブラウザだけでなく、サーバーサイドやクラウド環境での利用にも大きな可能性を秘めるようになりました。

WebAssemblyの持つ「高い移植性」「高い安全性」「高速な実行」といった特徴は、クラウドネイティブなアプリケーション開発など、特に以下のような領域で非常に相性が良いと考えられています。

サーバーレスコンピューティング

従来のサーバーレス関数は、コンテナイメージのサイズが大きい、あるいは起動に時間がかかるといった課題を抱えていました。

これは、コンテナ内部にOSを含め、実行に必要な全ての情報を格納する必要があるためです。

特に、非アクティブな状態から初めて呼び出される際(いわゆるコールドスタート時)には、イメージサイズやOSの起動プロセスが原因で、顕著な遅延が発生することがありました。

しかし、Wasmは軽量なランタイムにより、モジュールの初期化をミリ秒単位、場合によってはマイクロ秒単位で完了させることが可能です。

この迅速な起動は、コンテナベースのサーバーレスと比較して大きな利点となり、結果として、低遅延、低リソース消費、セキュリティの向上、そして高いスケーラビリティといったメリットを提供します。

エッジコンピューティング

ネットワークの末端(エッジ)に近い場所で処理を行うエッジコンピューティングでは、コンピューティングリソースが限られているケースが多く見られます。

軽量かつ高速に動作するWasmは、こうしたエッジデバイス上でのアプリケーション実行に適しています。

また、エッジデバイスにおいては、コンテナと比較してWasmモジュールは使用するリソースが小さいため、効率の面で有利です。

加えて、インスタンス化されたモジュールが提供するサンドボックス環境は、セキュリティ面での利点を一層強化します。

(コンテナはOSカーネルを共有しますが、Wasmは各モジュールが自身の独立したメモリ空間内でサンドボックス化されるため、よりきめ細かい分離が可能です。)

クラウドの構成要素を結合する

小さなサービスを組み合わせてアプリケーションを構築するマイクロサービスアーキテクチャにおいて、Wasmは汎用プラグインのような機能を提供することができます。

そして、WASIという標準化されたインターフェースの存在が、このプラグインとしての利便性や移植性をさらに高める重要な要素となっています。

Wasm は万能ではない

Wasmは強力な技術ではありますが、まだ解決されていない課題もあります。

制限されるアクセス

まだ、Wasmは直接ブラウザ(DOMなど)にアクセスすることはできず、JavaScriptグルーコードに依存しています。

これはアプリケーション開発において、複雑性とオーバーヘッドを発生させます。

(オーバーヘッドは特定の処理を実行するために必要な間接的な処理時間・メモリを差します。)

ただ、将来的にはJavaScriptに頼らず実行できる計画ですので、時間はかかるものの、解決できる問題として期待して良いでしょう。

WASI が成熟していない

WASIの生態系と機能は成長していますが、まだ不十分な場合もあります。

Wasmは難しい

Wasmは高水準言語から生成されるのが普通ですが、線形メモリといった内部の仕組みの理解は、ガーベジコレクション機能を持つ言語に慣れたプログラマーにとっては、習得に時間を要する場合があります。

まとめ

この記事では、WebAssemblyの基本的な仕組みから、その特徴(高速性、安全性、移植性)、そしてWASIによるブラウザ外への展開、さらにはクラウドコンピューティングとの関連性について解説しました。

WebAssemblyは、単にWebブラウザでのJavaScriptの課題を補うだけでなく、サーバーサイド、エッジ、IoTデバイスなど、様々な環境で安全かつ高速にコードを実行できる汎用的な実行基盤として進化を続けています。

Javaの「Write once, Run anywhere」というスローガンに似て、WebAssemblyは「Write once, Run (almost) anywhere」という可能性を秘めており、今後のIT業界において重要な役割を担っていくことが期待されています。

Yi JunHyung (記事一覧)

25卒 新入社員

コーヒーと共に生きる