Qué es WebAssembly
WebAssembly (WASM) es un formato binario de bajo nivel diseñado para ejecutarse en máquinas virtuales de pila (stack machines) a velocidades casi nativas. No es un lenguaje de programación sino un destino de compilación: puedes escribir en C, C++, Rust, Go, AssemblyScript o Python (con Pyodide) y compilarlo a WASM.
Por qué WASM es revolucionario:
- Rendimiento: 10-100× más rápido que JavaScript para código computacionalmente intenso
- Portabilidad: mismo binario en Chrome, Firefox, Safari, Node.js, Deno y servidores
- Seguridad: ejecución en sandbox con memoria aislada
- Múltiples lenguajes: el mismo ecosistema de herramientas funciona con C, Rust, Go, etc.
Casos de uso principales:
- Editores de imagen/vídeo en el navegador (Figma, Adobe Photoshop Web)
- Emuladores y juegos (DOSBox, Unity, Unreal Engine 4)
- Criptografía y hashing de alta performance
- Procesamiento de audio/vídeo sin servidor
- IA/ML en el edge (modelos ONNX en navegador)
Compilar C/C++ a WASM con Emscripten
Emscripten es el compilador estándar para C/C++ → WASM:
# Instalación de Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh # Linux/macOS
# emsdk_env.bat # Windows
Ejemplo: función matemática en C
// matematicas.c
#include <emscripten/emscripten.h>
#include <math.h>
EMSCRIPTEN_KEEPALIVE
double calcular_hipotenusa(double a, double b) {
return sqrt(a * a + b * b);
}
EMSCRIPTEN_KEEPALIVE
int es_primo(int n) {
if (n < 2) return 0;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return 0;
}
return 1;
}
EMSCRIPTEN_KEEPALIVE
long long fibonacci(int n) {
if (n <= 1) return n;
long long a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b; a = b; b = c;
}
return b;
}
# Compilar a WASM
emcc matematicas.c \
-o matematicas.js \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_calcular_hipotenusa","_es_primo","_fibonacci"]' \
-s EXPORTED_RUNTIME_METHODS='["cwrap","ccall"]' \
-s ALLOW_MEMORY_GROWTH=1 \
-O3
# Resultado: matematicas.js + matematicas.wasm
Usar el módulo en JavaScript
// index.html
const Module = await import('./matematicas.js');
await Module.ready;
// Envolver funciones C con tipos JS
const hipotenusa = Module.cwrap('calcular_hipotenusa', 'number', ['number', 'number']);
const esPrimo = Module.cwrap('es_primo', 'number', ['number']);
const fibonacci = Module.cwrap('fibonacci', 'number', ['number']);
console.log(hipotenusa(3, 4)); // → 5
console.log(esPrimo(97)); // → 1 (true)
console.log(fibonacci(40)); // → 102334155 (instantáneo en WASM)
// Benchmark: WASM vs JS puro
function fibJS(n) {
if (n <= 1) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) { [a, b] = [b, a + b]; }
return b;
}
const N = 42;
console.time('WASM');
for (let i = 0; i < 10000; i++) fibonacci(N);
console.timeEnd('WASM');
console.time('JavaScript');
for (let i = 0; i < 10000; i++) fibJS(N);
console.timeEnd('JavaScript');
// WASM suele ser 2-5× más rápido en bucles intensivos
Compilar Rust a WASM con wasm-pack
Rust es el lenguaje preferido para WASM por su control de memoria sin GC:
# Instalar Rust y wasm-pack
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install wasm-pack
# Crear proyecto Rust para WASM
cargo new --lib mi_wasm
cd mi_wasm
# Cargo.toml
[package]
name = "mi_wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn comprimir_rle(datos: &[u8]) -> Vec<u8> {
"""Compresión Run-Length Encoding simple."""
let mut resultado = Vec::new();
let mut i = 0;
while i < datos.len() {
let byte_actual = datos[i];
let mut count = 1u8;
while i + count as usize < datos.len()
&& datos[i + count as usize] == byte_actual
&& count < 255
{
count += 1;
}
resultado.push(count);
resultado.push(byte_actual);
i += count as usize;
}
resultado
}
#[wasm_bindgen]
pub fn descomprimir_rle(datos: &[u8]) -> Vec<u8> {
let mut resultado = Vec::new();
let mut i = 0;
while i + 1 < datos.len() {
let count = datos[i] as usize;
let byte = datos[i + 1];
resultado.extend(std::iter::repeat(byte).take(count));
i += 2;
}
resultado
}
#[wasm_bindgen]
pub fn suma_vectores(a: &[f64], b: &[f64]) -> Vec<f64> {
a.iter().zip(b.iter()).map(|(x, y)| x + y).collect()
}
#[wasm_bindgen]
pub fn multiplicar_matrices(
a: &[f64], b: &[f64],
filas_a: usize, cols_a: usize, cols_b: usize
) -> Vec<f64> {
let mut c = vec![0.0f64; filas_a * cols_b];
for i in 0..filas_a {
for j in 0..cols_b {
for k in 0..cols_a {
c[i * cols_b + j] += a[i * cols_a + k] * b[k * cols_b + j];
}
}
}
c
}
# Compilar
wasm-pack build --target web --release
# Resultado en pkg/:
# mi_wasm.js, mi_wasm_bg.wasm, mi_wasm.d.ts
// Usar en JavaScript/TypeScript
import init, { comprimir_rle, descomprimir_rle, multiplicar_matrices }
from './pkg/mi_wasm.js';
await init();
// Compresión RLE
const datos = new Uint8Array([1,1,1,2,2,3,3,3,3,4]);
const comprim = comprimir_rle(datos);
const decomp = descomprimir_rle(comprim);
console.log('Original:', datos);
console.log('Comprimido:', comprim); // [3,1, 2,2, 4,3, 1,4]
console.log('Restaurado:', decomp); // igual al original
// Multiplicación de matrices 4×4
const A = new Float64Array([1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16]);
const B = new Float64Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]); // identidad
const C = multiplicar_matrices(A, B, 4, 4, 4);
console.log('A×I =', Array.from(C)); // A sin cambios
Usar WASM desde Python con wasmer
# pip install wasmer wasmer-compiler-cranelift
from wasmer import engine, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler
def cargar_wasm(ruta_wasm):
"""Carga y devuelve una instancia de módulo WASM."""
store = Store(engine.JIT(Compiler))
with open(ruta_wasm, 'rb') as f:
bytecode = f.read()
modulo = Module(store, bytecode)
instancia = Instance(modulo)
return instancia
def ejemplo_wasmer():
"""Ejemplo completo: cargar WASM y llamar funciones."""
# Módulo WASM mínimo en formato WAT (WebAssembly Text)
# Normalmente usarías un .wasm compilado desde C/Rust
wat = '''
(module
(func $sumar (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add)
(export "sumar" (func $sumar))
(func $factorial (param $n i64) (result i64)
(local $resultado i64)
i64.const 1
local.set $resultado
(block $salir
(loop $bucle
local.get $n
i64.const 1
i64.le_s
br_if $salir
local.get $resultado
local.get $n
i64.mul
local.set $resultado
local.get $n
i64.const 1
i64.sub
local.set $n
br $bucle))
local.get $resultado)
(export "factorial" (func $factorial)))
'''
store = Store(engine.JIT(Compiler))
modulo = Module(store, wat)
inst = Instance(modulo)
# Llamar funciones WASM
sumar = inst.exports.sumar
factorial = inst.exports.factorial
print(f"3 + 4 = {sumar(3, 4)}") # → 7
print(f"10! = {factorial(10)}") # → 3628800
print(f"20! = {factorial(20)}") # → 2432902008176640000
# Alternativa con wasmtime (más estable, Google/Fastly/Microsoft)
def ejemplo_wasmtime():
"""pip install wasmtime"""
try:
import wasmtime
except ImportError:
print("pip install wasmtime")
return
store = wasmtime.Store()
modulo = wasmtime.Module(store.engine, '''
(module
(func (export "cuadrado") (param i32) (result i32)
local.get 0
local.get 0
i32.mul))
''')
inst = wasmtime.Instance(store, modulo, [])
cuadrado = inst.exports(store)['cuadrado']
for n in [3, 7, 12, 100]:
print(f"cuadrado({n}) = {cuadrado(store, n)}")
Pyodide: Python completo en el navegador vía WASM
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script>
</head>
<body>
<script type="text/javascript">
async function main() {
// Cargar Python en el navegador
const pyodide = await loadPyodide();
// Instalar paquetes Python en el navegador
await pyodide.loadPackage(['numpy', 'pandas']);
// Ejecutar Python puro en el navegador
const resultado = pyodide.runPython(`
import numpy as np
import pandas as pd
# Cálculo vectorial con NumPy
datos = np.random.randn(1000)
media = np.mean(datos)
std_dev = np.std(datos)
print(f"Media: {media:.4f}, Desv: {std_dev:.4f}")
# Procesar DataFrame en el navegador
df = pd.DataFrame({
'x': np.linspace(0, 10, 100),
'y': np.sin(np.linspace(0, 10, 100))
})
print(f"DataFrame: {df.shape}")
df.describe()
`);
console.log('Python en WASM ejecutado correctamente');
// Convertir datos Python ↔ JavaScript
const arr_py = pyodide.runPython('list(range(10))');
const arr_js = arr_py.toJs();
console.log('Array Python→JS:', arr_js);
}
main();
</script>
</body>
</html>
Cuándo usar WASM vs alternativas
| Caso de uso | WASM | JS/TS | Python nativo | Node.js C++ addon |
|---|---|---|---|---|
| Algoritmos intensivos en navegador | ✅ Ideal | ⚠️ Lento | ❌ No funciona | ❌ No funciona |
| Reutilizar código C/C++/Rust | ✅ Ideal | ❌ | ❌ | ⚠️ Complejo |
| Apps web rápidas | ✅ | ✅ | ❌ | ❌ |
| Servidor de alta performance | ✅ (WASI) | ⚠️ | ✅ | ✅ |
| Portabilidad sin instalación | ✅ Máxima | ✅ | ❌ | ❌ |
| Debugging y profiling | ⚠️ Difícil | ✅ Fácil | ✅ Fácil | ⚠️ |
Conclusión
WebAssembly ha pasado de ser una curiosidad a convertirse en infraestructura crítica: Figma, AutoCAD Web, Google Earth, Adobe Photoshop Web y docenas de aplicaciones de productividad lo usan en producción. Para Python, las rutas principales son wasmer/wasmtime para ejecutar módulos WASM en scripts de servidor, y Pyodide para ejecutar Python completo en el navegador. Para rendimiento máximo, Rust + wasm-pack sigue siendo la combinación recomendada.
Conversiones relacionadas
Conversiones frecuentes del catálogo: