Introducción a WebAssembly - Formato Binario de Alta Velocidad para el Navegador

Avanzado | 2025.12.02

¿Qué es WebAssembly?

WebAssembly (WASM) es un formato de instrucciones binarias ejecutable en el navegador. Puede compilarse desde lenguajes como C/C++, Rust y Go, permitiendo ejecutar código nativo con un rendimiento cercano al de JavaScript.

flowchart LR
    subgraph Source["Código Fuente"]
        Rust["Rust"]
        Cpp["C/C++"]
        Go["Go"]
    end

    subgraph Compile["Compilación"]
        LLVM["LLVM"]
        WASM[".wasm<br/>Binario"]
    end

    subgraph Runtime["Entorno de Ejecución"]
        Browser["Browser<br/>Runtime"]
        JS["Llamada desde<br/>JavaScript"]
    end

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

Características:

  • Velocidad de ejecución cercana a la nativa
  • Independiente del lenguaje (soporte multilenguaje)
  • Ejecución segura en sandbox
  • Interoperabilidad con JavaScript
  • Soporte para compilación en streaming

Comparación de Rendimiento

ProcesamientoJavaScriptWASMNativo
Multiplicación de matrices (1000x1000)2,500ms850ms700ms
Procesamiento de imagen (1920x1080)180ms72ms-
Cifrado (AES-256)320ms125ms-

※ La diferencia puede reducirse debido a las optimizaciones JIT del motor JS

Rust + WebAssembly

Configuración

# Instalación de wasm-pack
cargo install wasm-pack

# Creación del proyecto
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"     # Optimización de tamaño
lto = true          # Optimización en tiempo de enlace

Implementación en Rust

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

// Función llamable desde JavaScript
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// Función que maneja strings
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

// Exportar struct
#[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;
    }
}

// Procesamiento de arrays
#[wasm_bindgen]
pub fn sum_array(arr: &[i32]) -> i32 {
    arr.iter().sum()
}

// Usar console de JS
#[wasm_bindgen]
pub fn log_to_console(message: &str) {
    web_sys::console::log_1(&message.into());
}

// Ejemplo de procesamiento de imagen de alta velocidad
#[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;
        // Coeficientes 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) se mantiene igual
    }
}

// Secuencia Fibonacci (para benchmark)
#[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
}

Compilación y Uso

# Compilar
wasm-pack build --target web --release
// Uso desde JavaScript/TypeScript
import init, {
  add,
  greet,
  Calculator,
  sum_array,
  apply_grayscale,
  fibonacci
} from './pkg/wasm_example.js';

async function main() {
  // Inicialización del módulo WASM
  await init();

  // Llamadas a funciones básicas
  console.log(add(2, 3)); // 5
  console.log(greet('World')); // "Hello, World!"

  // Uso de clase
  const calc = new Calculator();
  calc.add(10);
  calc.multiply(2);
  console.log(calc.get_value()); // 20

  // Procesamiento de arrays
  const arr = new Int32Array([1, 2, 3, 4, 5]);
  console.log(sum_array(arr)); // 15

  // Procesamiento de imagen
  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);

  // Medición de rendimiento
  console.time('WASM Fibonacci');
  fibonacci(40);
  console.timeEnd('WASM Fibonacci');
}

main();

C/C++ + Emscripten

Configuración

# Instalación de Emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

Implementación en C++

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

// Funciones básicas
int add(int a, int b) {
    return a + b;
}

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

// Exportar clase
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;
    }
};

// Operación de matrices de alta velocidad
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;
}

// Bindings con 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");
}

Compilación

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

Integración con JavaScript

// Métodos de carga del módulo WASM

// 1. Carga básica
const wasmModule = await WebAssembly.instantiateStreaming(
  fetch('/module.wasm'),
  importObject
);

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

// 3. Memoria compartida
const memory = new WebAssembly.Memory({ initial: 256, maximum: 512 });

const importObject = {
  env: {
    memory,
    // Llamar función JavaScript desde WASM
    jsLog: (ptr: number, len: number) => {
      const bytes = new Uint8Array(memory.buffer, ptr, len);
      const str = new TextDecoder().decode(bytes);
      console.log(str);
    },
  },
};

// 4. Pasar grandes cantidades de datos
function passLargeArray(wasmInstance: WebAssembly.Instance, data: Float32Array) {
  const { memory, alloc, dealloc, process } = wasmInstance.exports as any;

  // Copiar datos a memoria WASM
  const ptr = alloc(data.byteLength);
  const wasmArray = new Float32Array(memory.buffer, ptr, data.length);
  wasmArray.set(data);

  // Ejecutar procesamiento
  process(ptr, data.length);

  // Obtener resultado
  const result = new Float32Array(memory.buffer, ptr, data.length).slice();

  // Liberar memoria
  dealloc(ptr, data.byteLength);

  return result;
}

Casos de Uso Prácticos

flowchart TB
    subgraph UseCases["Casos de Uso de WebAssembly"]
        subgraph Media["1. Procesamiento de Imagen/Video"]
            M1["Figma (Herramienta de diseño)"]
            M2["Squoosh (Compresión de imágenes)"]
            M3["FFmpeg.wasm (Conversión de video)"]
        end

        subgraph Games["2. Juegos y 3D"]
            G1["Unity WebGL"]
            G2["Unreal Engine"]
            G3["Godot Engine"]
        end

        subgraph Tools["3. Herramientas de Desarrollo"]
            T1["VS Code (Monaco Editor)"]
            T2["Pyodide (Python en navegador)"]
            T3["SQLite WASM"]
        end

        subgraph Security["4. Criptografía y Seguridad"]
            S1["1Password (Gestor de contraseñas)"]
            S2["Signal (Chat cifrado)"]
            S3["Cálculo de hash"]
        end
    end

Optimización de Tamaño

# Para Rust
# Optimización con wasm-opt
wasm-opt -Oz input.wasm -o output.wasm

# Compresión
gzip -9 output.wasm
# Compresión Brotli (más eficiente)
brotli -9 output.wasm
# Configuración de optimización en Cargo.toml
[profile.release]
opt-level = "z"      # Optimización priorizando tamaño
lto = true           # Optimización en tiempo de enlace
codegen-units = 1    # Unidad de generación de código a 1
panic = "abort"      # Abortar en caso de panic
strip = true         # Eliminar información de símbolos

WASI (WebAssembly System Interface)

// Código Rust compatible con WASI
use std::fs;
use std::io::{self, Write};

fn main() {
    // Lectura/escritura de archivos
    let content = fs::read_to_string("/input.txt").unwrap();
    fs::write("/output.txt", content.to_uppercase()).unwrap();

    // Salida estándar
    println!("Hello from WASI!");

    // Variables de entorno
    for (key, value) in std::env::vars() {
        println!("{}: {}", key, value);
    }
}
# Compilar para objetivo WASI
rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release

# Ejecutar con wasmtime
wasmtime target/wasm32-wasi/release/app.wasm

Soporte de Navegadores

NavegadorEstado de Soporte
Chrome57+ (desde 2017)
Firefox52+ (desde 2017)
Safari11+ (desde 2017)
Edge16+ (desde 2017)
Node.js8+ (desde 2017)

Enlaces de Referencia

← Volver a la lista