
Binaryen es un compilador y una biblioteca de infraestructura de cadena de herramientas para WebAssembly, escrita en C ++. Su objetivo es hacer que la compilación de websembly sea fácil, rápida y efectiva :
Fácil : Binaryen tiene una API C simple en un solo encabezado, y también se puede usar de JavaScript. Acepta la entrada en forma similar a WebAsmbly, pero también acepta un gráfico de flujo de control general para compiladores que prefieren eso.
Rápido : el IR interno de Binaryen utiliza estructuras de datos compactos y está diseñado para Codegen y optimización completamente paralelos, utilizando todos los núcleos de CPU disponibles. El IR de Binaryen también se compila hasta WebAssembly de manera extremadamente fácil y rápida porque es esencialmente un subconjunto de WebAsmbly.
Efectivo : el optimizador de Binaryen tiene muchos pases (ver una descripción general más tarde) que puede mejorar el tamaño y la velocidad del código. Estas optimizaciones tienen como objetivo hacer que Binaryen sea lo suficientemente potente como para ser utilizado como un backend compilador por sí mismo. Un área específica de enfoque está en las optimizaciones específicas de websembly (que los compiladores de propósito general podrían no hacer), que se puede considerar como una minificación WASM, similar a la minificación para JavaScript, CSS, etc., todos los cuales son específicos del lenguaje.
Las cadenas de herramientas que usan binarias como componente (generalmente ejecutan wasm-opt ) incluyen:
Emscripten (C/C ++)wasm-pack (óxido)J2CL (Java; J2Wasm )Kotlin (Kotlin/Wasm)Dart (Flutter)wasm_of_ocaml (OCAML)Para obtener más información sobre cómo funcionan algunos de esos, consulte las partes de la arquitectura de la cadena de herramientas del V8 WASMGC Porting BlogPost.
Los compiladores que usan binaryen como biblioteca incluyen:
AssemblyScript que compila una variante de TypeScript a WebAssemblywasm2js que compila WebAssembly a JSAsterius que compila a Haskell a WebAssemblyGrain que compila grano en websemblyBinaryen también proporciona un conjunto de utilidades de cadena de herramientas que puede
Consulte las instrucciones que contribuyen si está interesado en participar.
El IR interno de Binaryen está diseñado para ser
Hay algunas diferencias entre Binaryen IR y el idioma WebAssembly:
catch en la función de manejo de excepciones) se representan como subexpresiones pop .--generate-stack-ir --print-stack-ir , que imprime la pila IR, esto se garantiza que es válido para los analizadores de WASM).ref.func debe estar en la tabla o declararse a través de una (elem declare func $..) . Binaryen emitirá esos datos cuando sea necesario, pero no los representa en IR. Es decir, IR se puede trabajar sin necesidad de pensar en declarar referencias de funciones.local.set hace la especificación WASM (que históricamente fue apodada "1A"), en la que un local.get . A pesar de estar alineado con la especificación WASM, hay algunos detalles menores que puede notar:Block sin nombre en Binaryen IR no interfiere con la validación. Los bloques sin nombre nunca se emiten en el formato binario (solo emitimos su contenido), por lo que los ignoramos para fines de locales no anulables. Como resultado, si lee el texto de WASM emitido por Binaryen, entonces puede ver lo que parece ser un código que no debería validar según la especificación (y puede no validar en los analizadores de texto WASM), pero esa diferencia no existirá en el formato binario (los binarios emitidos por Binaryen siempre funcionarán en todas partes, aparte de los errores por el curso).requiresNonNullableLocalFixups() en pass.h y la clase LocalStructuralDominance .ref.func es siempre un tipo de función específico, y no de funcref simple. También no es anulable.try_table en las ramas (si la rama, nunca se envía un NULL), es decir, envía (Ref Exn) y no (Ref Null EXN). En ambos casos, si GC no está habilitado, emitemos el tipo menos refinado en el binario. Al leer un binario, los tipos más refinados se aplicarán a medida que construimos el IR.br_if son más refinados en binarios IR: tienen el tipo de valor, cuando un valor fluye. En la especificación de WASM, el tipo es el del objetivo de la rama, que puede ser menos refinado. El uso del tipo más refinado aquí asegura que optimizemos de la mejor manera posible, usando toda la información de tipo, pero sí significa que algunas operaciones de ida y vuelta pueden parecer un poco diferentes. En particular, cuando emitimos un br_if cuyo tipo es más refinado en Binaryen IR, emitimos un elenco justo después de él, de modo que la salida tenga el tipo correcto en la especificación WASM. Eso puede causar algunos bytes de tamaño adicional en casos raros (evitamos esta sobrecarga en el caso común donde el valor br_if no se usa).stringview_wtf16 etc.) sean emitidas usando ref.cast . Esto simplifica el IR, ya que permite que ref.cast se use siempre en todos los lugares (y se baja para ref.as_non_null cuando sea posible en el optimizador). Sin embargo, la especificación StringRef no parece permitir esto, y para solucionar que el escritor binario reemplazará ref.cast que emite una vista de cadena a un tipo no anulable para ref.as_non_null . Un ref.cast de una vista de cadena que es un OP no-op se omite por completo.Como resultado, puede notar que las conversiones de ida y vuelta (wasm => binaryen ir => wasm) cambian un poco el código en algunos casos de esquina.
src/wasm-stack.h ). Stack IR permite un montón de optimizaciones que se adaptan a la forma de la máquina de pila del formato binario de WebAssembly (pero la pila IR es menos eficiente para las optimizaciones generales que el principal Binaryen IR). Si tiene un archivo WASM que ha sido particularmente bien optimizado, una conversión simple de ida y vuelta (solo lea y escribe, sin optimización) puede causar diferencias más notables, ya que Binaryen lo encaja en el formato más estructurado de Binaryen IR. Si también optimiza durante la conversión de ida y vuelta, entonces se ejecutarán OPTS de Stack IR y el WASM final estará mejor optimizado.Notas Al trabajar con Binaryen IR:
Las funciones intrínsecas de binarias parecen llamadas a las importaciones, por ejemplo,
( import " binaryen-intrinsics " " foo " ( func $foo ))Implementarlos de esa manera les permite ser leídos y escritos por otras herramientas, y evita errores confusos en un error de formato binario que podría ocurrir en esas herramientas si tuviéramos una extensión de formato binario personalizado.
El optimizador puede optimizar un método intrínseco. Si no es así, debe bajar antes de enviar el WASM, ya que de lo contrario se verá como una llamada a una importación que no existe (y las máquinas virtuales mostrarán un error al no tener un valor adecuado para esa importación). Esa disminución final no se realiza automáticamente. Un usuario de intrínsecos debe ejecutar el pase para eso explícitamente, ya que las herramientas no saben cuándo el usuario tiene la intención de terminar de optimizar, ya que el usuario puede tener una tubería de pasos de optimización múltiples, o puede estar haciendo experimentación local, o bordeando/reduciendo, etc. Solo el usuario sabe cuándo ocurre la optimización final antes de que la Wasm esté "final" y listo para ser enviado. Tenga en cuenta que, en general, algunas optimizaciones adicionales pueden ser posibles después de la disminución final, por lo que un patrón útil es optimizar una vez normalmente con intrínsecos, luego reducirlas, luego optimizar después de eso, por ejemplo:
wasm-opt input.wasm -o output.wasm -O --intrinsic-lowering -OCada intrínseco define su semántica, que incluye lo que el optimizador puede hacer con él y a qué se volverá la disminución final. Ver intrínsecs.h para las definiciones detalladas. Aparece un resumen rápido aquí:
call.without.effects : Similar a un call_ref en el sentido de que recibe parámetros, y una referencia a una función para llamar, y las llamadas que funcionan con esos parámetros, excepto que el optimizador puede asumir que la llamada no tiene efectos secundarios, y puede optimizarla (si no tiene un resultado que se usa, generalmente). Este repositorio contiene código que construye las siguientes herramientas en bin/ (consulte las instrucciones de construcción):
wasm-opt : carga websembly y ejecuta binaryen IR pasa sobre él.wasm-as : ensambla WebAssembly en formato de texto (actualmente formato de expresión S) en formato binario (pasando por Binaryen IR).wasm-dis : UNS-ESSEMBLE WEBSAmbly en formato binario en formato de texto (pasando por Binaryen IR).wasm2js : un compilador WebAssembly-to-JS. EMScripten utiliza esto para generar JavaScript como una alternativa a WebAssembly.wasm-reduce : un reductor de prueba para los archivos de WebAssembly. Dado un archivo WASM que es interesante por alguna razón (por ejemplo, se bloquea una VM específica), WASM-Reduce puede encontrar un archivo WASM más pequeño que tenga la misma propiedad, que a menudo es más fácil de depurar. Vea los documentos para obtener más detalles.wasm-shell : un shell que puede cargar e interpretar el código de WebSembly. También puede ejecutar la suite de prueba de especificaciones.wasm-emscripten-finalize : toma un binario WASM producido por LLVM+LLD y realiza pases específicos de EMSCRIPTEN sobre él.wasm-ctor-eval : una herramienta que puede ejecutar funciones (o partes de las funciones) en el momento de la compilación.wasm-merge : fusiona múltiples archivos WASM en un solo archivo, conectando las importaciones correspondientes a las exportaciones como lo hace. Como un Bundler para JS, pero para WASM.wasm-metadce : una herramienta para eliminar partes de los archivos WASM de una manera flexible que depende de cómo se use el módulo.binaryen.js : una biblioteca de JavaScript independiente que expone métodos de binarios para crear y optimizar los módulos WASM. Para construcciones, consulte binaryen.js en npm (o descargarlo directamente de github o impkg). Requisitos mínimos: node.js v15.8 o Chrome v75 o Firefox v78.Todas las herramientas binarias son deterministas, es decir, dadas las mismas entradas, siempre debe obtener las mismas salidas. (Si ve un caso que se comporta de otra manera, presente un problema).
Las instrucciones de uso para cada uno están a continuación.
Binaryen contiene muchos pases de optimización para hacer que el websembly sea más pequeño y rápido. Puede ejecutar el optimizador de binarios usando wasm-opt , pero también se pueden ejecutar mientras usan otras herramientas, como wasm2js y wasm-metadce .
addDefaultFunctionOptimizationPasses .wasm-opt --help sobre cómo establecerlos y otros detalles.Consulte cada pase de optimización para obtener detalles de lo que hace, pero aquí hay una descripción general de algunos de los relevantes:
if los brazos tienen algunas instrucciones compartidas al final).block a uno externo cuando sea posible, reduciendo su número.local.set de un valor que ya está presente en un local. (Se superpone a CoalesCelocals; esto logra la operación específica que acaba de mencionar sin todo el otro trabajo lo hace CounseCelocals y, por lo tanto, es útil en otros lugares en la tubería de optimización).br o br_table (como girar un block con un br en el medio en el if cuando es posible).local.get/set/tee ", haciendo cosas como reemplazar un conjunto y una obtención de mover el valor del conjunto a la obtención (y crear un tee) cuando sea posible. También crea valores de retorno block/if/loop en lugar de usar un local para pasar el valor.if que no tiene contenido, una caída de un valor constante sin efectos secundarios, un block con un solo niño, etc."LTO" en lo anterior significa que una optimización es la optimización del tiempo de enlace similar a que funciona en múltiples funciones, pero en cierto sentido, Binaryen es siempre "LTO", ya que generalmente se ejecuta en el WASM vinculado final.
Las técnicas de optimización avanzada en el optimizador de binarios incluyen ssaificación, IR plano y pila/amapola IR.
Consulte la página Wiki de Optimizer Cookbook para obtener más información sobre cómo usar el optimizador de manera efectiva.
Binaryen también contiene varios pases que hacen otras cosas que las optimizaciones, como la legalización para JavaScript, Asincify, etc.
Binaryen usa submódulos Git (al momento de escribir solo para GTEST), por lo que antes de construir tendrá que inicializar los submódulos:
git submodule init
git submodule updateDespués de eso, puedes construir con Cmake:
cmake . && make Se requiere un compilador C ++ 17. En MacOS, debe instalar cmake , por ejemplo, a través de brew install cmake . Tenga en cuenta que también puede usar ninja como su generador: cmake -G Ninja . && ninja .
Para evitar la dependencia de GTEST, puede aprobar -DBUILD_TESTS=OFF a CMake.
Binaryen.js se puede construir con Emscripten, que se puede instalar a través del SDK.
emcmake cmake . && emmake make binaryen_jsemcmake cmake -DBUILD_FOR_BROWSER=ON . && emmake makeUsando el instalador de Microsoft Visual Studio, instale el componente "Visual C ++ Tools for CMake".
Generar los proyectos:
mkdir build
cd build
" %VISUAL_STUDIO_ROOT%Common7IDECommonExtensionsMicrosoftCMakeCMakebincmake.exe " ..Sustituya Visual_studio_Root con la ruta a su instalación de Visual Studio. En caso de que esté utilizando las herramientas de compilación de Visual Studio, la ruta será "C: Archivos de programa (x86) Microsoft Visual Studio 2017 BuildTools".
Desde el símbolo del sistema de desarrolladores, cree los proyectos deseados:
msbuild binaryen.vcxprojCMake genera un proyecto llamado "all_build.vcxproj" para construir convenientemente todos los proyectos.
Las construcciones son distribuidas por las diversas cadenas de herramientas que usan binarias, como Emscripten, wasm-pack , etc. También hay lanzamientos oficiales en GitHub:
https://github.com/webassembly/binaryen/releases
Actualmente se incluyen construcciones de las siguientes plataformas:
Linux-x86_64Linux-arm64MacOS-x86_64MacOS-arm64Windows-x86_64Node.js (experimental): un puerto de wasm-opt a JavaScript+WebAssembly. Ejecute node wasm-opt.js como un reemplazo de entrega de una construcción nativa de wasm-opt , en cualquier plataforma que Node.js se ejecute. Requiere nodo.js 18+ (para hilos Wasm Eh y Wasm). (Tenga en cuenta que esta compilación también puede ejecutarse en entornos Deno, Bun u otros entornos JavaScript+WebAssembly, pero solo se prueba en Node.js.) Correr
bin/wasm-opt [.wasm or .wat file] [options] [passes, see --help] [--help]El optimizador WASM recibe webensamblaje como entrada, y puede ejecutar la transformación que lo pasa, así como imprimirlo (antes y/o después de las transformaciones). Por ejemplo, intente
bin/wasm-opt test/lit/passes/name-types.wast -all -S -o -Eso generará uno de los casos de prueba en la suite de prueba. Para ejecutar un pase de transformación, intente
bin/wasm-opt test/lit/passes/name-types.wast --name-types -all -S -o - El name-types Pass asegura que cada tipo tenga un nombre y renombre los nombres de tipo excepcionalmente largos. Puede ver el cambio que causa la transformación comparando la salida de los dos comandos.
Es fácil agregar sus propias pasas de transformación al shell, simplemente agregar archivos .cpp a src/passes y reconstruir el shell. Por ejemplo, Código, eche un vistazo a name-types Pass.
Algunas notas más:
bin/wasm-opt --help para la lista completa de opciones y pases.--debug emitirá información de depuración. Los canales de depuración individuales (definidos en el código fuente a través de #define DEBUG_TYPE xxx ) pueden habilitarse pasándolos como lista de cadenas separadas por comas. Por ejemplo: bin/wasm-opt --debug=binary . Estos canales de depuración también se pueden habilitar a través de la variable de entorno BINARYEN_DEBUG .Correr
bin/wasm2js [input.wasm file]Esto imprimirá JavaScript en la consola.
Por ejemplo, intente
bin/wasm2js test/hello_world.watEsa salida contiene
function add ( x , y ) {
x = x | 0 ;
y = y | 0 ;
return x + y | 0 | 0 ;
}como una traducción de
( func $add (; 0 ;) ( type $0 ) ( param $x i32 ) ( param $y i32 ) ( result i32 )
( i32.add
( local.get $x )
( local.get $y )
)
)La salida de WASM2JS está en formato del módulo ES6, básicamente, convierte un módulo WASM en un módulo ES6 (para ejecutarse en navegadores y versiones de nodo más antiguos. Puede usar Babel, etc. para convertirlo en ES5). Veamos un ejemplo completo de llamar a ese Hello World Wat; Primero, cree el archivo JS principal:
// main.mjs
import { add } from "./hello_world.mjs" ;
console . log ( 'the sum of 1 and 2 is:' , add ( 1 , 2 ) ) ;Ejecutar esto (tenga en cuenta que necesita un nuevo nodo.js con soporte del módulo ES6):
$ bin/wasm2js test/hello_world.wat -o hello_world.mjs
$ node --experimental-modules main.mjs
the sum of 1 and 2 is: 3Las cosas tienen en cuenta con la salida de WASM2JS:
-O u otro nivel de optimización. Eso optimizará a lo largo de toda la tubería (WASM y JS). Sin embargo, no hará todo lo que una miniadora JS sería, como Minify Whitespace, por lo que aún debe ejecutar una miniadora JS normal después. wasm-ctor-eval ejecuta funciones, o partes de ellas, en el momento de la compilación. Después de hacerlo, serializa el estado de tiempo de ejecución en el WASM, que es como tomar una "instantánea". Cuando el WASM se carga y se ejecuta en una VM, continuará la ejecución desde ese punto, sin volver a hacer el trabajo que ya se ejecutó.
Por ejemplo, considere este pequeño programa:
( module
;; A global variable that begins at 0.
( global $global ( mut i32 ) ( i32.const 0 ))
( import " import " " import " ( func $import ))
( func " main "
;; Set the global to 1.
( global.set $global
( i32.const 1 ))
;; Call the imported function. This *cannot* be executed at
;; compile time.
( call $import )
;; We will never get to this point, since we stop at the
;; import.
( global.set $global
( i32.const 2 ))
)
)Podemos evaluar parte de él en el momento de la compilación como este:
wasm-ctor-eval input.wat --ctors=main -S -o - Esto le dice que hay una sola función que queremos ejecutar ("CTOR" es la abreviatura de "Constructor global", un nombre que proviene del código que se ejecuta antes del punto de entrada de un programa) y luego para imprimirlo como texto a stdout . El resultado es este:
trying to eval main
...partial evalling successful, but stopping since could not eval: call import: import.import
...stopping
(module
(type $none_ = > _none (func))
(import " import " " import " (func $import ))
(global $global (mut i32) (i32.const 1))
(export " main " (func $0 _0))
(func $0 _0
(call $import )
(global.set $global
(i32.const 2)
)
)
) El registro nos muestra que logramos evaluar parte de main() , pero no todo, como se esperaba: podemos evaluar el primer global.get . Tenga en cuenta cómo en la salida WASM el valor del global se ha actualizado de 0 a 1, y que se ha eliminado el primer global.get : el WASM ahora está en un estado que, cuando lo ejecutamos en una VM, continuará funcionando sin problemas desde el punto en que wasm-ctor-eval se detuvo.
En este pequeño ejemplo, acabamos de ahorrar una pequeña cantidad de trabajo. Cuánto trabajo se puede guardar depende de su programa. (Puede ayudar a hacer un cálculo puro por adelantado y dejar llamadas a las importaciones lo más tarde posible).
Tenga en cuenta que el nombre de wasm-ctor-eval está relacionado con las funciones del constructor global, como se mencionó anteriormente, pero no hay limitación de lo que puede ejecutar aquí. Cualquier exportación del WASM se puede ejecutar, si su contenido es adecuado. Por ejemplo, en emscripten wasm-ctor-eval incluso se ejecuta en main() cuando es posible.
wasm-merge combina archivos WASM juntos. Por ejemplo, imagine que tiene un proyecto que usa archivos WASM de múltiples cadenas de herramientas. Entonces puede ser útil fusionarlos a todos en un solo archivo WASM antes del envío, ya que en un solo archivo de WASM, las llamadas entre los módulos se convierten en llamadas normales dentro de un módulo, lo que les permite que se incluyan, se eliminen el código muerto, y así sucesivamente, mejorando la velocidad y el tamaño.
wasm-merge opera en archivos WASM normales. Difiere de wasm-ld en ese sentido, ya que wasm-ld opera en archivos de objetos WASM. wasm-merge puede ayudar en situaciones de múltiples toolas en las que al menos una de las cadenas de herramientas no usa archivos de objetos WASM.
Por ejemplo, imagine que tenemos estos dos archivos WASM:
;; a.wasm
( module
( import " second " " bar " ( func $second.bar ))
( export " main " ( func $func ))
( func $func
( call $second.bar )
)
) ;; b.wasm
( module
( import " outside " " log " ( func $log ( param i32 )))
( export " bar " ( func $func ))
( func $func
( call $log
( i32.const 42 )
)
)
) Los nombres de archivo en su impulso local son a.wasm y b.wasm , pero para fusionar / agrupar, digamos que el primero se conoce como "first" y el segundo como "second" . Es decir, queremos que la importación del primer módulo de "second.bar" llame a la función $func en el segundo módulo. Aquí hay un comando wasm-ferge para eso:
wasm-merge a.wasm first b.wasm second -o output.wasmLe damos el primer archivo WASM, luego su nombre, y luego el segundo archivo WASM y luego su nombre. La salida fusionada es esta:
( module
( import " outside " " log " ( func $log ( param i32 )))
( export " main " ( func $func ))
( export " bar " ( func $func_2 ))
( func $func
( call $func_2 )
)
( func $func_2
( call $log
( i32.const 42 )
)
)
) wasm-merge combinó los dos archivos en uno, fusionando sus funciones, importaciones, etc., todo mientras fijaba los conflictos de nombres y conecta las importaciones correspondientes a las exportaciones. En particular, tenga en cuenta cómo $func llama $func_2 , que es exactamente lo que queríamos: $func_2 es la función del segundo módulo (renombrado para evitar una colisión de nombres).
Tenga en cuenta que la salida WASM en este ejemplo podría beneficiarse de la optimización adicional. Primero, la llamada a $func_2 ahora se puede ingresar fácilmente, para que podamos ejecutar wasm-opt -O3 para hacerlo por nosotros. Además, es posible que no necesitemos todas las importaciones y exportaciones, para las cuales podemos ejecutar Wasm-Metadce. Un buen flujo de trabajo podría ser ejecutar wasm-merge , luego wasm-metadce , luego terminar con wasm-opt .
wasm-merge es como un Bundler para los archivos WASM, en el sentido de un "JS Bundler" pero para WASM. Es decir, con los archivos WASM anteriores, imagine que teníamos este código JS para instanciarlos y conectarlos en tiempo de ejecución:
// Compile the first module.
var first = await fetch ( "a.wasm" ) ;
first = new WebAssembly . Module ( first ) ;
// Compile the first module.
var second = await fetch ( "b.wasm" ) ;
second = new WebAssembly . Module ( second ) ;
// Instantiate the second, with a JS import.
second = new WebAssembly . Instance ( second , {
outside : {
log : ( value ) => {
console . log ( 'value:' , value ) ;
}
}
} ) ;
// Instantiate the first, importing from the second.
first = new WebAssembly . Instance ( first , {
second : second . exports
} ) ;
// Call the main function.
first . exports . main ( ) ; Lo que wasm-merge hace es básicamente lo que hace JS: conecte las importaciones a las exportaciones, resolviendo nombres utilizando los nombres de módulos que proporcionó. Es decir, al ejecutar wasm-merge estamos moviendo el trabajo de conectar los módulos desde el tiempo de ejecución hasta el tiempo de compilación. Como resultado, después de ejecutar wasm-merge necesitamos mucho menos JS para obtener el mismo resultado:
// Compile the single module.
var merged = await fetch ( "merged.wasm" ) ;
merged = new WebAssembly . Module ( merged ) ;
// Instantiate it with a JS import.
merged = new WebAssembly . Instance ( merged , {
outside : {
log : ( value ) => {
console . log ( 'value:' , value ) ;
}
}
} ) ;
// Call the main function.
merged . exports . main ( ) ;Todavía necesitamos buscar y compilar el WASM fusionado, y proporcionarle la importación JS, pero el trabajo para conectar dos módulos WASM ya no es necesario.
Por defecto, los errores wasm-merge si hay nombres de exportación superpuestos. Es decir, wasm-merge manejará automáticamente los nombres de las funciones superpuestas, etc., porque no son visibles externamente (el código todavía se comporta igual), pero si renombramos las exportaciones, el exterior necesitaría ser modificado para esperar los nuevos nombres de exportación, por lo que en su lugar, erramos en tales conflictos de nombres.
Si desea que se renombraran las exportaciones, ejecute wasm-merge con --rename-export-conflicts . Las exportaciones posteriores tendrán un sufijo adjunto para asegurarse de que no se superpongan con exportaciones anteriores. Los sufijos son deterministas, por lo que una vez que vea lo que son, puede llamarlos desde el exterior.
Otra opción es utilizar --skip-export-conflicts que simplemente omitirán las exportaciones posteriores que tienen nombres conflictivos. Por ejemplo, esto puede ser útil en el caso donde el primer módulo es el único que interactúa con el exterior y los módulos posteriores solo interactúan con el primer módulo.
wasm-merge utiliza las características de varios medicamentos y múltiples table. Es decir, si múltiples módulos de entrada tienen una memoria, entonces la salida WASM tendrá varias recuerdos y dependerá de la característica de múltiples memoria, lo que significa que las máquinas virtuales WASM mayores podrían no poder ejecutar el WASM. (Como una solución para las máquinas virtuales tan antiguas, puede ejecutar wasm-opt --multi-memory-lowering para reducir múltiples recuerdos en una sola).
./check.py (o python check.py ) ejecutará wasm-shell , wasm-opt , etc. en las casas de prueba en test/ , y verificará sus salidas.
El script check.py admite algunas opciones:
./check.py [--interpreter = /path/to/interpreter] [TEST1] [TEST2].../check.py --list-suites .emcc o nodejs en la ruta. No se ejecutarán si no se puede encontrar la herramienta, y verá una advertencia.tests/spec , en submódulos Git. Ejecutar ./check.py debe actualizarlos. Tenga en cuenta que estamos tratando de transferir gradualmente las pruebas Legacy WASM-OPT para usar lit y filecheck a medida que las modificamos. Para las pruebas de passes que salen desastes, esto se puede hacer automáticamente con scripts/port_passes_tests_to_lit.py y para pruebas de no passes que salen desastes, consulte #4779 para un ejemplo de cómo hacer un puerto manual simple.
Para las pruebas iluminadas, las expectativas de prueba (las líneas de verificación) a menudo se pueden actualizar automáticamente a medida que se realizan cambios en binaryen. Consulte scripts/update_lit_checks.py .
Las pruebas no iluminadas también se pueden actualizar automáticamente en la mayoría de los casos. Ver scripts/auto_update_tests.py .
./third_party/setup.py [mozjs | v8 | wabt | all] (o python third_party/setup.py ) instala las dependencias requeridas como el shell JS Spidermonkey, el V8 JS Shell y Wabt en third_party/ . Otros scripts los recogen automáticamente cuando se instalan.
Ejecute pip3 install -r requirements-dev.txt para obtener los requisitos para las pruebas lit . Tenga en cuenta que debe tener instalaciones de ubicación pip en su $PATH (en Linux, ~/.local/bin ).
./scripts/fuzz_opt.py [--binaryen-bin = build/bin] (o python scripts/fuzz_opt.py ) ejecutará varios modos de difuso en entradas aleatorias con pases aleatorios hasta que encuentre un posible error. Vea la página Wiki para obtener todos los detalles.
Binaryen can read and write source maps (see the -ism and -osm flags to wasm-opt ). It can also read and read source map annotations in the text format, that is,
;; @ src.cpp:100:33
( i32.const 42 ) That 42 constant is annotated as appearing in a file called src.cpp at line 100 and column 33 . Source maps and text format annotations are interchangeable, that is, they both lead to the same IR representation, so you can start with an annotated wat and have Binaryen write that to a binary + a source map file, or read a binary + source map file and print text which will contain those annotations.
The IR representation of source map info is simple: in each function we have a map of expressions to their locations. Optimization passes should update the map as relevant. Often this "just works" because the optimizer tries to reuse nodes when possible, so they keep the same debug info.
The text format annotations support a shorthand in which repeated annotations are not necessary. For example, children are tagged with the debug info of the parent, if they have no annotation of their own:
;; @ src.cpp:100:33
( i32.add
( i32.const 41 ) ;; This receives an annotation of src.cpp:100:33
;; @ src.cpp:111:44
( i32.const 1 )
)The first const will have debug info identical to the parent, because it has none specified, and generally such nesting indicates a "bundle" of instructions that all implement the same source code.
Note that text printing will not emit such repeated annotations, which can be confusing. To print out all the annotations, set BINARYEN_PRINT_FULL=1 in the environment. That will print this for the above add :
[i32] ;; @ src.cpp:100:33
( i32.add
[i32] ;; @ src.cpp:100:33
( i32.const 41 )
[i32] ;; @ src.cpp:111:44
( i32.const 1 )
) (full print mode also adds a [type] for each expression, right before the debug location).
The debug information is also propagated from an expression to its next sibling:
;; @ src.cpp:100:33
( local.set $x
( i32.const 0 )
)
( local.set $y ;; This receives an annotation of src.cpp:100:33
( i32.const 0 )
) You can prevent the propagation of debug info by explicitly mentioning that an expression has not debug info using the annotation ;;@ with nothing else:
;; @ src.cpp:100:33
( local.set $x
;; @
( i32.const 0 ) ;; This does not receive any annotation
)
;; @
( local.set $y ;; This does not receive any annotation
( i32.const 7 )
) This stops the propagatation to children and siblings as well. So, expression (i32.const 7) does not have any debug info either.
There is no shorthand in the binary format. That is, roundtripping (writing and reading) through a binary + source map should not change which expressions have debug info on them or the contents of that info.
The source maps format defines a mapping using segments , that is, if a segment starts at binary offset 10 then it applies to all instructions at that offset and until another segment begins (or the end of the input is reached). Binaryen's IR represents a mapping from expressions to locations, as mentioned, so we need to map to and from the segment-based format when writing and reading source maps.
That is mostly straightforward, but one thing we need to do is to handle the lack of debug info in between things that have it. If we have ABC where B lacks debug info, then just emitting a segment for A and C would lead A 's segment to also cover B , since in source maps segments do not have a size - rather they end when a new segment begins. To avoid B getting smeared in this manner, we emit a source maps entry to B of size 1, which just marks the binary offset it has, and without the later 3 fields of the source file, line number, and column. (This appears to be the intent of the source maps spec, and works in browsers and tools.)
Binaryen also has optional support for DWARF. This primarily just tracks the locations of expressions and rewrites the DWARF's locations accordingly; it does not handle things like re-indexing of locals, and so passes that might break DWARF are disabled by default. As a result, this mode is not suitable for a fully optimized release build, but it can be useful for local debugging.
Binaryen's name was inspired by Emscripten 's: Emscripten's name suggests it converts something into a script - specifically JavaScript - and Binaryen's suggests it converts something into a binary - specifically WebAssembly . Binaryen began as Emscripten's WebAssembly generation and optimization tool, so the name fit as it moved Emscripten from something that emitted the text-based format JavaScript (as it did from its early days) to the binary format WebAssembly (which it has done since WebAssembly launched).
"Binaryen" is pronounced in the same manner as "Targaryen".
Sí, lo hace. Here's a step-by-step tutorial on how to compile it under Windows 10 x64 with with CMake and Visual Studio 2015 . However, Visual Studio 2017 may now be required. Help would be appreciated on Windows and OS X as most of the core devs are on Linux.