Capítulo 1. Introducción Introducción
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Las afirmaciones extraordinarias requieren pruebas extraordinarias.
Dr. Carl Sagan
Este capítulo presentará WebAssembly y proporcionará el contexto de su amplio alcance. En cierto sentido, es la culminación de la evolución de la web en las últimas décadas. Hay bastante historia que cubrir para que todo tenga sentido. Si no te gustan la historia y la exposición, puedes saltarte este capítulo e ir directamente alCapítulo 2, pero espero que no lo hagas. Creo que es importante comprender por qué esta tecnología es tan importante y de dónde procede.
Qué ofrece WebAssembly
Una de las mayores habilidades que puede desarrollar un ingeniero de es la capacidad de evaluar lo que aporta una nueva tecnología. Como nos recuerda el Dr. Fred Brooks, de la Universidad de Carolina del Norte, no hay "balas de plata"; todo tiene compensaciones. A menudo, la complejidad no se elimina con una nueva tecnología, sino que simplemente se traslada a otro lugar. Así que cuando algo cambia realmente lo que es posible o cómo hacemos nuestro trabajo en una dirección positiva, merece nuestra atención y debemos averiguar por qué.
Cuando intento comprender las implicaciones de algo nuevo, suelo empezar tratando de determinar la motivación de quienes están detrás de ello. Otra buena fuente de información es dónde se ha quedado corta una alternativa. ¿Qué ha habido antes y cómo influye en esta nueva tecnología que intentamos descifrar? Al igual que en el arte y la música, constantemente tomamos prestadas buenas ideas de múltiples fuentes, así que para comprender realmente por qué WebAssembly merece nuestra atención y lo que proporciona, primero debemos fijarnos en lo que le ha precedido y cómo marca la diferencia.
En el documento que presentó formalmente al mundo WebAssembly, los autores indican que la motivación consistía en elevarse para satisfacer las necesidades del software moderno distribuido por la web de formas que JavaScript por sí solo no podía.1 En última instancia, se trataba de una búsqueda para proporcionar software que sea:
-
Seguro
-
Rápido
-
Portátil
-
Compacto
En esta visión, WebAssembly se centra en la intersección del desarrollo de software, la web, su historia y cómo ofrece funcionalidad en un espacio geográficamente distribuido. Con el tiempo, la idea se ha ampliado drásticamente más allá de este punto de partida para imaginar una plataforma computacional ubicua, segura y de alto rendimiento que afecte a casi todos los aspectos de nuestra vida profesional como tecnólogos. WebAssembly tendrá un impacto en los mundos del desarrollo web del lado del cliente, las aplicaciones de escritorio y empresariales, la funcionalidad del lado del servidor, la modernización del legado, los juegos, la educación, la computación en la nube, las plataformas móviles, los ecosistemas del Internet de las Cosas (IoT), las iniciativas sin servidor y de microservicios, y mucho más. Espero convencerte de ello a lo largo de este libro.
Nuestras plataformas de implementación son más variadas que nunca, por lo que necesitamos portabilidad tanto a nivel de código como de aplicación. Un conjunto de instrucciones común o un objetivo de código de bytes puede hacer que los algoritmos funcionen en varios entornos, porque sólo tenemos que asignar los pasos lógicos a cómo pueden expresarse en una arquitectura de máquina concreta. Los programadores utilizan interfaces de programación de aplicaciones (API) como OpenGL,2 POSIX3 o Win324 porque proporcionan la funcionalidad para abrir archivos, generar subprocesos o dibujar cosas en la pantalla. Son cómodas y reducen la cantidad de código que un desarrollador necesita escribir en , pero crean una dependencia de la presencia de bibliotecas para proporcionar la funcionalidad. Si la API no está disponible en un entorno de destino, la aplicación no se ejecutará. Esta fue una de las formas en que Microsoft pudo utilizar su fuerza en el mercado de los sistemas operativos para dominar también en el espacio de los paquetes de aplicaciones. Por otro lado, los estándares abiertos pueden facilitar la portabilidad del software a distintos entornos.
Otro problema con la parte del tiempo de ejecución del software que estamos construyendo es que los distintos hosts tienen capacidades de hardware diferentes (número de núcleos, presencia de GPU) o restricciones de seguridad (si se pueden abrir archivos o enviar o recibir tráfico de red). A menudo, el software se adapta a lo que hay disponible utilizando enfoques de prueba de características para determinar qué recursos puede aprovechar una aplicación, pero esto suele complicar la funcionalidad empresarial. Sencillamente, no podemos permitirnos el tiempo y el dinero necesarios para reescribir constantemente el software para múltiples plataformas. En su lugar, necesitamos mejores estrategias de reutilización. También necesitamos esta flexibilidad sin la complejidad de modificar el código para que sea compatible con la plataforma en la que se ejecutará. Hacer que el código sea diferente para distintos entornos anfitriones aumenta su complejidad y complica las estrategias de prueba e implementación.
Después de varias décadas, la propuesta de valor del software de código abierto está clara. Gravitamos hacia componentes valiosos y reutilizables escritos por otros desarrolladores como medio de satisfacer nuestras propias necesidades.5 Sin embargo, no todo el código disponible es fiable, y nos abrimos a los ataques de la cadena de suministro de software cuando ejecutamos bits no fiables que hemos descargado de Internet. Nos volvemos vulnerables a los riesgos, impactos empresariales y costes personales de los sistemas de software inseguros a través de ataques de phishing, violaciones de datos, malware y ransomware.
Hasta ahora, JavaScript ha sido la única forma de resolver algunos de estos problemas. Cuando se ejecuta en un entorno aislado, nos proporciona cierta seguridad. Es ubicuo y portátil. Los motores se han vuelto más rápidos. El ecosistema ha explotado en una avalancha de productividad. Sin embargo, una vez que sales de los confines de las protecciones basadas en el navegador, seguimos teniendo problemas de seguridad. Hay una diferencia entre el código JavaScript que se ejecuta como cliente y el que se ejecuta en el servidor. El diseño de un solo hilo complica las tareas de larga duración o altamente concurrentes. Debido a sus orígenes como lenguaje dinámico, hay varias clases de optimizaciones disponibles para otros lenguajes de programación que no están, y seguirán estando, disponibles como opciones ni siquiera para los tiempos de ejecución de JavaScript más rápidos y modernos.
Además, es demasiado fácil añadir dependencias de JavaScript y no darse cuenta de cuánto equipaje y riesgo se está arrastrando de forma transitoria. Los desarrolladores que no se toman el tiempo necesario para considerar detenidamente estas decisiones acaban entorpeciendo todos los aspectos de las pruebas, la implementación y el uso del software en sentido ascendente. Cada uno de estos scripts tiene que cargarse y validarse una vez que se transfiere por la red. Esto ralentiza el tiempo de uso y hace que todo parezca más lento. Cuando se modifica o elimina un paquete dependiente, tiene el potencial de perturbar enormes cantidades de software desplegado.6
Existe la percepción entre los observadores ocasionales de que WebAssembly es un asalto a JavaScript, pero sencillamente no es así. Claro que podrás evitar JavaScript si quieres, pero se trata sobre todo de darte opciones para resolver problemas en el lenguaje que elijas sin necesidad de un tiempo de ejecución separado ni de tener que preocuparte en qué lenguaje está escrito otro software. Ya es posible utilizar un módulo WebAssembly sin saber cómo se construyó. Esto va a aumentar la vida útil del valor empresarial que obtenemos de nuestro software y, al mismo tiempo, nos va a permitir innovar en la adopción de nuevos lenguajes sin afectar a todo lo demás.
A lo largo de las últimas décadas hemos experimentado varias herramientas, lenguajes, plataformas y marcos de trabajo que han intentado resolver estos problemas, pero WebAssembly representa una de las primeras veces que lo estamos haciendo bien. Sus diseñadores no intentan sobreespecificar nada. Están aprendiendo del pasado, adoptando la web y aplicando el pensamiento del espacio de problemas a lo que, en última instancia, es un problema difícil y multidimensional. Echemos un vistazo a las influencias formativas de esta emocionante nueva tecnología antes de sumergirnos más en ella.
Historia de la Web
Existe un chiste recurrente en la comunidad WebAssembly que dice que WebAssembly no es "ni web ni ensamblador".7 Aunque esto es cierto en algunos niveles, el nombre es suficientemente sugerente de lo que proporciona. Es una plataforma objetivo con una serie de instrucciones vagamente asamblearias.8 El hecho de que los módulos WebAssembly vayan a entregarse con frecuencia a través de la web como otro tipo de recurso direccionable por URL justifica la inclusión de la palabra Web en el nombre.
Una de las principales distinciones entre el "desarrollo de software convencional" y el "desarrollo web" es que, efectivamente, no se requiere instalación con el último una vez que tienes un navegador disponible. Esto cambia las reglas del juego en lo que respecta a los costes de entrega y a la capacidad de modificar rápidamente las nuevas versiones en función de los errores y las peticiones de funciones. Cuando se enmarca en otros ecosistemas tecnológicos multiplataforma como Internet y la web, también facilita mucho el soporte de múltiples entornos de hardware y software.
Sir Tim Berners-Lee, el inventor de la World Wide Web, trabajaba en la Organización Europea para la Investigación Nuclear (CERN), donde presentó una propuesta parainterconectardocumentos, imágenes y datos en pos de los objetivos de investigación más amplios del CERN.9 Aunque el impacto es evidente en retrospectiva, tuvo que anunciar sus ideas internamente varias veces antes de que le pidieran que actuara en consecuencia.10 Como organización, el CERN estaba representado por decenas de centros de investigación de todo el mundo, que enviaban científicos con sus propios ordenadores, aplicaciones y datos. No había capacidad real para obligar a todos a utilizar los mismos sistemas operativos o plataformas, así que reconoció la necesidad de una solución técnica para resolver el problema.
Antes de la web, existían servicios como Archie,11 Gopher12 y WAIS,13 pero imaginó una plataforma más fácil de usar que, en última instancia, se engendró como una innovación a nivel de aplicación en la parte superior de la arquitectura en capas de Internet. También tomó ideas del Lenguaje de Marcado Generalizado Estándar (SGML) y las convirtió en la base de el Lenguaje de Marcado de Hipertexto (HTML).14
El resultado de estos diseños se convirtió rápidamente en el principal mecanismo de entrega de información, documentación y, finalmente, funcionalidad de las aplicaciones al mundo. Lo hizo sin necesidad de que las distintas partes interesadas se pusieran de acuerdo sobre tecnologías o plataformas específicas, definiendo el intercambio de normas, e incluyó tanto la forma en que se hacían las peticiones como lo que se devolvía en respuesta. Cualquier pieza de software que entendiera las normas podría comunicarse con cualquier otra pieza de software que también las entendiera. Este nos da libertad de elección y la capacidad de evolucionar a un lado independientemente del otro.
Orígenes de JavaScript
El modelo de interacción de la web se llama Protocolo de Transferencia de Hipertexto (HTTP). Se basa en un conjunto restringido de verbos para intercambiar mensajes basados en texto. Aunque era un modelo sencillo y eficaz, fácil de implantar, pronto se vio que era inadecuado para la tarea de las aplicaciones interactivas modernas, debido a las latencias inherentes al retorno constante al servidor. La idea de poder enviar código al navegador siempre ha sido atractiva. Si se ejecutara en el lado del usuario de la interacción, no todas las actividades requerirían un retorno al servidor. Esto haría que las aplicaciones web fueran mucho más interactivas, receptivas y agradables de usar. Sin embargo, no estaba del todo claro cómo conseguirlo. ¿Qué lenguaje de programación tendría más sentido? ¿Cómo equilibraríamos la potencia expresiva con curvas de aprendizaje poco profundas para que más personas pudieran participar en el proceso de desarrollo? ¿Qué lenguajes funcionaban mejor que otros, y cómo protegeríamos los recursos sensibles del lado del cliente frente al software malicioso?
La mayor parte de la innovación en el espacio de los navegadores fue impulsada originalmente por Netscape Communications Corp. Lo creas o no, Netscape Navigator era originalmente un software de pago, pero el mayor interés de la empresa era vender software del lado del servidor.15 Ampliando lo que era posible en el cliente, podía crear y vender una funcionalidad de servidor más potente y lucrativa.
En aquel momento, Java estaba emergiendo de sus inicios como lenguaje integrado para dispositivos de consumo, pero aún no tenía mucho historial de éxito. Era una idea atractiva como versión simplificada de C++ que se ejecutaba en una plataforma virtual y, por tanto, era inherentemente multiplataforma. Como entorno diseñado para ejecutar software descargado a través de la red, incorporaba seguridad mediante el diseño del lenguaje, contenedores aislados y modelos de permisos de grano fino.
Portar aplicaciones entre varios sistemas operativos era un asunto delicado, y la perspectiva de no tener que hacerlo creó un frenesí en torno a lo que sería el futuro del desarrollo de software. Sun Microsystems se encontró en la envidiable posición de tener una solución a una tormenta perfecta de problemas y oportunidades. Dado este potencial, se estaba discutiendo llevar Java al navegador, pero no estaba claro cómo sería ese acuerdo ni cuándo aterrizaría.
Como lenguaje de programación orientada a objetos (POO) , Java contenía sofisticadas características del lenguaje, como hilos y herencia. A Netscape le preocupaba que esto pudiera resultar demasiado difícil de dominar para los desarrolladores de software no profesionales , así que la empresa contrató a Brendan Eich para crear un "Scheme16 para el navegador", imaginando un lenguaje de scripting más sencillo y ligero.17 Brendan tenía libertad para tomar algunas decisiones sobre lo que quería incluir en el lenguaje, pero también estaba bajo presión para hacerlo lo antes posible. Un lenguaje para aplicaciones interactivas se consideraba un paso adelante crucial para esta plataforma emergente, y todo el mundo lo quería para ayer. Como señala Sebastián Peyrott en la entrada del blog "Breve historia de JavaScript", lo que surgió fue "un prematuro lovechild de Scheme y Self, con aspecto de Java".18
Al principio, JavaScript en el navegador se limitaba a interacciones sencillas, como menús dinámicos, cuadros de diálogo emergentes y respuesta a clics de botones. Eran avances significativos respecto a los viajes de ida y vuelta al servidor para cada acción, pero seguían siendo juguetes comparados con lo que era posible en las máquinas de sobremesa y estaciones de trabajo de la época.
La empresa para la que trabajé durante los primeros días de la web creó el primer entorno de visualización de toda la Tierra, con terabytes de información del terreno, imágenes hiperespectrales y extracción de fotogramas de vídeo de drones.19 Al principio se necesitaban estaciones de trabajo Silicon Graphics, por supuesto, pero en un par de años se pudo ejecutar en PC con unidades de procesamiento gráfico (GPU) de consumo. Nada parecido era remotamente posible en la web por aquel entonces, aunque, gracias a WebAssembly, eso ya no es cierto.20
Sencillamente, no se podía confundir el verdadero desarrollo de software con el desarrollo web. Sin embargo, como hemos señalado, una de las cosas buenas de la separación de intereses entre el cliente y el servidor era que el cliente podía evolucionar independientemente del servidor. Mientras Java y el modelo Java Enterprise llegaron a dominar el backend, JavaScript evolucionó en el navegador y acabó convirtiéndose en la fuerza dominante que es.
Evolución de la Plataforma Web
Cuando los applets de Java y JavaScript pasaron a estar disponibles en en el navegador Netscape, los desarrolladores empezaron a experimentar con páginas dinámicas, animaciones y componentes de interfaz de usuario más sofisticados. Durante años no fueron más que aplicaciones de juguete, pero la visión era atractiva y no era difícil imaginar adónde podría llevarnos.
Microsoft sentía la necesidad de seguir el ritmo, pero no estaba demasiado interesada en apoyar directamente las tecnologías de sus competidores. Pensaba (con razón) que este desarrollo web podría acabar poniendo en peligro su dominio del sistema operativo. Cuando Microsoft lanzó Internet Explorer con soporte para scripts, la empresa lo llamó JScript para evitar problemas legales y realizó ingeniería inversa del intérprete de Netscape. La versión de Microsoft soportaba la interacción con elementos del Modelo de Objetos Componentes (COM) basados en Windows y tenía otros giros que facilitaban la escritura de scripts incompatibles entre los navegadores. Su apoyo inicial a los esfuerzos por estandarizar JavaScript como ECMAScript decayó durante un tiempo y, finalmente, comenzaron las Guerras de los Navegadores.21 Fue una época frustrante para los desarrolladores y, en última instancia, conllevó demandas anticompetitivas contra Microsoft por parte del gobierno estadounidense.
Cuando la suerte de Netscape decayó, Internet Explorer empezó a dominar el espacio de los navegadores y la innovación multiplataforma decayó durante un tiempo, incluso mientras JavaScript pasaba por el proceso de estandarización. Los applets de Java se generalizaron en algunos círculos, pero se ejecutaban en un entorno aislado, por lo que era más difícil utilizarlos como base para impulsar la actividad dinámica de las páginas web. Ciertamente, podías utilizar las API gráficas y de interfaz de usuario de Sun para hacer cosas productivas y divertidas en , pero se ejecutaban en un espacio de memoria distinto del Modelo de Objetos del Documento HTML (DOM).22 Eran incompatibles y tenían modelos de programación y eventos diferentes. Las interfaces de usuario no tenían el mismo aspecto entre los elementos sandboxed y los elementos web. En general, era una experiencia de desarrollo totalmente inadecuada.
Otras tecnologías no portables como ActiveX se hicieron populares en el espacio de desarrollo web de Microsoft. Flash de Macromedia se convirtió en Flash de Adobe y tuvo un breve pero activo periodo de popularidad durante aproximadamente una década. Sin embargo, todas estas opciones secundarias seguían presentando problemas. Los espacios de memoria estaban amurallados entre sí y los modelos de seguridad eran menos robustos de lo que nadie había esperado. Los motores eran nuevos y estaban en constante desarrollo, por lo que los fallos eran frecuentes. ActiveX proporcionaba protecciones de firma de código pero no sandboxing, por lo que se hacían posibles ataques bastante terroríficos si se podían falsificar certificados.
Firefox surgió de Mozilla como un competidor viable de las cenizas de Netscape. Con el tiempo, tanto él como Chrome de Google se convirtieron en alternativas adecuadas a Internet Explorer. Cada bando tenía sus adeptos, pero cada vez había más interés en resolver las incompatibilidades entre ellos. La introducción de la posibilidad de elegir en el espacio de los navegadores obligó a cada uno de los vendedores a esforzarse más y hacerlo mejor para eclipsarse mutuamente como medio de lograr el dominio técnico y atraer cuota de mercado.
Como resultado, los motores de JavaScript consiguieron que fuera significativamente más rápido. Aunque HTML 4 seguía siendo "estrafalario" y difícil de usar en distintos navegadores y plataformas, empezaba a ser posible aislar esas diferencias. La combinación de estos avances y el deseo de trabajar dentro de las estructuras de los entornos basados en estándares animó a Jesse James Garrett a imaginar un enfoque diferente del desarrollo web. Él introdujo el término Ajax, que significaba la combinación de un conjunto de estándares: JavaScript asíncrono y XML. La idea era dejar que los datos de los sistemas backend fluyeran hacia las aplicaciones frontend, que responderían dinámicamente a las nuevas entradas. Al trabajar a nivel de manipulación del DOM en lugar de tener un espacio de interfaz de usuario separado y aislado, los navegadores podían convertirse en consumidores universales de aplicaciones en arquitecturas cliente-servidor basadas en la web.
El sufrido proceso de estandarización de HTML 5 había comenzado también durante este periodo, en un intento de mejorar la coherencia entre navegadores, introducir nuevoselementos de entrada y modelos de metadatos, y proporcionar gráficos 2D y elementos de vídeo acelerados por hardware, entre otras características. La convergencia del estilo Ajax, la estandarización y maduración de ECMAScript como lenguaje, la mayor facilidad de compatibilidad entre navegadores y un entorno web cada vez más rico en funciones provocaron una explosión de actividad e innovación. Hemos visto innumerables marcos de aplicación basados en JavaScript ir y venir, pero había un impulso constante hacia adelante en cuanto a lo que era posible. A medida que los desarrolladores ampliaban los límites, los proveedores de navegadores mejoraban sus motores para permitir que los límites se ampliaran aún más. Fue un ciclo virtuoso que dio paso a nuevas visiones del potencial de los sistemas de software seguros, portátiles y sin instalación.
A medida que se eliminaban otros obstáculos y limitaciones, este pequeño y extraño lenguaje en el centro de todo se convirtió en un lastre cada vez más inercial para el avance. Los motores se fueron convirtiendo en entornos de desarrollo de primera clase, con mejores herramientas de depuración y análisis del rendimiento. Los nuevos paradigmas de programación, como el estilo basado en promesas, permitían que el código de las aplicaciones mejor modularizadas y asíncronas obtuviera resultados potentes en el entorno notoriamente monohilo de JavaScript.23 Pero el propio lenguaje era incapaz de los tipos de optimizaciones que eran posibles en otros lenguajes como C o C++. Sencillamente, había límites a lo que iba a ser posible desde el punto de vista del rendimiento del lenguaje.
Los estándares de la plataforma web siguieron avanzando con el desarrollo y la adopción de tecnologías como WebGL24 y WebRTC.25 Por desgracia, las limitaciones de rendimiento de JavaScript lo hacían poco adecuado para ampliar los navegadores con funciones que implicaban redes de bajo nivel, código multihilo y códecs de gráficos y vídeo en streaming.
La evolución de la plataforma requería el penoso trabajo de las organizaciones miembros del W3C para decidir qué era importante diseñar y construir, y luego desplegarlo en las distintas implementaciones de los navegadores. A medida que la gente se interesaba cada vez más por utilizar la web como plataforma para aplicaciones más pesadas e interactivas, este proceso se consideraba cada vez más insostenible. O bien había que escribirlo todo (o reescribirlo) enJavaScript, o bien los navegadores tenían que estandarizar el comportamiento y las interfaces, lo que podía significar que se tardaría años en realizar nuevos avances.
Fue por estas y otras razones que Google empezó a considerar un enfoque alternativo para el desarrollo web del lado del cliente seguro, rápido, y portable.
Cliente nativo (NaCl)
En 2011, Google lanzó un nuevo proyecto de código abierto llamado Cliente Nativo (NaCl). La idea era proporcionar una ejecución de código a velocidad casi nativa en el navegador, mientras se ejecutaba en un sandbox de privilegios limitados por razones de seguridad. Puedes pensar que es un poco como ActiveX con un modelo de seguridad real detrás. La tecnología encajaba bien con algunos de los objetivos más amplios de Google, como dar soporte a ChromeOS y alejar las aplicaciones de escritorio de las aplicaciones web. Inicialmente, no estaba pensada para ampliar las capacidades de la web abierta para todo el mundo.
Los casos de uso eran principalmente para apoyar la entrega basada en navegador de software de cálculo intensivo, como:
-
Juegos
-
Sistemas de edición de audio y vídeo
-
Informática científica y sistemas CAD
-
Simulaciones
Inicialmente se centró en C y C++ como lenguajes fuente , pero como se basaba en la cadena de herramientas del compilador LLVM,26 sería posible admitir lenguajes adicionales que pudiera generar la Representación Intermedia (IR) de LLVM.27 Este será un tema recurrente en nuestra transición a WebAssembly, como verás.
Aquí había dos formas de código distribuible. La primera era la homónima NaCl, que daba lugar a módulos "nexe" dirigidos a una arquitectura de hardware específica (por ejemplo, ARM o x86-64) y que sólo podían distribuirse a través de la tienda Google Play. La otra era una forma portátil llamada PNaCl28 que se expresaría en el formato Bitcode de LLVM, haciéndolo independiente del objetivo. Se llamaban módulos "pexe" y tendrían que transformarse en una arquitectura nativa en elentorno anfitrión del cliente.
La tecnología tuvo éxito en el sentido de que el rendimiento demostrado en el navegador sólo se alejaba mínimamente de las velocidades de ejecución nativas. Utilizando las técnicas de aislamiento de fallos de software (SFI) de , fue posible descargar código seguro y de alto rendimiento de la web y ejecutarlo en navegadores. Varios juegos populares como Quake yDoom se compilaron en este formato para mostrar lo que era posible en última instancia. El problema era que los binarios NaCl debían generarse y mantenerse para cada plataforma de destino y sólo se ejecutaban en Chrome. También se ejecutaban en un espacio fuera de proceso, por lo que no podían interactuar directamente con otras API Web o código JavaScript.
Aunque se podía ejecutar en entornos aislados con privilegios limitados, era necesariovalidar estáticamente los archivos binarios para asegurarse de que no intentaban invocar directamente servicios del sistema operativo. El código generado tenía que seguir ciertos patrones de alineación de límites de direcciones para asegurarse de que no violaba los espacios de memoria asignados.
Como ya se ha indicado, los módulos PNaCl eran más portables. La infraestructura LLVM podía generar el código NaCl-nativo o el Bitcode portable sin modificar el código fuente original. Fue un buen resultado, pero hay una diferencia entre la portabilidad del código y la portabilidad de la aplicación. Las aplicaciones necesitan que las API de las que dependen estén disponibles para funcionar. Google proporcionó una interfaz binaria de aplicación (ABI) llamada API Pepper29 para servicios de bajo nivel, como bibliotecas de gráficos 3D, reproducción de audio, acceso a archivos (emulado sobre IndexedDB o LocalStorage), y más. Aunque los módulos PNaCl podían ejecutarse en Chrome en diferentes plataformas gracias a LLVM, sólo podían ejecutarse en navegadores que proporcionaran implementaciones adecuadas de las API Pepper. Aunque Mozilla había expresado en un principio su interés en hacerlo, finalmente decidieron que querían probar un enfoque diferente que llegó a conocerse como asm.js. NaCl merece un enorme reconocimiento por hacer avanzar a la industria en esta dirección, pero al final fue demasiado engorroso y demasiado específico de Chrome para hacer avanzar la web abierta. El intento de Mozilla tuvo más éxito en ese frente, aunque no proporcionara a el mismo nivel de rendimiento que el enfoque del cliente nativo.
asm.js
El proyecto asm.js estaba, al menos en parte, motivado por un intento de llevar a la web una mejor historia de los juegos. Esto pronto se amplió para incluir el deseo de permitir que aplicaciones arbitrarias llegaran de forma segura a los sandboxes de los navegadores sin tener que modificar sustancialmente el código existente.
Como hemos comentado anteriormente, el ecosistema de los navegadores ya estaba avanzando para que los gráficos 2D y 3D, el manejo del audio, el vídeo acelerado por hardware y otros elementos estuvieran disponibles de forma multiplataforma y basada en estándares. La idea era que operar dentro de ese entorno permitiría a las aplicaciones utilizar cualquiera de esas funciones definidas para ser invocadas desde JavaScript. Los motores de JavaScript eran eficientes y disponían de robustos entornos sandboxed que habían sido sometidos a importantes auditorías de seguridad, por lo que nadie tenía ganas de empezar de cero ahí. El verdadero problema seguía siendo la incapacidad de optimizar JavaScript por adelantado (AOT), de modo que el rendimiento en tiempo de ejecución de pudiera mejorarse aún más.
Debido a su naturaleza dinámica y a la falta de un soporte de enteros adecuado, había varios obstáculos de rendimiento que no podían gestionarse de forma significativa hasta que el código se cargaba en el navegador. Una vez que esto ocurría, los compiladores de optimización "justo a tiempo" (JIT) de podían acelerar las cosas, pero seguían existiendo problemas inherentes, como la lentitud de las referencias a matrices con comprobación de límites. Aunque JavaScript en su totalidad no podía optimizarse antes de tiempo, un subconjunto sí podía.
Los detalles exactos de lo que eso significa no son superrelevantes para nuestra narración histórica, pero el resultado final sí lo es. asm.js también utilizaba el analizador clang basado en LLVM30 a través de la cadena de herramientas Emscripten.31 El código C y C++ compilado es muy optimizable de antemano, por lo que las instrucciones generadas pueden hacerse muy rápidas mediante pases de optimización existentes. LLVM representa una arquitectura limpia y modular, por lo que se pueden sustituir partes de él, incluida la generación backend del código máquina. En esencia, el equipo de Emscripten podía reutilizar las dos primeras etapas (análisis sintáctico y optimización) y luego emitir este subconjunto de JavaScript como un backend personalizado. Como el resultado sería "sólo JavaScript", sería mucho más portable que el método NaCl/PNaCl. Lamentablemente, la contrapartida estaba en el rendimiento. Representaba una mejora significativa con respecto al JavaScript directo, pero no era ni de lejos tan eficaz como el método de Google. Sin embargo, era lo suficientemente bueno como para sorprender a los desarrolladores. Sin embargo, más allá de las modestas mejoras de rendimiento, el mero hecho de poder implementar aplicaciones C y C++ existentes en un navegador con un rendimiento razonable y prácticamente sin cambios en el código era convincente. Aunque hubo demostraciones muy convincentes con el motor Unity,32 veamos un ejemplo sencillo. "¡Hola, mundo!" parece un buen punto de partida:
#include <stdio.h>
int
main
()
{
printf
(
"Hello, world!
\n
"
);
return
0
;
}
Observa que no hay nada inusual en esta versión del programa clásico. Si lo almacenaras en un archivo llamado hola.c, la cadena de herramientas Emscripten te permitiría emitir un archivo llamado a.out.js, que puede ejecutarse directamente en Node.js33 o, mediante algún andamiaje, en un navegador:
brian@tweezer ~/s/w/ch01> emcc hello.c brian@tweezer ~/s/w/ch01> node a.out.js Hello, world!
Muy guay, ¿no?
Sólo hay un problema:
brian@tweezer ~/s/w/ch01> ls -lah a.out.js -rw-r--r-- 1 brian staff 119K Aug 17 19:08 a.out.js
Con 119 kilobytes, ¡es un programa "Hola, mundo" terriblemente grande! Un vistazo rápido al ejecutable nativo puede darte una idea de lo que está pasando:
brian@tweezer ~/s/w/ch01> clang hello.c brian@tweezer ~/s/w/ch01> ls -lah a.out -rwxr-xr-x 1 brian staff 48K Aug 17 19:11 a.out
¿Por qué nuestro programa JavaScript supuestamente optimizado es casi tres veces más grande que la versión nativa? No es sólo porque, como archivo basado en texto, JavaScript es más verboso. Mira de nuevo el programa:
#
include <stdio.h>
int
main
(
)
{
printf
(
"
Hello, world!
\n
"
)
;
return
0
;
}
La cabecera identifica la fuente de las definiciones de las funciones estándar relacionadas con la IO.
La referencia a la función
printf()
será satisfecha por una biblioteca dinámica cargada en tiempo de ejecución.
Si miramos los símbolos definidos en el ejecutable compilado mediante nm, veremos que la definición de la función printf()
no está contenida en el binario.34 Está marcada con "U" de "indefinida":
brian@tweezer ~/s/w/ch01> nm -a a.out 0000000100002008 d __dyld_private 0000000100000000 T __mh_execute_header 0000000100000f50 T _main U _printf U dyld_stub_binder
Cuando Clang generó el ejecutable, dejó una referencia a la función que espera que proporcione el sistema operativo. No hay ninguna biblioteca estándar disponible de este modo para un navegador, al menos no en el sentido de cargable dinámicamente, por lo que habrá que proporcionar la función de la biblioteca y todo lo que necesite. Además, esta versión no puede hablar directamente con la consola de un navegador, por lo que habrá que proporcionarle ganchos para llamar a una función como la del navegador console.log()
. Para que funcione en el navegador, la funcionalidad tiene que enviarse con la aplicación, por eso acaba siendo tan grande.
Esto destaca muy bien la diferencia entre código portable y aplicaciones portables, otro tema común en este libro. Por ahora, podemos maravillarnos de que funcione, pero hay una razón por la que este libro no se llama asm .js: La Guía Definitiva. asm.js fue un hito notable que demostró que era posible generar código JavaScript razonablemente eficaz en un entorno aislado a partir de varios lenguajes optimizables. El propio JavaScript también podía optimizarse aún más de formas que el superconjunto no podía. Al poder generar este subconjunto mediante cadenas de herramientas basadas en LLVM y un backend personalizado, el nivel de esfuerzo fue mucho menor de lo que podría haber sido de otro modo.
asm.js representa una buena posición de repliegue para los navegadores que no soportan los estándares WebAssembly, pero ahora es el momento de preparar el escenario para el tema del libro.
El auge de WebAssembly
Con NaCl, encontramos una solución que proporcionaba sandboxing y rendimiento. Con PNaCl, encontramos portabilidad de plataforma, pero no portabilidad de navegador. Con asm.js, encontramos portabilidad del navegador y sandboxing, pero no el mismo nivel de rendimiento. También estábamos limitados a tratar con JavaScript, lo que significaba que no podíamos ampliar la plataforma con nuevas características (por ejemplo, enteros eficientes de 64 bits) sin cambiar primero el propio lenguaje. Dado que éste se regía por una organización internacional de normalización, era poco probable que se tratara de un enfoque con plazos de entrega rápidos.
Además, JavaScript tiene ciertos problemas con la forma en que los navegadores lo cargan y validan desde la web. El navegador tiene que esperar a que termine de descargar todos los archivos referenciados antes de empezar a validarlos y optimizarlos (mientras que las optimizaciones posteriores requerirán que esperemos a que la aplicación ya se esté ejecutando). Teniendo en cuenta lo que ya hemos dicho sobre cómo los desarrolladores cargan sus aplicaciones con cantidades ridículamente grandes de dependencias transitivas, la transferencia de red y el rendimiento en tiempo de carga de JavaScript es otro cuello de botella que hay que superar más allá de los problemas establecidos en tiempo de ejecución.
Después de ver lo que era posible con estas soluciones parciales, surgió un fuerte apetito de código portátil, de alto rendimiento y con sandbox. Varias partes interesadas de los entornos de navegadores, estándares web y JavaScript sentían la necesidad de una solución que funcionara dentro de los límites del ecosistema existente. Se había realizado un enorme trabajo para que los navegadores llegaran hasta donde habían llegado. Era totalmente posible crear aplicaciones dinámicas, atractivas e interactivas entre plataformas de sistemas operativos e implementaciones de navegadores. Con sólo un poco más de esfuerzo, parecía posible fusionar estas visiones en un enfoque unificador basado en estándares.
Fue en estas circunstancias, en 2015, cuando nada menos que Brendan Eich, el creador de JavaScript, anunció que se había empezado a trabajar en WebAssembly.35 Destacó algunas razones específicas del esfuerzo y lo denominó una "sintaxis binaria para código seguro de bajo nivel, inicialmente co-expresiva con asm.js, pero a largo plazo capaz de divergir de la semántica de JS, con el fin de servir mejor como formato común a nivel de objeto para múltiples lenguajes de programación a nivel de fuente".
Y continuó: "Ejemplos de una posible divergencia a más largo plazo: excepciones de coste cero, enlace dinámico, call/cc. Sí, nuestro objetivo es desarrollar el formato de archivo objeto del lenguaje de programación políglota de la Web".
En cuanto a por qué estas diversas partes estaban interesadas en esto, ofreció esta justificación: "asm.js es genial, pero una vez que los motores optimizan para él, el analizador sintáctico se convierte en el punto caliente, muy caliente en los dispositivos móviles. La compresión del transporte es necesaria y ahorra ancho de banda, pero la descompresión antes del análisis sintáctico es perjudicial".
Y por último, quizá lo más sorprendente del anuncio fue quién iba a participar:
Un Grupo de la Comunidad del W3C, el WebAssembly CG, abierto a todos. Como puedes ver en los registros de GitHub, WebAssembly ha sido hasta ahora un esfuerzo conjunto de Google, Microsoft, Mozilla y algunas otras personas. Lamento que al principio el trabajo se realizara a través de una cuenta privada de GitHub, pero se trataba de una medida temporal para ayudar a las distintas grandes empresas a alcanzar un consenso y participar en el juego cooperativo a largo plazo que hay que jugar para sacar esto adelante.
En poco tiempo, otras empresas como Apple, Adobe, AutoCAD, Unity y Figma se sumaron al esfuerzo. Esta visión que había comenzado décadas antes y había implicado un sinfín de conflictos e intereses propios se estaba transformando en una iniciativa unificada para traernos por fin un entorno de ejecución seguro, rápido, portátil y compatible con la web.
No había fin a las posibles complejidades confusas que conllevaba la creación de esta plataforma. No estaba del todo claro lo que había que especificar por adelantado. No todos los lenguajes admiten hilos de forma nativa. No todos los lenguajes utilizan excepciones. C/C++ y Rust eran ejemplos de lenguajes que tenían tiempos de ejecución que no requerían recolección de basura. El diablo siempre está en los detalles, pero la voluntad de colaborar estaba ahí. Y, como suele decirse, donde hay voluntad, hay un camino.
Aproximadamente un año después, el CG se convirtió en un Grupo de Trabajo (GT) del W3C, al que se encomendó la tarea de definir normas reales. Tomaron una serie de decisiones para definir una plataforma WebAssembly de Producto Mínimo Viable (MVP) que fuera compatible con los principales proveedores de navegadores. Además, la comunidad Node.js estaba entusiasmada, ya que esto podría proporcionar una solución a la pesada tarea de gestionar bibliotecas nativas para las partes de las aplicaciones Node que debían escribirse en un lenguaje de bajo nivel. En lugar de tener dependencias de bibliotecas de Windows, Linux y macOS, una aplicación Node.js podría tener una biblioteca WebAssembly que podría cargarse en el entorno V8 y convertirse en código ensamblador nativo sobre la marcha. De repente, WebAssembly parecía preparado para ir más allá de los objetivos de implementación de código en navegadores, pero no nos adelantemos. Tenemos el resto de este libro para contarte esa parte de la historia.
1 Andreas Haas et al., "Bringing the Web Up to Speed with WebAssembly", presentado en la 38ª Conferencia ACM SIGPLAN sobre Diseño e Implementación de Lenguajes de Programación, junio de 2017, http://dx.doi.org/10.1145/3062341.3062363.
2 Durante muchos años, OpenGL fue el estándar que definía las aplicaciones gráficas 3D portátiles. Actualmente está siendo suplantado por API más modernas como Vulkan y Metal, pero puedes obtener más información sobre los estándares en el sitio web de OpenGL.
3 La Interfaz Portátil de Sistemas Oper ativos (POSIX) es una colección de normas IEEE para definir la funcionalidad común de las aplicaciones, de modo que funcione en múltiples sistemas operativos.
4 Win32 forma parte de un conjunto más amplio de API que proporcionan a los desarrolladores acceso a funciones comunes disponibles en los sistemas operativos Windows.
5 Esta técnica permite al decisor establecer unos criterios mínimos para decidir cuándo se satisfacen sus necesidades. El objetivo del satisficing no es encontrar una solución perfecta, sino una que sea aceptable dada la situación en cuestión.
6 La página de Wikipedia sobre npm destaca varios casos en los que las dependencias rotas han tenido grandes repercusiones.
7 Lo mejor que se puede decir es que fue J.F. Bastien quien lo dijo por primera vez, pero ni siquiera él está seguro.
8 El lenguaje ensamblador es un lenguaje de programación de bajo nivel, normalmente asociado a la arquitectura del procesador y al conjunto de instrucciones de una determinada máquina.
9 El nombre CERN procede del francés Conseil Européen pour la Recherche Nucléaire. Sus numerosos y apasionantes proyectos se detallan en su página web.
10 ¡En su tiempo libre!
11 Archie fue uno de los primeros motores de búsqueda para ayudar a la gente de a encontrar archivos en servidores FTP.
12 Gopher fue un emocionante precursor de la web basada en HTTP de la que hemos llegado a depender.
13 El Servidor de Información de Área Amplia (WAIS) fue otro de los primeros sistemas para buscar y solicitar información textual en sistemas distribuidos.
14 SGML es una norma ISO para definir documentos estructurados y declarativos que sirvió de base a HTML, DocBook y LinuxDoc.
15 Por aquel entonces compré una licencia de Netscape 1.0 Silicon Graphics IRIX. Todavía tengo el CD dando vueltas por algún sitio por... razones históricas.
16 Scheme es una versión bastante ligera de Lisp.
17 Hay un buen resumen de la historia temprana de JavaScript en Internet.
18 Self es un lenguaje de programación orientado a objetos que influyó en la herencia basada en prototipos de JavaScript.
19 Autometric tenía unos antecedentes alocados que implicaban a Paramount Pictures, el tubo Trinitron y ayudar a la NASA a decidir dónde aterrizar en la Luna. Desde entonces, ha sido adquirida por Boeing.
20 Google Earth ahora se ejecuta en el navegador.
21 Aunque actualmente los fabricantes de navegadores tienden a colaborar más estrechamente en materia de estándares, durante un tiempo compitieron ferozmente. Este período de tiempo se discute en Wikipedia.
22 El DOM es la estructura de árbol de una página web o aplicación que es renderizada por el navegador. A menudo se envía en forma textual declarativa del servidor al cliente como HTML, pero JavaScript es capaz de manipularlo en el navegador.
23 Las promesas (o futuros) permiten a los desarrolladores modelos de programación relativamente sencillos, al tiempo que proporcionan aplicaciones con capacidad concurrente.
24 WebGL llevó a la web un modelo similar de gráficos 3D del mundo OpenGL.
25 WebRTC proporciona mecanismos para establecer acceso autorizado a cámaras y micrófonos, así como conexiones cifradas entre iguales.
26 LLVM no significa nada, pero es una cadena de herramientas extremadamente influyente que deberías conocer mejor. Lo mencionaremos con frecuencia en este libro.
27 Normalmente, el software se compila en forma binaria, ejecutable. El RI permite que exista en una forma analizada y estructurada con fines de optimización, entre otras razones.
28 Se pronuncia "pináculo".
29 Porque, NaCl... ¿lo pillas?
30 Clang es una suite de herramientas de compilación LLVM para C, C++ y Objective-C.
31 Aprenderemos más sobre Emscripten en el transcurso del libro, pero si tienes curiosidad.
32 Conseguir una experiencia de juego sin instalación en el navegador está impulsando gran parte de esta innovación. Puedes ver un ejemplo del motor Unity utilizando WebGL en el navegador.
33 Node.js es un entorno JavaScript del lado del servidor extremadamente popular del que hablaremos más en el Capítulo 8.
34 nm es un comando de Unix para mostrar la tabla de símbolos de un archivo ejecutable.
35 Brendan Eich, "De ASM.JS a WebAssembly", 17 de junio de 2015, https://brendaneich.com/2015/06/from-asm-js-to-webassembly.
Get WebAssembly: La Guía Definitiva now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.