Fortran ejecutándose en WebAssembly
(gws.phd)- Para llevar código numérico Fortran heredado de ciencia e ingeniería al navegador, hace falta una ruta de compilación a WebAssembly, y webR usa un
flang-newparcheado de LLVM f2c, LFortran, Dragonegg, Classic Flang y LLVM Flang tienen limitaciones respectivamente en soporte de Fortran moderno, arquitecturas objetivo y mantenibilidad, así que todavía no existe una solución estándar simple- LLVM Flang no soporta directamente el objetivo
wasm32-unknown-emscripten, por lo que hace falta agregarTargetWasm32, y al enlazar el runtime aparece un problema por la diferencia en el tamaño delongentre host y objetivo - Combinando
flang-newparcheado, Emscripten y bibliotecas estáticas del runtime de Fortran, se pueden llamar subrutinas Fortran desde C o JavaScript y también manejarPRINT,ALLOCATEy argumentosCHARACTER - Las implementaciones de referencia de BLAS 3.12.0 y LAPACK 3.12.0 se compilaron como bibliotecas estáticas para WebAssembly, y se ejecutaron en el navegador demos de clasificación de dígitos escritos a mano e interpolación polinómica
Llevar código numérico Fortran existente al navegador
- Fortran es un lenguaje antiguo que apareció en 1957, pero se ha usado durante mucho tiempo en cálculo científico e ingenieril, y Fortran moderno ya se apartó en gran medida de las restricciones de formato fijo de Fortran 77
- El objetivo es compilar rutinas modernas de Fortran a WebAssembly para ejecutarlas en el navegador, recibir argumentos numéricos, ejecutar rutinas de BLAS y LAPACK, y luego devolver resultados o imprimirlos en la consola
- Este enfoque permite llevar a la web entornos de programación de más alto nivel que dependen de BLAS y LAPACK, como SciPy o R
- En lugar de reescribir rutinas numéricas en JavaScript o Rust, se pueden aprovechar herramientas y bibliotecas ya validadas basadas en Fortran
- El proyecto webR compila código Fortran a WebAssembly usando el compilador parcheado
flang-newde LLVM - El método actual depende de hacks, y sin ayuda de desarrolladores de compiladores con más experiencia es difícil contribuir estos cambios a LLVM
Estado de las herramientas Fortran→WebAssembly
- A 2024 existen varias herramientas y toolchains, pero todavía no hay una solución simple con funcionalidad completa
-
f2cf2cconvierte Fortran 77 a código C, y Emscripten luego puede compilarlo a WebAssembly- Pyodide usa este método al compilar paquetes de Python que incluyen código Fortran
- Incluso en la hoja de ruta de Pyodide, este enfoque se evalúa como “no funciona bien”
- No encaja con código Fortran moderno, y aun después de la conversión requiere errores graves corregidos y parches extensivos
-
LFortran
- LFortran ha ampliado bastante sus capacidades en los últimos años y puede compilar directamente a WebAssembly
- Sus desarrolladores señalan que sigue en fase alfa, así que se esperan problemas al compilar código real
- Algunos proyectos como MINPACK sí se pueden compilar, pero como no soporta toda la especificación de Fortran, proyectos más grandes pueden fallar
- Su meta de desarrollo es soportar completamente Fortran 2018, y una de sus funciones más destacadas es un REPL interactivo de Fortran similar a Jupyter
-
Dragonegg
- Dragonegg es un plugin de GCC que usa el frontend de GCC para exportar LLVM IR
- Puede generar salida WebAssembly mediante el backend de LLVM, y así fue como webR compiló por primera vez código fuente Fortran a WebAssembly
- Sus últimas versiones soportadas son
gcc-4.8yllvm-3.3, así que requiere GCC y LLVM muy antiguos - La mayoría de usuarios necesita una VM o un contenedor Docker, y además el LLVM IR exportado por Dragonegg requiere postprocesamiento adicional para producir salida WebAssembly
- En 2020, en la práctica, casi era la única forma de compilar código Fortran a WebAssembly
-
Classic Flang
- Classic Flang es un compilador Fortran orientado a LLVM basado en el
pgfortrande PGI/NVIDIA liberado como código abierto - No soporta salida de 32 bits, así que no se puede usar para el objetivo
wasm32 - Firefox, Chrome y Node soportan
wasm64al momento de escribir esto, pero está oculto detrás de flags de funcionalidad - La documentación del proyecto también indica que quizá no sea buena idea elegir Classic Flang para proyectos nuevos
- Classic Flang es un compilador Fortran orientado a LLVM basado en el
-
LLVM Flang
- LLVM Flang es un proyecto que reimplementa desde cero un frontend Fortran para LLVM, y forma parte del proyecto LLVM desde LLVM 11
- Todavía no se considera listo para producción, pero la versión preproducción de
flang-newya es bastante utilizable para compilar código Fortran real - En su estado predeterminado no puede generar salida WebAssembly
- Aprovechando el diseño modular de LLVM, se puede usar el frontend de Flang junto con el backend de WebAssembly de LLVM
- Ya era posible en 2020, pero requería parches más grandes a LLVM, inyección de rutinas matemáticas personalizadas y un proceso de compilación de múltiples etapas
- Ahora, gracias al desarrollo del frontend
flang-new, se puede construir un compilador Fortran→WebAssembly con cambios pequeños en el código fuente de LLVM
Compilar LLVM Flang para WebAssembly
- El LLVM instalado con un gestor de paquetes puede no incluir el binario
flang-new- Por ejemplo, en LLVM v17.0.6 de Homebrew para macOS no se encuentra el comando
flang-new
- Por ejemplo, en LLVM v17.0.6 de Homebrew para macOS no se encuentra el comando
- Como hay que modificar el código fuente de LLVM Flang, se compiló directamente el código fuente de LLVM v18.1.1
- La configuración de CMake deja el triple objetivo por defecto como
wasm32-unknown-emscripteny habilita el objetivoWebAssemblyy los proyectosclang;flang;mlir - Al terminar la compilación,
build/bin/flang-new --versionpermite verificar la siguiente información- Versión:
flang-new version 18.1.1 - Objetivo:
wasm32-unknown-emscripten - Modelo de hilos:
posix
- Versión:
Primer problema: el objetivo wasm32 no está implementado
- Al compilar una subrutina Fortran simple
foo.f08conflang-new, aparece el siguiente errornot yet implemented: target not implemented
- La causa es que el triple objetivo
wasm32-unknown-emscriptentodavía no está implementado enflang-new - La solución es un parche que agrega las características del objetivo
TargetWasm32enflang/lib/Optimizer/CodeGen/Target.cpp- Configura el ancho por defecto en 32
- Define el marshaling para convertir argumentos y tipos de retorno complejos a tipos LLVM del lado de WebAssembly
- Conecta
TargetWasm32en la ramallvm::Triple::ArchType::wasm32
- Después de aplicar el parche y recompilar, el código fuente Fortran se compila como objeto WebAssembly
- Resultado de
file foo.o:WebAssembly (wasm) binary module version 0x1 (MVP) - En
llvm-nm foo.ose puede verificar el símbolofoo_
- Resultado de
Llamar subrutinas Fortran desde C y JavaScript
- Las rutinas Fortran normalmente pasan argumentos por referencia, y
INTENT()puede declarar cómo se usa cada argumento - La subrutina Fortran de ejemplo
foorecibe argumentos enterosx,y,zy realizaz = x + y - En una compilación nativa, al ejecutar
gfortran -c foo.f08 -o foo.o, el nombre del símbolo puede llevar un guion bajo al final, comofoo_ - Al llamarlo desde C, el símbolo externo se declara como
extern void foo_(int*, int*, int*);y los argumentos se pasan por dirección - En WebAssembly,
foo.ogenerado conflang-newse puede enlazar con código C usando Emscriptenemcc main.c foo.o -o main.js- Salida de
node main.js:1 + 1 = 2
-
Llamada directa desde JavaScript
- También se puede llamar una subrutina Fortran directamente desde JavaScript sin código C intermedio
- En la etapa de enlace con Emscripten se exportan
_foo_,_mallocy_free emcc foo.o -sEXPORTED_FUNCTIONS=_foo_,_malloc,_free -o foo.js- JavaScript asigna memoria para enteros con
Module._malloc(), escribe valores enModule.HEAPU32y luego llamaModule._foo_(x, y, z) - El resultado de ejecución es el siguiente
x = 123y = 456x + y = 579- En el navegador, cargando
foo.jsystandalone.jsdesde HTML, se puede ver el mismo resultado en la consola de JavaScript
Segundo problema: la biblioteca runtime de Fortran
- Al compilar una subrutina Fortran con
PRINT *, "Hello, World!", en la etapa de enlace aparecen errores por símbolos runtime faltantes_FortranAioBeginExternalListOutput_FortranAioOutputAscii_FortranAioEndIoStatement
- La causa es que la biblioteca runtime de LLVM Fortran todavía no se había compilado para WebAssembly
- La biblioteca runtime está escrita en C++ dentro de
llvm-project/flang/runtimeen el árbol de código fuente de LLVM - Si se compilan las fuentes runtime con
em++yemarde Emscripten, se puede crear la biblioteca estáticabuild/flang/runtime/libFortranRuntime.a - Al enlazar esa biblioteca, la compilación de
Hello, World!avanza, pero al principio aparecen advertencias por incompatibilidad de firmas de funciones
Tercer problema: diferencia en el tamaño de long entre host y objetivo
- Al enlazar
hello.ocon la biblioteca runtime de Fortran, aparece una advertencia de incompatibilidad de firma para_FortranAioOutputAscii- Del lado del objeto Fortran se espera
(i32, i32, i64) -> i32 - Del lado del runtime compilado con Emscripten se define como
(i32, i32, i32) -> i32
- Del lado del objeto Fortran se espera
- En WebAssembly, los tipos de argumentos y retorno de símbolos definidos en múltiples unidades de compilación deben ser consistentes
- Este problema no se queda en advertencia: al ejecutar en Node falla con
RuntimeError: unreachable - En
RTBuilder.hde LLVM Flang hay un comentario TODO que indica que el uso desizeofasumebuild == host == target - En hosts Unix modernos de 64 bits,
sizeof(long)es de 8 bytes, pero en el objetivowasm32-unknown-emscriptendebería ser de 4 bytes - Al pasar argumentos de tipo
CHARACTERde Fortran a funciones o subrutinas, puede agregarse un argumento oculto para transmitir la longitud de la cadena - En la biblioteca runtime de Fortran, este argumento de longitud se declara como
size_t, que a través de una cadena detypedeftermina siendo equivalente aunsigned long - Esa diferencia de tamaño en el argumento oculto causa la incompatibilidad entre
i64ei32
Parche temporal: forzar valores de 4 bytes
- La solución ideal sería que
flang-newemitai32oi64de acuerdo con la arquitectura objetivo y el modelo de datos, sin depender del host al hacer compilación cruzada - Por ahora se usa un parche que fija por fuerza el tamaño de
longen 4 bytes para ajustarse awasm32y Emscripten - El parche tiene dos partes
- En
RTBuilder.h, los tipos modelo paralongyunsigned longse fuerzan a8 * 4en lugar de8 * sizeof(...) - En
CodeGen.cpp, los argumentos de llamadas amalloc()se generan como enteros de 32 bits en vez de 64 bits, y el tamaño de asignación se castea ai32
- En
- Este cambio también corrige la asignación dinámica basada en
ALLOCATE()introducida en Fortran 90 - Tras recompilar, al enlazar
hello.f08yhello.ccon la biblioteca runtime, la compilación ya no muestra advertencias y en Node aparece la siguiente salidaHello, World!
Compilar BLAS para WebAssembly
- BLAS es un conjunto de rutinas de bajo nivel para operaciones comunes de álgebra lineal, como multiplicación de matrices y vectores
- Las rutinas originales de BLAS se publicaron en 1979 y son un estándar de facto en computación numérica
- La implementación de referencia BLAS 3.12.0 está escrita en Fortran 90 y puede obtenerse desde netlib
- En
make.incse configuran las siguientes herramientas para compilarFC = ../build/bin/flang-newFFLAGS = -O2AR = emarRANLIB = emranlib
- El resultado de compilación es la biblioteca estática
blas_LINUX.a - La rutina Fortran de ejemplo
barllama a la rutina BLAS de nivel 2ZGEMV() ZGEMV()realiza una operación matriz-vector compleja, y el ejemplo usa argumentosCOMPLEX(KIND=8)y el argumento de configuraciónCHARACTER'N'- Un programa en C crea arreglos complejos, los pasa a la rutina Fortran e imprime el resultado
- La salida al ejecutar en Node es la siguiente
Y[0]: 23.000000 + 6.000000iY[1]: 18.000000 + 10.000000iY[2]: 6.000000 + 16.000000i
- Esto confirma que BLAS compilado desde código fuente Fortran 90 se ejecuta sobre WebAssembly
Ejemplo en navegador: clasificador de dígitos escritos a mano
- La demo clasifica dígitos del 0 al 9 dibujados a mano usando una red neuronal artificial perceptrón multicapa (MLP)
- El usuario puede dibujar un número con mouse o pantalla táctil, y las probabilidades relativas predichas por la red se muestran en la gráfica de la derecha
- Los pesos del modelo se entrenaron previamente en Python, pero la clasificación se realiza en tiempo de ejecución en el navegador con JavaScript y WebAssembly
- El proceso de clasificación del MLP consiste esencialmente en repeticiones de sumas y multiplicaciones matriz-vector
- El cálculo pesado lo realiza una sola subrutina Fortran usando la rutina BLAS de nivel 2
DGEMV()
Compilar LAPACK para WebAssembly
- LAPACK es una biblioteca de software para resolver numéricamente problemas de álgebra lineal, construida sobre BLAS
- La implementación de referencia LAPACK 3.12.0 la ofrece netlib y se distribuye bajo una licencia BSD modificada
- Después de copiar
make.inc.exampleamake.inc, se cambian las siguientes opcionesFCse define con la ruta completa alflang-newcompiladoFFLAGS = -O2AR = emarRANLIB = emranlibTIMER = INT_CPU_TIME
- El comando
make libgenera la biblioteca estática para WebAssemblyliblapack.a - Luego las rutinas de LAPACK pueden llamarse de forma similar al ejemplo de BLAS
Ejemplo en navegador: interpolación polinómica con álgebra lineal
- La demo encuentra el polinomio interpolante para un conjunto de puntos y muestra que las rutinas de LAPACK se ejecutan en el navegador
- Cuando el usuario hace clic en la gráfica para agregar un nuevo punto, se encuentra el polinomio interpolante que pasa por todos los puntos
- El método usa Vandermonde’s method
- El sistema de álgebra lineal obtenido con este enfoque se resuelve numéricamente con la rutina
DGELS()de LAPACK - Siempre es posible encontrar un polinomio de grado
n-1que incluya exactamentenpuntos de datos - Cuando
ncrece, puede aparecer el fenómeno de Runge, en el que el polinomio oscila fuertemente entre puntos de datos consecutivos, y eso puede evitarse con interpolación por splines
Tareas pendientes y herramientas disponibles
- Con LLVM Flang parcheado, es posible compilar código moderno de Fortran a objetos WebAssembly
- La ventaja de este enfoque es que permite usar herramientas y bibliotecas Fortran ya existentes, sin reescribir algoritmos numéricos para la web en JavaScript
- Si
flang-newsoporta oficialmente WebAssembly, se reducirá la carga de mantener un fork de LLVM para webR y los paquetes de R - Por ahora todavía hace falta una mejor ruta para corregir correctamente los problemas de compilación cruzada en LLVM Flang para todos los objetivos
- Para usuarios que no pueden o no quieren compilar LLVM Flang directamente, se ofrece en GitHub Container Registry un contenedor Docker binario de LLVM Flang parcheado
1 comentarios
Opiniones de Hacker News
Para dar un poco más de contexto, esta exploración de Fortran forma parte del excelente trabajo de WebR que George está haciendo para ejecutar R en el navegador.
El código fuente de R tiene bastante código Fortran y, según entiendo, WebR originalmente compilaba Fortran a C primero con f2c, y luego compilaba ese C a wasm.
Gracias al parche de LLVM Flang, ahora se puede compilar WebR con un compilador Fortran de verdad.
George no lo dijo directamente en la entrada del blog, pero ha dicho que espera que Flang acepte este parche o lo implemente de una mejor manera.
Eso eliminaría la necesidad de mantener un parche aparte, y permitiría que Flang sin modificaciones compile a wasm, lo que también beneficiaría a otros proyectos que usan Fortran.
https://docs.r-wasm.org/webr/latest/
Pero yo estoy enfocado en el trabajo necesario para terminar el desarrollo del producto Fortran de Nvidia, así que no me queda tiempo para esto.
Hace 20 años trabajé en compilación de FORTRAN en Xilinx, y lo único que recuerdo es que el archivo de encabezado f2c.h tenía una definición de
barf./* f2c.h -- Standard Fortran to C header file //* barf [ba:rf] 2. "He suggested using FORTRAN, and everybody barfed."(https://www.netlib.org/clapack/f2c.h)
Me gusta mucho que se use el ejemplo no trivial más simple como forma de explicación.
Como el artículo se basa en un problema concreto, “llamar funciones BLAS desde JavaScript”, siento que aprendí mucho; es un gran artículo.
[1] https://en.m.wikipedia.org/wiki/The_Theoretical_Minimum
No sé si estar maravillado u horrorizado; probablemente ambas cosas.
Al compilar f18, recomiendo usar el código fuente más reciente de llvm-project/main.
El proyecto avanza rápido, así que se pierde tiempo si uno depura problemas que ya fueron corregidos o se pierde funciones que ya fueron implementadas.
¿Se trata de trabajar en el port a WebAssembly para llevar el código intermedio hasta el punto en que funcione con Fortran?
No sé mucho de desarrollo con WebAssembly.
Desde el punto de vista del consumidor, ¿WebAssembly ofrece algo ahora mismo? ¿O es más bien trabajo de base para un futuro en el que los programas sean verdaderamente portables?
He escuchado que los dispositivos WebAssembly facilitan limitar accesos como red o archivos, pero no sé si eso es teórico o si ya está implementado.
La diferencia clave es que Wasm en sí no tiene biblioteca estándar ni expone funciones de entrada/salida.
Por eso, como host, puedes crear la VM tú mismo y exponer funciones para que el binario Wasm las importe, y el binario Wasm solo puede acceder al mundo exterior a través de esas funciones.
Otra ventaja es que el formato binario no es propietario y tiene una especificación, así que cualquiera puede implementar una VM de Wasm.
Dicho eso, por ahora todavía no diría que esté en buen estado: es demasiado temprano, hay muchas funciones nuevas en estandarización por grupos como el W3C, y el proceso es muy lento.
También hay formas de distribuir o hacer compilación cruzada para la mayoría de los objetivos.
Es parecido a cómo normalmente no sabes ni te importa mucho si la CPU de tu computadora es ARM o x86.
Así que, si no te interesan detalles como si corre como código nativo o sobre una VM como JVM, .NET o WASM, es difícil decir qué ofrece por encima de otras soluciones.
Normalmente solo se notan los malos casos, y eso se convierte en memes como “todos los programas Electron son monstruos inflados que devoran recursos, y todas las apps nativas son automáticamente maravillas de ingeniería de software eficiente”.
Qué lástima no haber guardado el código Fortran 78 que escribí en 1981/82; habría podido probar si corre aquí.
Era un formateador de código fuente para el lenguaje de programación Jovial, y no era algo que tuviera sentido hacer en Fortran, pero en ese momento era la única opción.
¿Existe algún ecosistema de álgebra lineal listo para producción, o al menos bastante listo, que se pueda usar desde JavaScript?
Al buscar, casi siempre aparecen ports a JavaScript de bibliotecas conocidas de hace unos 10 años, por ejemplo ports vía emscripten, y me pregunto si me estoy perdiendo algo.
Es raro que no se profundice más en LFortran
También tiene un ejemplo WASM excelente y sorprendente que corre en línea
https://dev.lfortran.org/
En 2020 le faltaban muchas funciones y solo soportaba un subconjunto muy pequeño de Fortran, pero ahora soporta un conjunto mucho más amplio de características del lenguaje y puede compilar bastante código Fortran
También puede compilar de forma nativa a WebAssembly
Sin embargo, todavía hay aspectos poco pulidos para usar LFortran, y los desarrolladores afirman que el proyecto actualmente se considera en etapa alfa y que pueden surgir problemas al compilar código real
Algunos proyectos, como MINPACK, se pueden compilar con éxito, pero como aún no soporta toda la especificación de Fortran, muchos proyectos más grandes todavía no se pueden compilar
Los desarrolladores de LFortran apuntan a soporte completo de Fortran 2018, y una característica destacada es un REPL interactivo de Fortran al estilo de Jupyter
Con unos años más de desarrollo, parece que será una excelente opción para compilar código Fortran para WebAssembly
Además, se recomienda ver la demo de LFortran en https://dev.lfortran.org, que es muy impresionante, aunque mi primer intento de cambiar
x * 2porx * 3no fue soportado por el generador de código actualTambién existe Fortran sobre .NET y Java
https://www.silverfrost.com/14/ftn95/ftn95_fortran_95_for_microsoft_dotnet_features.aspx
https://dl.acm.org/doi/10.1145/376656.376833
Cuando trabajé en https://medium.com/@tomasreimers/compiling-tensorflow-for-the-browser-f3387b8e1e1c, pensé que fue una gran suerte que TensorFlow usara Eigen y no BLAS/Lapack, la popular biblioteca matemática escrita en Fortran
De lo contrario, habría sido mucho más trabajo