Emscripten Ernesto Alfonso Bascón Pantoja Jalasoft [email protected] http://oopscenities.net RESUMEN usuario, con muchas caracterı́sticas diseñadas para soportar mouse, aceleradores de teclado, múltiples ventanas, etc. (Adobe PhotoShop, Adobe Illustrator, Microsoft Office, Microsoft Visual Studio, Fruity Loops, AutoCAD, etc.) que tienen interfaces de usuario maduras y cuyas caracterı́sticas son difı́ciles de portar a entornos web. En este ensayo presentaremos Emscripten [1], una infraestructura de desarrollo que permite que aplicaciones escritas en código nativo (generalmente usando C ó C++) se ejecuten dentro de un navegador web. Keywords Emscripten, web, nativo, C, C++, navegador, compilador, plataforma, LLVM, JavaScript, asm.js 1. • Alto consumo de recursos. Algunas aplicaciones (como los juegos), necesitan una inmensa cantidad de recursos (memoria, tarjetas gráficas, procesador, etc.) no disponibles para aplicaciones web. INTRODUCCIÓN Dada la emergencia vertiginosa de las tecnologı́as web, el desarrollo de aplicaciones para estas tecnologı́as se ha visto a su vez acelerado gracias a las grandes mejoras en el rendimiento de los motores de ejecución de JavaScript en la mayorı́a de los navegadores web (Firefox, Chrome, Internet Explorer, Safari y Opera) y gracias a la proliferación de frameworks que facilitan el desarrollo de mencionadas aplicaciones. Estos frameworks implementan diferentes caracterı́sticas que facilitan el desarrollo de aplicaciones cliente, mejoran la interacción con el servidor, mantienen el modelo de datos sincronizado entre los clientes y los servidores, etc. Y aunque dichos frameworks facilitan la creación de software web, existe en el mercado una cantidad inmensa de aplicaciones escritas para entornos de escritorio que no son fácilmente portables a entornos web por diversos factores: • Falta de motivación. Si bien estamos en una época en que todo es desechable (incluı́do el software), muchas empresas de desarrollo pueden encontrar falta de motivación para rehacer sobre una plataforma web sus aplicaciones que ya existen, escalan, tienen muchos bugs arreglados, venden, funcionan y sobretodo, cubren las necesidades de sus clientes. Emscripten, al ser una infrastructura de desarrollo (compilador, toolchain, SDK, runtime, etc.) que permite compilar aplicaciones nativas para que puedan ejecutarse (con poca o ninguna modificación) dentro de un navegador, permite que aplicaciones ya existentes, de alto rendimiento o de interfaz de usuario compleja, puedan implantarse en el mundo de las aplicaciones web; eliminando en su totalidad (o en muy alto grado) la necesidad de reescribir la aplicación. • Alta complejidad. Aplicaciones escritas desde hace muchos años que han venido creciendo y adquiriendo caracterı́sticas que las hacen muy completas pero a su vez complejas y difı́ciles de rescribir en un tiempo prudente. Emscripten es implementado y desarrollado en Mozilla. • Interfaz de usuario sofisticada. Aplicaciones desarrolladas desde cero pensando en la experiencia del El proyecto de software libre LLVM es una infraestructura de compilación diseñada como un conjunto de librerı́as modulares reusables con una interfaz muy bien definida. Gracias a este enfoque, LLVM facilita la construcción de compiladores, optimizadores y generadores de código binario para diferentes conjuntos de instrucciones de una manera bastante sencilla.[2] Permission to make digital or hard copies of all or part of this work for personal or classroom use is only granted for Jalasoft employees or use at the Jala Foundation respectively; copies must bear this notice and the full citation on the first page. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. c 2015 JalaTechZone, octubre 2015, Cochabamba, Bolivia. Copyright soft TechZone. 2. 2.1 INFRAESTRUCTURA LLVM A diferencia de los compiladores GCC que están diseñados de una manera monolı́tica, LLVM proporciona múltiples componentes que pueden acoplarse o intercambiarse entre sı́ (ver figura 1), como ser: 3 Figura 1: Arquitectura de LLVM CARACTERÍSTICAS DE EMSCRIPTEN Figura 2: Comparativa de rendimiento de Asm.js [4] • Varios front-ends. LLVM soporta varios frontends, y gracias a ello, es posible conectar a la infraestructura de compilación una variedad de lenguajes de programación diferentes, como ser C, C++, ObjectiveC, Rust, Ruby, Python, Haskell, etc. Los front-ends de LLVM toman código fuente escrito en algún lenguaje de programación y generan un Árbol de Sintaxis Abstracto (AST) que es una representación estándar del código fuente. • Optimizador de Representación Intermedia. Es el corazón de LLVM: A partir del árbol de sintaxis abstracto, LLVM genera la representación intermedia (IR) que es su núcleo fundamental. Esta representación intermedia es un lenguaje de programación de bajo nivel similar a assembler. IR es un conjunto de instrucciones RISC fuertemente tipado que no toma en cuenta detalles de la plataforma destino de la aplicación. que el motor de ejecución que lo implementa, pueda optimizar la ejecución de un programa Asm.js bastante y ası́ lograr velocidades de ejecución mucho mayores y en algunos casos cercanas a velocidades nativas. Mozilla ha diseñado y agregado soporte para asm.js en su navegador Firefox, pero Google Chrome y Microsoft Edge lo han implementado también. Éste es un ejemplo de código Asm.js escrito manualmente: function s t r l e n ( ptr ) { // c a l c u l a e l tamaño de una cadena C ptr = ptr | 0 ; var c u r r = 0 ; curr = ptr ; w h i l e (MEM8[ c u r r ] | 0 != 0 ) { curr = ( curr + 1 ) | 0 ; } return ( curr − ptr ) | 0 ; } • Varios back-ends. Los back-ends permiten transformar código IR en una salida orientada a la plataforma de destino, ası́, se pueden proveer back-ends para x86, x86-64, AMD, MIPS, etc. Emscripten es un back-end de LLVM, puesto que transforma IR en asm.js. En el código escrito arriba, • Intérprete. LLVM también provee de un intérprete que permite ejecutar directamente IR. • ptr representa el puntero que apunta al primer caracter de una cadena C. • Depurador. LLVM incorpora un depurador que reemplaza al depurador gdb de gcc. 2.2 • "|0" no tiene efecto en la expresión donde se encuentra pues realiza una operación ”OR” a nivel de bits; PERO indica al motor de ejecución de JavaScript que la expresión es una expresión entera (y ası́ optimizar su ejecución). Sin dicha operación ”OR”, el motor de ejecución podrı́a inferir que la variable es de otro tipo numérico. Asm.js Asm.js es un subconjunto estricto de JavaScript que puede ser usado como un lenguaje de bajo nivel como destino de compilación. Asm.js provee una abstracción similar a la del modelo de C ó C++: un gran ”heap” binario con almacenamiento eficiente, aritmética de números enteros y con coma flotante, definiciones de funciones de primer orden y punteros a funciones[3]. • MEM8 es el array que representa a la memoria de la aplicación C. Asm.js es, a diferencia de JavaScript, un lenguaje fuertemente tipado donde la memoria es modelada como un gran array; ası́, cualquier asignación, acceso o liberación de memoria es hecha como una operación sobre ese array. El código Asm.js por lo general no es fácilmente legible o entendible por seres humanos pero tampoco ha sido diseñado con ese objetivo, sino como un lenguaje de muy bajo nivel (de ahı́ el nombre asm.js) que simula un entorno de ejecución nativo. En la figura 2, se pueden ver las diferencias de rendimiento de varios algoritmos implementados nativamente y usando Asm.js y ejecutándolos en varios entornos diferentes. 3. CARACTERÍSTICAS DE EMSCRIPTEN Como se ve en la figura 3, Emscripten está construı́do sobre LLVM: Reutiliza el frontend de Clang y el optimizador de IR e implementa un backend que genera código Asm.js. Un programa escrito en Asm.js se comportará de manera idéntica tanto en un motor de ejecución que sabe cómo ejecutarlo, como en un motor de JavaScript estándar. Las limitaciones incorporadas a Asm.js y el tipado estricto, hacen Ası́mismo, Emscripten provee: 2 4 4. USO DE APIS WEB USO DE APIS WEB Las siguientes APIs web son soportadas en Emscripten: 4.1 Figura 3: Infraestructura de Emscripten 3.1 • MEMFS, que es un sistema de archivos almacenados en memoria; por tanto, cada vez que el programa es cargado de nuevo por el navegador, el sistema de archivos aparece al usuario como totalmente vacı́o. Emscripten proporciona los mecanismos necesarios para cargar los archivos necesarios por el sistema. Compiladores C y C++ Emscripten provee un compilador que recibe los mismos argumentos y entiende la misma sintaxis que gcc ó clang. Gracias a ello, es muy sencillo reemplazar el compilador C ó C++ por el compilador de Emcripten (emcc, em++) en un archivo make. Este compilador básicamente utiliza Clang[5] (Clang es un front-end de LLVM que permite compilar C, C++ y Objective-C) para generar código Asm.js y HTML. Al usar Clang como front-end, todas las caracterı́sticas disponibles en C++14 y en las últimas versiones del lenguaje están ya disponibles para Emscripten. 3.2 • NODEFS, es un sistema de archivos virtual que permite almacenar la información usando los mecanismos provistos por node.js. • IDBFS, es un sistema de archivos que permite almacenar la información persistentemente utilizando indexedDB, que es parte del mecanismo de persistencia de la mayorı́a de los navegadores actuales. Generador de código Asm.js y HTML El generador de código es capaz de generar, a partir de la representación intermedia de LLVM (IR), código Asm.js que puede ser ejecutado por cualquier motor de ejecución de JavaScript (como node.js) ó HTML con código JavaScript incrustado, para poder ser ejecutado directamente en un navegador web. 3.3 Lo interesante de estos sistemas de archivos virtuales, es que, aunque cada uno almacena la información de manera muy diferente, Emscripten se encarga de que su comportamiento sea exactamente el mismo para las funciones de C y C++ de manejo de archivos. Inclusión de librerías estándar 4.2 Emscripten viene con las librerı́as estándares de C y C++; ası́ cualquier aplicación estándar puede ser compilada de manera muy sencilla usando Emscripten. 3.4 FileSystem API Emscripten provee las funciones básicas de manejo de archivos de C (FILE*, fopen, fclose, fread, fwrite, etc.) pero están implementadas de tal manera que en lugar de acceder al sistema de archivos nativo (no posible debido a que el navegador trabaja en un ”sandbox” seguro), accede a un sistema de archivos virtual que puede ser: WebGL WebGL[7] es una API disponible en JavaScript que permite acceso a aceleración de gráficos por hardware. Gracias a WebGL se pueden desarrollar juegos o animaciones de alta calidad y buen rendimiento en JavaScript. SDL SDL[6] ha sido portado a Emscripten y viene ya en la plataforma. Emscripten permite utilizar las APIs de OpenGL y a parSDL es importante porque provee los cimientos para la creación tir de un ”wrapper”, dichas llamadas son transformadas en de interfaces gráficas de usuario en C++ (Qt tiene un backinvocaciones WebGL en el navegador. end para SDL y la mayorı́a de los juegos comerciales están escritos con librerı́as escritas usando SDL). 4.3 HTML5 3.5 Emscripten provee una librerı́a escrita en C que permite acceder a: Emscripten ports Emscripten ports[8] es un repositorio que contiene un conjunto de librerı́as nativas portadas a JavaScript usando Emscripten. Entre dichas librerı́as están vorbis, zlib, libpng y otras. • Eventos de teclado, ratón, rueda, foco, etc. en nivel DOM 3. 3.6 • Eventos de orientación de dispositivo para giroscopio y acelerómetro. Interoperabilidad Emscripten provee varios mecanismos para conectar C y C++ con JavaScript[9]. Existen mecanismos para: • Eventos de orientacion de pantalla, para manejo de pintado como retrato o paisaje. • Eventos para manipulación de pantalla completa. • Invocar funciones compiladas en C desde código JavaScript. • Invocar código JavaScript desde código C/C++. • APIs para manejo de vibración en teléfonos móviles. • Eventos de visibilidad de página. • Implementar una API C en JavaScript. Usando este enfoque se portó exitosamente SDL y se hizo la implementación de WebGL y libc. • Eventos de toque. • Manejo de CSS. 3 8 5. WEBASSEMBLY LIMITACIONES Emscripten posee las siguientes limitaciones: • Emscripten no soporta múltiples hilos de ejecución (threads) aunque sus desarrolladores están trabajando para sı́ soportarlos. Mientras tanto, sı́ provee facilidades para utilizar Web Workers[11]. Los Web Workers, aunque mucho más restrictivos que los hilos, al no permitir acceso a recursos compartidos entre workers, logran que la paralelización de tareas se haga de manera mucho más simple y eficiente (no hay necesidad de semáforos y por tanto no existe la posibilidad de dead-locks, condiciones de carrera o corrupción de datos compartidos). • Su soporte para manejo de audio es aún limitado. • Su soporte para acceso a redes es sólo ası́ncrono dada la naturaleza ası́ncrona de las APIs de JavaScript. Figura 4: ”Hola mundo” en Firefox • Si bien varias aplicaciones y juegos han sido portados con éxito a Emscripten, existen varias aplicaciones construı́das utilizando tecnologı́as propietarias o no estándares, que son difı́ciles o imposibles de portar hacia Emscripten. Por ejemplo, todas las aplicaciones implementadas sobre Windows usando la Win32 API, MFC ó tecnologı́as COM, no son portables a menos que se re-escriba buena parte del código que utiliza dichas tecnologı́as o se escriban librerı́as que publiquen las mismas APIs pero que utilicen tecnologı́as estándar por debajo. WineLib[10] es una librerı́a de código abierto que implementa la Win32 API. Lo mismo ocurre con aplicaciones escritas en Objective-C utilizando las librerı́as propias de Apple (Cocoa, Core Audio, Core Graphics, etc.). Si el código es guardado en un archivo hm.cpp, el comando para compilarlo usando el compilador de C++ es: g++ hm.cpp -o hm -std=c++1y g++ es el nombre del compilador de C++; -o especifica el nombre del archivo de salida y -std=c++1y especifica que se está utilizando el estándar C++14. Ese comando generará un archivo llamado hm que se puede ejecutar en un entorno Linux escribiendo: ./hm E imprimirá ”Hola mundo” en la consola. 6. LICENCIA Para compilarlo usando Emscripten, se debe escribir la siguiente lı́nea de comandos: Emscripten es un proyecto de software libre que puede ser utilizado y distribuido de acuerdo a lo estipulado por la licencia MIT[12] que permite distribuir y utilizarlo tanto en software comercial como libre siempre y cuando se distribuya con una copia de la licencia MIT y el respectivo copyright del proyecto. 7. em++ hm.cpp -o hm.js -std=c++1y Como se puede ver, la interfaz es casi la misma; em++ es el nombre del compilador Emscripten de C++. Si el archivo de salida tiene extensión .js, el compilador generará un archivo JavaScript que puede ser ejecutado usando Node.js: "HOLA MUNDO" Para ejemplificar un poco el uso de Emscripten, veremos cómo compilar y ejecutar un programa ”Hola mundo” escrito en C++14: nodejs hm.js También puede compilarse para generar un archivo HTML5 con código JavaScript incrustado usando la siguiente lı́nea de comandos: #i n c l u d e <i o s t r e a m > u s i n g namespace s t d ; em++ hm.cpp -o hm.html -std=c++1y auto main ( ) −> i n t { auto f = [ ] ( auto&& s ) { c o u t << s << e n d l ; }; f ( ” Hola mundo ”s ) ; return 0; } La figura 4 muestra la ejecución de ese archivo hm.html en una ventana de navegador. 8. WEBASSEMBLY WebAssembly[13] es un proyecto conjunto de Mozilla, Microsoft, Google y Apple cuya finalidad es la de diseñar un bytecode para JavaScript; gracias a ese bytecode, las aplicaciones en JavaScript podrán ser cargadas de manera binaria 4 12 REFERENCIAS en lugar de código fuente, eliminando ası́ los procesos de ”parseado”, compilado y optimizado de los programas y por tanto, mejorando aún más el rendimiento de una aplicación nativa corriendo en el navegador. Dado que Emscripten usa toda la infraestructura de LLVM, serı́a posible utilizar frontends de Haskell, Rust ó D por ejemplo, para compilar aplicaciones existentes en dichos lenguajes a Asm.js. 9. Aún existen varias cosas que deben ser implementadas y mejoradas, pero el futuro de esta tecnologı́a se ve brillante. RELACIÓN CON NACL Y PNACL NaCl (Native Client) y PNaCl (Portable Native Client) son tecnologı́as de Google implementadas en Chrome que permiten ejecutar código nativo dentro de dicho navegador. Aunque la finalidad es la misma que la de Emscripten, presenta varias diferencias: 12. • NaCl y PNaCl presentan una API que el programador debe utilizar al implementar o portar su aplicación C++ para que pueda ejecutarse dentro de Google Chrome. Emscripten permite portar la aplicación sin modificación alguna. • Ya que Chrome ejecuta el código nativo, el rendimiento de una aplicación implementada usando su tecnologı́a deberı́a ser mejor que la de Emscripten. • NaCl y PNaCl son tecnologı́as especı́ficas a Google Chrome y ninguna otra empresa las ha implementado en sus navegadores, convirtiéndose en una tecnologı́a privativa a Chrome. Emscripten, por el contrario, utiliza Asm.js que es un subconjunto de JavaScript; donde está implementado (Firefox, Chrome y Edge) permite la ejecución muy óptima y eficiente de la aplicación compilada a Asm.js; y donde no, permite la ejecución de la aplicación con un rendimiento inferior. Por tanto Emscripten es una tecnologı́a mucho más universal porque está construı́da sobre tecnologı́a ya existente. • NaCl y PNaCl replican hasta cierto punto tecnologı́as como Microsoft ActiveX que limitaron el progreso de las tecnologı́as web. 10. USOS NOTABLES • Los motores de juego Unity3D, Godot Game Engine y Unreal Engine proveen opciones para exportar sus resultados en HTML5 y para ello utilizan Emscripten como backend. • En diciembre de 2014, el Internet Archive lanzó un emulador DOSBox compilado en Emscripten que provee acceso a miles de juegos desarrollados para PC y MS-DOS.[14] 11. CONCLUSIONES Emscripten es una plataforma que ya permite que muchas aplicaciones nativas sean fácilmente portadas a la web. Después de su aparición y de haber demostrado su factibilidad, WebAssembly se presenta como el paso siguiente hacia la ejecución de aplicaciones nativas sobre la web. Implementar un front-end en Emscripten que genere bytecodes de WebAssembly en lugar de Asm.js deberı́a ser una tarea relativamente sencilla dada toda la infraestructura ya existente. Es posible que aparezcan frameworks y librerı́as escritas en C++ orientadas al desarrollo de aplicaciones nativas para Emscripten. 5 [1] [2] [3] [4] REFERENCIAS BIBLIOGRÁFICAS http://emscripten.org 1 http://en.wikipedia.org/wiki/LLVM 1 http://asmjs.org/spec/latest 2 http://ejohn.org/blog/asmjs-javascript-compile-target/ 2 [5] http://clang.llvm.org 3 [6] http://www.libsdl.org 3 [7] https://www.khronos.org/webgl/ 3 [8] https://github.com/emscripten-ports 3 [9] http://kripken.github.io/emscriptensite/docs/porting/connecting cpp and javascript/Interactingwith-code.html 3 [10] http://wiki.winehq.org/Winelib 4 [11] https://w3c.github.io/workers/ 4 [12] http://opensource.org/licenses/MIT 4 [13] https://www.w3.org/community/webassembly/ 5 [14] https://github.com/jsmess/jsmess 5