WebAssembly: una breve introducción

Hace algún tiempo comencé a explorar la promesa de una nueva tecnología destinada a convertirse en la sucesora de Javascript en la web. WebAssembly fue presentada oficialmente en un artículo en donde ingenieros de Mozilla, Google, Microsoft y Apple hablan de traer eficiencia y seguridad a los navegadores a través de esta.

WebAssembly (o wasm) ha seguido evolucionando desde entonces y su ejecución ya no se centra en los navegadores. Ha pasado a estar presente en servidores, computación frontera, micro-computadoras, internet de las cosas, computación en la nube y microservicios. Además ha traído una gran capacidad para portar código escrito en lenguajes como C/C++ a la web. Ejemplos claros son Autocad, videojuegos y Squoosh (una aplicación web para comprimir imágenes).

Es por esto que considero que WebAssembly es una de esas tecnologías que tendrá un alto impacto en los próximos años y su uso permeará el desarrollo de software en general.

Los fundamentos

WebAssembly es un conjunto de instrucciones en formato binario para una máquina virtual que se basa en una pila de ejecuciones. No es un lenguaje de programación, es un destino de compilación de bajo nivel.

Esto implica que cualquier lenguaje de programación puede generar código binario .wasm que sea interpretado y ejecutado por un anfritrión. Esto es similar a cuando compilamos código en Rust para Linux x86_64 en nuestra computadora y podemos ejecutar el binario en otra computadora con la misma arquitectura.

Wasm está diseñado sobre cuatro pilares:

  • Seguro.
  • Rápido y eficiente.
  • Portable.
  • Compacto.

Seguro

Webassembly es esencialmente un sandbox, sólo hace uso de un espacio de memoria que ha sido otorgado por el anfitrión y su código no es capaz de interactuar con el hardware del anfitrión por si mismo. La especificación limita el acceso a los recursos mediante redirecciones y validaciones de límites en el acceso a memoria y funciones.

No hay interrupciones, llamadas al sistema, apuntadores a la pila o direcciones de memoria.

Rápido y Eficiente

Dado que el código se encuentra ya en un bajo nivel después de la compilación WebAssembly no necesita una transformación extra antes de ser ejecutado. Sólo necesita ser decodificado y validado para asegurarse de que no haya errores.

El binario contiene información explícita sobre su ejecución, encabezados de funciones y tipos de datos.

Claramente estas características lo ponen por delante de Javascript. En otros entornos dependerá de la implementación en el anfitrión.

Portable

Un conjunto de instrucciones como destino de compilación puede hacer que el navegador web sea capaz de ejecutar el mismo Autocad (levemente adaptado) que ha sido desarrollado en C desde hace décadas. El navegador (al menos los cuatro más populares) es entonces un anfitrión de WebAssembly.

Compacto

Al ser un conjunto de instrucciones en formato binario los archivos Wasm tienden a ser extremadamente compactos, lo que indirectamente también favorece su portabilidad.

El módulo WASM

Los programas en WebAssembly son organizados en módulos, los cuales son unidades que serán lanzadas, cargadas y compiladas para su ejecución. No contiene ningún estado en sí mismo. Cada módulo se compone de diferentes elementos, entre ellos se encuentran:

  • Funciones
  • Tablas. Son listas de referencias, por ejemplo a funciones.
  • Memoria. Son conjuntos de bloques lineales de bytes de un tamaño variable. Esta puede ser leída o escrita por las instrucciones en el módulo.
  • Globales. Valores variables o constantes definidos para todo el módulo.
  • Tipos de datos.
  • imports y exports que permiten la comunicación con su anfitrión. Entre estos se encuentran valores en entrada, recursos de memoria, funciones, entre otras, así como otros módulos.
  • Función de inicio. Cuando se define es ejecutada después de que un módulo es instanciado.

Se observa que un módulo en WASM es sólo una especie de conjuntos de bytes o Blob. Es su interacción con el anfitrión lo que le permitirá ser funcional.

Su uso y adopción

WebAssembly se encuentra ahora en su versión 1.0 y poco a poco más y más anfitriones están apareciendo y dando soporte a sus funcionalidades. Aparte de los navegadores se tiene a Node.js y Deno en servidores, así como wasmtime, wasm3, wasmer y wasmcloud.

Las especificaciones de WebAssembly no dan soporte para la interacción con el hardware del anfitrión por lo cuál también se están desarrollando APIs para proveer acceso a sistemas de archivos, sockets y otros recursos a los módulos. El más notorio es WASI, el cual es un proyecto de la Bytecode Alliance, una organización sin fines de lucro que se dedica a crear una estandarización el ecosistema de WebAssembly.

También están apareciendo entornos completos para desarrollar aplicaciones usando sistemas basados en eventos, como spin para backend, o Blazor C# para el frontend.

Un ejemplo de implementación

Como se ha mencionado anteriormente WebAssembly es un destino de compilación de otro lenguaje de programación que requiere un entorno de ejecución.

Por el momento WebAssembly sólo cuenta con un número reducido de tipos de datos: i32, i64, f32 y f64, por lo cual iniciaremos con un ejemplo de suma de dos enteros de 32 bit.

Usaremos Rust para generar el archivo .wasm, después wasm3 para ejecutarlo en línea de comandos y Firefox para ejecutarlo en el navegador.

// add.rs

// le indicamos al compilador de Rust que necesitamos este
// nombre de función intacto
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}
# instalando el target de compilación
; rustup target add wasm32-unknown-unknown

# compilando
; rustc -A dead_code --target wasm32-unknown-unknown -O --crate-type=cdylib add.rs -o add.wasm
# Ejecutando usando wasm3

# instalando wasm3 en os x
; brew install wasm3
# ejecutando la función "add" con argumentos 2 y 3
; wasm3 --func add add.wasm 2 3
Result: 5

En el navegador hacemos uso del elemento global WebAssembly para poder ejecutar la instancia. Las primeras líneas del código son sólo para poder hacer uso de la función para sumar, lo cual se realiza con module.instance.exports.add(2, 3).

<!DOCTYPE HTML>
<html>
<head><title>Hola mundo</title></head>
<body>
    <script>
 async function suma() {
     const file = await fetch("add.wasm");
     const bytes = await module.arrayBuffer();
     const module = await WebAssembly.instantiate(bytes);

     // ejecutando la función "add" con argumentos 2 y 3
     const resultado = module.instance.exports.add(2, 3);
     console.log("Resultado:", resultado);
 }

 suma();
</script>
</body>
</html>

El hecho de que por ahora WebAssembly sólo soporte dígitos no implica que no podamos hacer uso de tipos de datos un poco más complejos, el reto está en que es muy tedioso. Incluso para simples cadenas de caracteres tenemos que reducir su representación a i32 o i64.

Afortunadamente la complejidad está siendo absorbida por proyectos como wasm-bindgen para Rust que ofrece herramientas para interactuar directamente con el navegador o wapc donde se pueden definir tipos de datos similares a estructuras e intercambiar datos entre huésped WASM y anfitrión Rust.

Conclusión

WebAssembly es un destino de compilación de bajo nivel que permite que el código generado en un lenguaje de programación pueda ser portado a diferentes entornos que permitan su ejecución. Además de su portabilidad tiene en cuenta la seguridad, rapidez y eficiencia.

Por ejemplo podemos generar un código en Rust para ser ejecutado en línea de comandos y después usar ese mismo código para usarlo en el navegador.

Con estos fundamentos ha ido ganando adopción en recientes años tanto en aplicaciones frontend como backend para la web. Además para crear aplicaciones web a partir de software que históricamente había sido destinado al escritorio por sus exigencias como Autocad.

Uno de sus puntos fuertes es otorgado por su portabilidad y rapidez de carga y ejecución ya que permite a muchos asociarlo directamente con sistemas basados en _plugins_donde el módulo se cargue sólo cuando sea necesario. De igual forma se puede imaginar una arquitectura de aplicación donde un módulo WASM es reemplazado por otro sólo modificando el archivo binario, es decir, sin redistribuir toda la aplicación nuevamente.

Diversos entornos y especificaciones han ido apareciendo para dar soporte al desarrollo de aplicaciones y su ejecución de forma independiente.

Es por esto que considero que WebAssembly es una tecnología que permeará el futuro del desarrollo de software debido a su rápido acceso, instanciación y su diseño seguro y escalable.

Referencias

https://webassembly.org/

https://developer.mozilla.org/es/docs/WebAssembly

https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/

https://hacks.mozilla.org/2017/07/memory-in-webassembly-and-why-its-safer-than-you-think/

WebAssembly for Cloud: A basic guide for WASM-based cloud apps

WebAssembly: The definitive guide

https://wasi.dev/

Comments:

WASI: una interfaz de sistemas para WebAssembly – Blog de Raymundo Vásquez Ruiz -

[…] la publicación anterior mencioné que WebAssembly sólo soporta datos numéricos en estos momentos (i32, i64, f32 y f64) y […]


#### [📦 WebAssembly y los microservicios – Blog de Raymundo Vásquez Ruiz](https://blog.vasquezruiz.me/%f0%9f%93%a6-webassembly-y-los-microservicios/ "") -

[…] WebAssembly viene con la promesa de ofrecer velocidad, seguridad y ligereza, algo que quizás podría situarlo entre una máquina virtual y V8 isolate. Además, al ser un destino de compilación, es independiente de cualquier lenguaje de programación. […]