code

WebAssembly入門 - ブラウザで動く高速バイナリフォーマット

2025.12.02

WebAssemblyとは

WebAssembly(WASM)は、ブラウザで実行可能なバイナリ命令フォーマットです。C/C++、Rust、Goなどの言語からコンパイルでき、JavaScriptに近いパフォーマンスでネイティブコードを実行できます。

flowchart LR
    subgraph Source["ソースコード"]
        Rust["Rust"]
        Cpp["C/C++"]
        Go["Go"]
    end

    subgraph Compile["コンパイル"]
        LLVM["LLVM"]
        WASM[".wasm<br/>バイナリ"]
    end

    subgraph Runtime["実行環境"]
        Browser["Browser<br/>Runtime"]
        JS["JavaScript<br/>から呼び出し"]
    end

    Source --> LLVM --> WASM --> Browser
    WASM --> JS

特徴:

  • ネイティブに近い実行速度
  • 言語に依存しない(多言語サポート)
  • サンドボックス化された安全な実行
  • JavaScriptとの相互運用
  • ストリーミングコンパイル対応

パフォーマンス比較

処理JavaScriptWASMネイティブ
行列乗算 (1000x1000)2,500ms850ms700ms
画像処理 (1920x1080)180ms72ms-
暗号化処理 (AES-256)320ms125ms-

※ JSエンジンのJIT最適化により差が縮まるケースもある

Rust + WebAssembly

セットアップ

# wasm-packのインストール
cargo install wasm-pack

# プロジェクト作成
cargo new --lib wasm-example
cd wasm-example
# Cargo.toml
[package]
name = "wasm-example"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console"] }

[profile.release]
opt-level = "z"     # サイズ最適化
lto = true          # リンク時最適化

Rust実装

// src/lib.rs
use wasm_bindgen::prelude::*;

// JavaScriptから呼び出し可能な関数
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// 文字列を扱う関数
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

// 構造体のエクスポート
#[wasm_bindgen]
pub struct Calculator {
    value: f64,
}

#[wasm_bindgen]
impl Calculator {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Calculator {
        Calculator { value: 0.0 }
    }

    pub fn add(&mut self, x: f64) {
        self.value += x;
    }

    pub fn subtract(&mut self, x: f64) {
        self.value -= x;
    }

    pub fn multiply(&mut self, x: f64) {
        self.value *= x;
    }

    pub fn divide(&mut self, x: f64) -> Result<(), JsValue> {
        if x == 0.0 {
            return Err(JsValue::from_str("Division by zero"));
        }
        self.value /= x;
        Ok(())
    }

    pub fn get_value(&self) -> f64 {
        self.value
    }

    pub fn reset(&mut self) {
        self.value = 0.0;
    }
}

// 配列処理
#[wasm_bindgen]
pub fn sum_array(arr: &[i32]) -> i32 {
    arr.iter().sum()
}

// JSのconsoleを使用
#[wasm_bindgen]
pub fn log_to_console(message: &str) {
    web_sys::console::log_1(&message.into());
}

// 高速な画像処理の例
#[wasm_bindgen]
pub fn apply_grayscale(data: &mut [u8]) {
    for chunk in data.chunks_exact_mut(4) {
        let r = chunk[0] as f32;
        let g = chunk[1] as f32;
        let b = chunk[2] as f32;
        // ITU-R BT.601 係数
        let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
        chunk[0] = gray;
        chunk[1] = gray;
        chunk[2] = gray;
        // chunk[3] (alpha) はそのまま
    }
}

// フィボナッチ数列(ベンチマーク用)
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
    if n <= 1 {
        return n as u64;
    }
    let mut a: u64 = 0;
    let mut b: u64 = 1;
    for _ in 2..=n {
        let temp = a + b;
        a = b;
        b = temp;
    }
    b
}

ビルドと使用

# ビルド
wasm-pack build --target web --release
// JavaScript/TypeScriptからの使用
import init, {
  add,
  greet,
  Calculator,
  sum_array,
  apply_grayscale,
  fibonacci
} from './pkg/wasm_example.js';

async function main() {
  // WASMモジュールの初期化
  await init();

  // 基本的な関数呼び出し
  console.log(add(2, 3)); // 5
  console.log(greet('World')); // "Hello, World!"

  // クラスの使用
  const calc = new Calculator();
  calc.add(10);
  calc.multiply(2);
  console.log(calc.get_value()); // 20

  // 配列処理
  const arr = new Int32Array([1, 2, 3, 4, 5]);
  console.log(sum_array(arr)); // 15

  // 画像処理
  const canvas = document.getElementById('canvas') as HTMLCanvasElement;
  const ctx = canvas.getContext('2d')!;
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  apply_grayscale(imageData.data);
  ctx.putImageData(imageData, 0, 0);

  // パフォーマンス計測
  console.time('WASM Fibonacci');
  fibonacci(40);
  console.timeEnd('WASM Fibonacci');
}

main();

C/C++ + Emscripten

セットアップ

# Emscriptenのインストール
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

C++実装

// src/main.cpp
#include <emscripten/bind.h>
#include <vector>
#include <string>
#include <cmath>

// 基本的な関数
int add(int a, int b) {
    return a + b;
}

std::string greet(const std::string& name) {
    return "Hello, " + name + "!";
}

// クラスのエクスポート
class Vector2D {
public:
    float x, y;

    Vector2D() : x(0), y(0) {}
    Vector2D(float x, float y) : x(x), y(y) {}

    float length() const {
        return std::sqrt(x * x + y * y);
    }

    Vector2D normalize() const {
        float len = length();
        if (len == 0) return Vector2D();
        return Vector2D(x / len, y / len);
    }

    Vector2D add(const Vector2D& other) const {
        return Vector2D(x + other.x, y + other.y);
    }

    float dot(const Vector2D& other) const {
        return x * other.x + y * other.y;
    }
};

// 高速な行列演算
std::vector<float> matrixMultiply(
    const std::vector<float>& a,
    const std::vector<float>& b,
    int n
) {
    std::vector<float> result(n * n, 0.0f);

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            float sum = 0.0f;
            for (int k = 0; k < n; ++k) {
                sum += a[i * n + k] * b[k * n + j];
            }
            result[i * n + j] = sum;
        }
    }

    return result;
}

// Embindでバインディング
EMSCRIPTEN_BINDINGS(module) {
    emscripten::function("add", &add);
    emscripten::function("greet", &greet);
    emscripten::function("matrixMultiply", &matrixMultiply);

    emscripten::class_<Vector2D>("Vector2D")
        .constructor<>()
        .constructor<float, float>()
        .property("x", &Vector2D::x)
        .property("y", &Vector2D::y)
        .function("length", &Vector2D::length)
        .function("normalize", &Vector2D::normalize)
        .function("add", &Vector2D::add)
        .function("dot", &Vector2D::dot);

    emscripten::register_vector<float>("FloatVector");
}

ビルド

emcc src/main.cpp \
  -o build/module.js \
  -s WASM=1 \
  -s MODULARIZE=1 \
  -s EXPORT_NAME="createModule" \
  -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
  --bind \
  -O3

JavaScriptとの連携

// WASM モジュールのロード方法

// 1. 基本的なロード
const wasmModule = await WebAssembly.instantiateStreaming(
  fetch('/module.wasm'),
  importObject
);

// 2. wasm-bindgen使用時
import init, { add } from './pkg/module.js';
await init();
console.log(add(1, 2));

// 3. メモリ共有
const memory = new WebAssembly.Memory({ initial: 256, maximum: 512 });

const importObject = {
  env: {
    memory,
    // JavaScriptの関数をWASMから呼び出し
    jsLog: (ptr: number, len: number) => {
      const bytes = new Uint8Array(memory.buffer, ptr, len);
      const str = new TextDecoder().decode(bytes);
      console.log(str);
    },
  },
};

// 4. 大きなデータの受け渡し
function passLargeArray(wasmInstance: WebAssembly.Instance, data: Float32Array) {
  const { memory, alloc, dealloc, process } = wasmInstance.exports as any;

  // WASMメモリにデータをコピー
  const ptr = alloc(data.byteLength);
  const wasmArray = new Float32Array(memory.buffer, ptr, data.length);
  wasmArray.set(data);

  // 処理実行
  process(ptr, data.length);

  // 結果を取得
  const result = new Float32Array(memory.buffer, ptr, data.length).slice();

  // メモリ解放
  dealloc(ptr, data.byteLength);

  return result;
}

実用的なユースケース

flowchart TB
    subgraph UseCases["WebAssemblyの活用事例"]
        subgraph Media["1. 画像・動画処理"]
            M1["Figma (デザインツール)"]
            M2["Squoosh (画像圧縮)"]
            M3["FFmpeg.wasm (動画変換)"]
        end

        subgraph Games["2. ゲーム・3D"]
            G1["Unity WebGL"]
            G2["Unreal Engine"]
            G3["Godot Engine"]
        end

        subgraph Tools["3. 開発ツール"]
            T1["VS Code (Monaco Editor)"]
            T2["Pyodide (Python in browser)"]
            T3["SQLite WASM"]
        end

        subgraph Security["4. 暗号・セキュリティ"]
            S1["1Password (パスワード管理)"]
            S2["Signal (暗号化チャット)"]
            S3["ハッシュ計算"]
        end
    end

サイズ最適化

# Rustの場合
# wasm-opt による最適化
wasm-opt -Oz input.wasm -o output.wasm

# 圧縮
gzip -9 output.wasm
# Brotli圧縮(より効率的)
brotli -9 output.wasm
# Cargo.toml での最適化設定
[profile.release]
opt-level = "z"      # サイズ優先の最適化
lto = true           # リンク時最適化
codegen-units = 1    # コード生成単位を1に
panic = "abort"      # パニック時にアボート
strip = true         # シンボル情報を削除

WASI(WebAssembly System Interface)

// WASI対応のRustコード
use std::fs;
use std::io::{self, Write};

fn main() {
    // ファイル読み書き
    let content = fs::read_to_string("/input.txt").unwrap();
    fs::write("/output.txt", content.to_uppercase()).unwrap();

    // 標準出力
    println!("Hello from WASI!");

    // 環境変数
    for (key, value) in std::env::vars() {
        println!("{}: {}", key, value);
    }
}
# WASIターゲットでビルド
rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release

# wasmtimeで実行
wasmtime target/wasm32-wasi/release/app.wasm

ブラウザサポート

ブラウザサポート状況
Chrome57+ (2017年〜)
Firefox52+ (2017年〜)
Safari11+ (2017年〜)
Edge16+ (2017年〜)
Node.js8+ (2017年〜)

参考リンク

← 一覧に戻る