WebAssembly: ¿la versión rápida de JavaScript?
Las aplicaciones que se ejecutan en navegador en lugar de ir instaladas en un disco duro son cada vez más completas. Además de los típicos programas de oficina como Microsoft 365 o Google Docs, que siempre presentan funciones nuevas, los juegos de navegador son cada vez más complejos y requieren más recursos. Este tipo de aplicaciones web se ofrece a menudo en JavaScript, pero, entretanto, cada vez más desarrolladores apuestan por WebAssembly: un nuevo planteamiento con resultados sorprendentes.
¿Qué es WebAssembly?
WebAssembly (abreviado, Wasm) es una nueva forma con la que los desarrolladores web pueden generar aplicaciones en internet. Hasta ahora, para ello había que recurrir a JavaScript. Pero JavaScript es relativamente lento y, en determinados escenarios, se ve limitado. Por eso, el World Wide Web Consortium (W3C) ha impulsado este nuevo método. Sin embargo, para que Wasm pueda funcionar, el navegador debe ser compatible con este lenguaje. Por este motivo, Mozilla (Firefox), Microsoft (Edge), Apple (Safari) y Google (Chrome) han participado en el desarrollo. En todas las versiones de navegador actuales de estos proveedores se pueden ejecutar aplicaciones en WebAssembly.
Para experimentar la capacidad de rendimiento de WebAssembly merece la pena jugar una partida a Funky Karts. El juego —en realidad, una aplicación móvil— se convirtió a WebAssembly para poder ejecutarse también en navegadores. El desarrollador ha escrito sobre el proyecto en un interesante blog, en el que ha descrito cada uno de los pasos de la conversión.
Fundamentalmente, WebAssembly se representa en forma de bytecode, que puede considerarse como un nivel intermedio entre el código máquina —que solo un ordenador puede entender— y un típico lenguaje de programación —legible para humanos, a condición de que se compile primero. Al requerir apenas esfuerzo para convertir el código, esto hace que WebAssembly sea más rápido. Sin embargo, escribir en bytecode es bastante inusual. La ventaja de Wasm es que no hace falta trabajar con este lenguaje de programación, ya que, en la práctica, la aplicación web puede escribirse en C o C++.
El texto fuente se convierte con la aplicación Emscripten. Antes de que existiera WebAssembly, esta herramienta ya estaba en uso para convertir código C/C++ a JavaScript (o ams.js). Actualmente, con ella también es posible transcribir código en Wasm. Esto significa que el código está precompilado y por ello no tiene que compilarse o interpretarse en el momento de la ejecución. Cuando el usuario abre finalmente la aplicación en el navegador, se inicia una pequeña máquina virtual. Y en ella se ejecuta la aplicación.
El texto fuente se convierte con la aplicación Emscripten. Antes de que existiera WebAssembly, esta herramienta ya estaba en uso para convertir código C/C++ a JavaScript (o ams.js). Actualmente, con ella también es posible transcribir código en Wasm. Esto significa que el código está precompilado y por ello no tiene que compilarse o interpretarse en el momento de la ejecución. Cuando el usuario abre finalmente la aplicación en el navegador, se inicia una pequeña máquina virtual. Y en ella se ejecuta la aplicación.
Ventajas de WebAssembly
Actualmente, WebAssembly presenta un único inconveniente: se difunde con mucha lentitud. Los desarrolladores web están acostumbrados al trabajo con JavaScript y no hay planes para desbancarlo. La dirección del proyecto da una gran importancia a que en la comunicación Wasm se presente como una opción complementaria de JavaScript. Pero, gracias a la compatibilidad con los grandes proveedores de navegadores y el W3C, la difusión está comenzando a despegar. Esto también se debe a que los visitantes de las páginas web no tienen que realizar ningún paso por su cuenta: las aplicaciones web en WebAssembly se cargan de manera tan sencilla como el código en JavaScript, solo que más rápido.
Muchos desarrolladores que ya escriben en C, C++ o en Rust, ahora también pueden programar directamente para la web. Los lenguajes de programación a veces también ofrecen otras posibilidades para el diseño de las aplicaciones: quien no encuentra en JavaScript las bibliotecas o los marcos adecuados para su programa, ahora cuenta con una selección más amplia para conseguir su objetivo. Los desarrolladores tienen por lo tanto varios motivos para estudiar WebAssembly con más detenimiento:
Muchos desarrolladores que ya escriben en C, C++ o en Rust, ahora también pueden programar directamente para la web. Los lenguajes de programación a veces también ofrecen otras posibilidades para el diseño de las aplicaciones: quien no encuentra en JavaScript las bibliotecas o los marcos adecuados para su programa, ahora cuenta con una selección más amplia para conseguir su objetivo. Los desarrolladores tienen por lo tanto varios motivos para estudiar WebAssembly con más detenimiento:
- Estándar web abierto de W3C
- Alto rendimiento y tamaños de archivo reducidos
- Por lo tanto, también perfecto para la navegación móvil
- En teoría, hace posible la realidad virtual en el navegador
- No se requiere ningún lenguaje de programación nuevo
- También se pueden utilizar C, C++ o Rust para la programación de aplicaciones web
- Compatible con todos los grandes proveedores de navegadores
- Sin limitaciones para el usuario
WebAssembly en la práctica
Realmente, no se espera que nadie programe en WebAssembly. De hecho, la principal ventaja de esta tecnología es que el programador puede usar uno de los lenguajes conocidos, como C. El código se transfiere luego al formato Wasm. No obstante, tiene sentido ahondar en el código ya compilado y echar un vistazo al funcionamiento de WebAssembly.
Hay dos variantes diferentes del código fuente: WebAssembly Text Format (WAT) y WebAssembly Binary Format (Wasm). Este último es el código real que la máquina ejecuta. Sin embargo, como está compuesto exclusivamente de código binario, no resulta útil para un análisis humano, motivo por el que existe el formato intermedio WAT. Como el código utiliza expresiones legibles, los propios programadores pueden analizarlo, si bien carece de la comodidad de trabajo que se conoce de los lenguajes de programación establecidos.
En este ejemplo se usa un código fuente muy simple en C:
Hay dos variantes diferentes del código fuente: WebAssembly Text Format (WAT) y WebAssembly Binary Format (Wasm). Este último es el código real que la máquina ejecuta. Sin embargo, como está compuesto exclusivamente de código binario, no resulta útil para un análisis humano, motivo por el que existe el formato intermedio WAT. Como el código utiliza expresiones legibles, los propios programadores pueden analizarlo, si bien carece de la comodidad de trabajo que se conoce de los lenguajes de programación establecidos.
En este ejemplo se usa un código fuente muy simple en C:
#define WASM_EXPORT __attribute__((visibility("default")))
WASM_EXPORT
int main() {
return 1;
}
El mismo código en formato WAT es mucho más largo:
(module
(type $t0 (func))
(type $t1 (func (result i32)))
(func $__wasm_call_ctors (type $t0))
(func $main (export "main") (type $t1) (result i32)
i32.const 1)
(table $T0 1 1 anyfunc)
(memory $memory (export "memory") 2)
(global $g0 (mut i32) (i32.const 66560))
(global $__heap_base (export "__heap_base") i32 (i32.const 66560))
(global $__data_end (export "__data_end") i32 (i32.const 1024)))
La legibilidad está muy limitada, si bien se pueden distinguir algunos elementos. En WebAssembly todo se divide en diferentes módulos. A su vez los módulos se dividen en funciones, que se especifican con parámetros. En total se distinguen cinco elementos:
- module: la unidad superior de WebAssembly
- function: agrupación dentro de un módulo
- memory: array con Bytes
- global: valor que se puede usar con diferentes módulos
- table: almacenamiento de referencias
0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0100 0000 ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01 ; section code
0000009: 00 ; section size (guess)
000000a: 02 ; num types
; type 0
000000b: 60 ; func
000000c: 00 ; num params
000000d: 00 ; num results
; type 1
000000e: 60 ; func
000000f: 00 ; num params
0000010: 01 ; num results
0000011: 7f ; i32
0000009: 08 ; FIXUP section size
; section "Function" (3)
0000012: 03 ; section code
0000013: 00 ; section size (guess)
0000014: 02 ; num functions
0000015: 00 ; function 0 signature index
0000016: 01 ; function 1 signature index
0000013: 03 ; FIXUP section size
; section "Table" (4)
0000017: 04 ; section code
0000018: 00 ; section size (guess)
0000019: 01 ; num tables
; table 0
000001a: 70 ; funcref
000001b: 01 ; limits: flags
000001c: 01 ; limits: initial
000001d: 01 ; limits: max
0000018: 05 ; FIXUP section size
; section "Memory" (5)
000001e: 05 ; section code
000001f: 00 ; section size (guess)
0000020: 01 ; num memories
; memory 0
0000021: 00 ; limits: flags
0000022: 02 ; limits: initial
000001f: 03 ; FIXUP section size
; section "Global" (6)
0000023: 06 ; section code
0000024: 00 ; section size (guess)
0000025: 03 ; num globals
0000026: 7f ; i32
0000027: 01 ; global mutability
0000028: 41 ; i32.const
0000029: 8088 04 ; i32 literal
000002c: 0b ; end
000002d: 7f ; i32
000002e: 00 ; global mutability
000002f: 41 ; i32.const
0000030: 8088 04 ; i32 literal
0000033: 0b ; end
0000034: 7f ; i32
0000035: 00 ; global mutability
0000036: 41 ; i32.const
0000037: 8008 ; i32 literal
0000039: 0b ; end
0000024: 15 ; FIXUP section size
; section "Export" (7)
000003a: 07 ; section code
000003b: 00 ; section size (guess)
000003c: 04 ; num exports
000003d: 04 ; string length
000003e: 6d61 696e main ; export name
0000042: 00 ; export kind
0000043: 01 ; export func index
0000044: 06 ; string length
0000045: 6d65 6d6f 7279 memory ; export name
000004b: 02 ; export kind
000004c: 00 ; export memory index
000004d: 0b ; string length
000004e: 5f5f 6865 6170 5f62 6173 65 __heap_base ; export name
0000059: 03 ; export kind
000005a: 01 ; export global index
000005b: 0a ; string length
000005c: 5f5f 6461 7461 5f65 6e64 __data_end ; export name
0000066: 03 ; export kind
0000067: 02 ; export global index
000003b: 2c ; FIXUP section size
; section "Code" (10)
0000068: 0a ; section code
0000069: 00 ; section size (guess)
000006a: 02 ; num functions
; function body 0
000006b: 00 ; func body size (guess)
000006c: 00 ; local decl count
000006d: 0b ; end
000006b: 02 ; FIXUP func body size
; function body 1
000006e: 00 ; func body size (guess)
000006f: 00 ; local decl count
0000070: 41 ; i32.const
0000071: 01 ; i32 literal
0000072: 0b ; end
000006e: 04 ; FIXUP func body size
0000069: 09 ; FIXUP section size
; section "name"
0000073: 00 ; section code
0000074: 00 ; section size (guess)
0000075: 04 ; string length
0000076: 6e61 6d65 name ; custom section name
000007a: 01 ; function name type
000007b: 00 ; subsection size (guess)
000007c: 02 ; num functions
000007d: 00 ; function index
000007e: 11 ; string length
000007f: 5f5f 7761 736d 5f63 616c 6c5f 6374 6f72 __wasm_call_ctor
000008f: 73 s ; func name 0
0000090: 01 ; function index
0000091: 04 ; string length
0000092: 6d61 696e main ; func name 1
000007b: 1a ; FIXUP subsection size
0000096: 02 ; local name type
0000097: 00 ; subsection size (guess)
0000098: 02 ; num functions
0000099: 00 ; function index
000009a: 00 ; num locals
000009b: 01 ; function index
000009c: 00 ; num locals
0000097: 05 ; FIXUP subsection size
0000074: 28 ; FIXUP section size
Si quieres experimentar con WebAssembly por primera vez, puedes hacerlo en el WebAssembly Studio. Aquí hay disponible un entorno de desarrollo online para Wasm.