
Binaryen est un compilateur et une bibliothèque d'infrastructures de chaîne d'outils pour WebAssembly, écrite en C ++. Il vise à rendre la compilation vers WebAssembly facile, rapide et efficace :
Facile : Binaryen a une API C simple dans un seul en-tête et peut également être utilisée à partir de JavaScript. Il accepte la saisie sous forme de type WebAssembly mais accepte également un graphique de flux de contrôle général pour les compilateurs qui le préfèrent.
Rapide : l'IR interne de Binaryen utilise des structures de données compactes et est conçue pour Codegen et optimisation entièrement parallèles, en utilisant tous les noyaux de processeur disponibles. L'IR de Binaryen se compile également à WebAssembly extrêmement facilement et rapidement car il s'agit essentiellement d'un sous-ensemble de WebAssembly.
Efficace : l'optimiseur de Binaryen a de nombreux passes (voir un aperçu plus tard) qui peut améliorer la taille et la vitesse du code. Ces optimisations visent à rendre Binaryen suffisamment puissant pour être utilisé comme backend de compilateur en soi. Un domaine spécifique est sur les optimisations spécifiques à WebAssembly (que les compilateurs à usage général pourraient ne pas faire), que vous pouvez considérer comme une minification WASM, similaire à la minification pour JavaScript, CSS, etc., qui sont toutes spécifiques à la langue.
Les chaînes d'outils utilisant Binaryen comme composant (généralement l'exécution wasm-opt ) incluent:
Emscripten (C / C ++)wasm-pack (rouille)J2CL (Java; J2Wasm )Kotlin (Kotlin / wasm)Dart (flottement)wasm_of_ocaml (OCAML)Pour en savoir plus sur le fonctionnement de certains de ces fonctions, consultez les parties d'architecture de la chaîne d'outils du blog de Blog Porting V8 WasMGC.
Les compilateurs utilisant Binaryen comme bibliothèque comprennent:
AssemblyScript qui compile une variante de dactylographie à webassemblywasm2js qui compile webassembly à jsAsterius qui compile Haskell à webassemblyGrain qui compile le grain à webassemblyBinaryen fournit également un ensemble d' utilitaires de chaîne d'outils qui peuvent
Consultez les instructions contributives si vous êtes intéressé à participer.
L'IR interne de Binaryen est conçu pour être
Il existe quelques différences entre Binaryen IR et le langage WebAssembly:
catch dans la fonction de gestion des exceptions) sont représentées sous forme de sous-expressions pop .--generate-stack-ir --print-stack-ir , qui imprime la pile IR, cela est garanti pour être valide pour les analyseurs WasM.)ref.func doit être soit dans le tableau, soit déclarée via un (elem declare func $..) . Binaryen émettra ces données si nécessaire, mais elle ne les représente pas dans l'IR. Autrement dit, IR peut être travaillé sans avoir besoin de penser à déclarer les références de fonction.local.get doit être structurellement dominé par un local.set En dépit d'être aligné sur la spécification WASM, il y a quelques détails mineurs que vous pouvez remarquer:Block sans nom dans Binaryen IR n'interfère pas avec la validation. Les blocs sans nom ne sont jamais émis dans le format binaire (nous émettons simplement leur contenu), nous les ignorons donc aux fins des habitants non nullables. En conséquence, si vous lisez le texte WASM émis par Binaryen, vous pouvez voir ce qui semble être un code qui ne devrait pas valider selon la spécification (et peut ne pas valider dans les analyseurs de texte WASM), mais cette différence n'existera pas dans le format binaire (binaires émis par Binaryen fonctionnera toujours partout, mis à part pour des bugs de cours).requiresNonNullableLocalFixups() dans pass.h et la classe LocalStructuralDominance .ref.func est toujours un type de fonction spécifique, et pas funcref simple. Il n'est pas non plus nulle.try_table envoie sur les branches (si nous branchons, un null n'est jamais envoyé), c'est-à-dire qu'il envoie (refce exn) et non (refnter null exn). Dans les deux cas, si GC n'est pas activé, nous émettons le type moins raffiné dans le binaire. Lors de la lecture d'un binaire, les types les plus raffinés seront appliqués à mesure que nous construisons l'IR.br_if sont plus raffinés dans Binaryen IR: ils ont le type de valeur, lorsqu'une valeur circule. Dans la spécification WasM, le type est celui de la cible de branche, qui peut être moins affiné. L'utilisation du type plus raffiné ici garantit que nous optimions de la meilleure façon possible, en utilisant toutes les informations de type, mais cela signifie que certaines opérations de randonnée peuvent être un peu différentes. En particulier, lorsque nous émettons un br_if dont le type est plus raffiné dans Binaryen IR, nous émettons un casting juste après, de sorte que la sortie a le bon type dans la spécification WASM. Cela peut provoquer quelques octets de taille supplémentaire dans de rares cas (nous évitons cette surcharge dans le cas commun où la valeur br_if n'est pas utilisée).stringview_wtf16 etc.) à l'aide de ref.cast . Cela simplifie l'IR, car il permet à ref.cast d'être toujours utilisé à tous les endroits (et il est abaissé pour ref.as_non_null dans la mesure du possible dans l'optimiseur). La spécification StringRef ne semble cependant pas permettre cela et résoudre que l'écrivain binaire remplacera ref.cast qui jette une vue de chaîne sur un type non nullenable à ref.as_non_null . Un ref.cast d'une vue à cordes qui est un no-op est complètement ignoré.En conséquence, vous remarquerez peut-être que les conversions aller-retour (wasm => binaryen ir => wasm) changent un peu de code dans certains cas d'angle.
src/wasm-stack.h ). Stack IR permet un tas d'optimisations qui sont adaptées à la forme de la machine de pile du format binaire de WebAssembly (mais la pile IR est moins efficace pour les optimisations générales que le principal binaryen IR). Si vous avez un fichier WASM qui a été particulièrement bien optimisé, une simple conversion aller-retour (juste lire et écrire, sans optimisation) peut provoquer des différences plus notables, car Binaryen s'adapte au format plus structuré de Binaryen IR. Si vous optimisez également lors de la conversion aller-retour, les opts de pile IR seront exécutés et le WASM final sera mieux optimisé.Notes lorsque vous travaillez avec Binaryen IR:
Les fonctions intrinsèques de Binaryen ressemblent à des appels aux importations, par exemple,
( import " binaryen-intrinsics " " foo " ( func $foo ))Les implémenter de cette façon leur permet de lire et d'écrire par d'autres outils, et cela évite de confondre les erreurs sur une erreur de format binaire qui pourrait se produire dans ces outils si nous avions une extension de format binaire personnalisé.
Une méthode intrinsèque peut être optimisée par l'optimiseur. Si ce n'est pas le cas, il doit être abaissé avant d'expédier le WASM, car sinon il ressemblera à un appel à une importation qui n'existe pas (et les VM affichent une erreur sur le fait de ne pas avoir de valeur appropriée pour cette importation). Cette baisse finale n'est pas effectuée automatiquement. Un utilisateur d'Intrinsics doit exécuter la passe pour cela explicitement, car les outils ne savent pas quand l'utilisateur a l'intention de terminer l'optimisation, car l'utilisateur peut avoir un pipeline d'étapes d'optimisation multiples, ou peut faire l'expérimentation locale, ou fuzzing / réduction, etc., seul l'utilisateur sait quand l'optimisation finale se produit avant que le WASM ne soit "final" et prêt à être décoché. Notez que, en général, certaines optimisations supplémentaires peuvent être possibles après l'abaissement final, et donc un modèle utile consiste à optimiser une fois normalement avec des intrinsèques, puis à les abaisser, puis à optimiser après cela, par exemple:
wasm-opt input.wasm -o output.wasm -O --intrinsic-lowering -OChaque intrinsèque définit sa sémantique, qui comprend ce que l'optimiseur est autorisé à en faire et à quoi l'abaissement final le tournera. Voir intrinsics.h pour les définitions détaillées. Un résumé rapide apparaît ici:
call.without.effects : similaire à un call_ref en ce qu'il reçoit des paramètres, et une référence à une fonction à appeler, et appelle cette fonction avec ces paramètres, sauf que l'optimiseur peut supposer que l'appel n'a aucun effet secondaire et peut être en mesure de l'optimiser (s'il n'a pas de résultat, généralement). Ce référentiel contient du code qui construit les outils suivants dans bin/ (voir les instructions de construction):
wasm-opt : charge WebAssembly et exécute Binaryen IR passe dessus.wasm-as : assemble WebAssembly au format texte (actuellement le format d'expression S) au format binaire (passant par Binaryen IR).wasm-dis : non-assemblage webassembly au format binaire au format texte (passant par Binaryen IR).wasm2js : un compilateur WebAssembly-to-JS. Ceci est utilisé par Emscripten pour générer JavaScript comme alternative à WebAssembly.wasm-reduce : un réducteur de testcase pour les fichiers WebAssembly. Étant donné un fichier WASM qui est intéressant pour une raison quelconque (par exemple, il plante une machine virtuelle spécifique), WASM-reduce peut trouver un fichier WASM plus petit qui a la même propriété, qui est souvent plus facile à déboguer. Voir les documents pour plus de détails.wasm-shell : un shell qui peut charger et interpréter le code WebAssembly. Il peut également exécuter la suite de tests Spec.wasm-emscripten-finalize : prend un binaire WasM produit par LLVM + LLD et effectue des passes spécifiques à Emscripten.wasm-ctor-eval : un outil qui peut exécuter des fonctions (ou des parties des fonctions) au moment de la compilation.wasm-merge : fusionne plusieurs fichiers WASM dans un seul fichier, connectant les importations correspondantes aux exportations comme elle le fait. Comme un bundler pour JS, mais pour Wasm.wasm-metadce : un outil pour supprimer les parties des fichiers WASM d'une manière flexible qui dépend de la façon dont le module est utilisé.binaryen.js : une bibliothèque JavaScript autonome qui expose les méthodes Binaryen pour créer et optimiser les modules WASM. Pour les builds, voir Binaryen.js sur NPM (ou téléchargez-le directement à partir de GitHub ou UNPKG). Exigences minimales: Node.js V15.8 ou Chrome V75 ou Firefox V78.Tous les outils Binaryen sont déterministes, c'est-à-dire, étant donné les mêmes entrées, vous devez toujours obtenir les mêmes sorties. (Si vous voyez un cas qui se comporte autrement, veuillez déposer un problème.)
Les instructions d'utilisation pour chacun sont ci-dessous.
Binaryen contient beaucoup de passes d'optimisation pour rendre WebAssembly plus petit et plus rapide. Vous pouvez exécuter l'optimiseur Binaryen en utilisant wasm-opt , mais ils peuvent également être exécutés lors de l'utilisation d'autres outils, comme wasm2js et wasm-metadce .
addDefaultFunctionOptimizationPasses .wasm-opt --help pour comment les définir et d'autres détails.Voir chaque passe d'optimisation pour plus de détails sur ce qu'il fait, mais voici un aperçu rapide de certains des pertinents:
if les bras ont des instructions partagées à leur fin).block à un extérieur dans la mesure du possible, réduisant leur numéro.local.set d'une valeur qui est déjà présente dans un local. (Chevauche des coalescelloques; cela réalise l'opération spécifique que je viens de mentionner sans tous les autres coalesceaux de travail, et est donc utile dans d'autres endroits dans le pipeline d'optimisation.)br ou br_table (comme tourner un block avec un br au milieu en un if possible).local.get/set/tee " Optimization Pass, faisant des choses comme le remplacement d'un ensemble et un objet par le déplacement de la valeur de l'ensemble vers le GET (et la création d'un tee) lorsque cela est possible. Crée également des valeurs block/if/loop Retour au lieu d'utiliser un local pour passer la valeur.if qui n'a pas de contenu, une goutte d'une valeur constante sans effets secondaires, un block avec un seul enfant, etc."LTO" dans ce qui précède signifie qu'une optimisation est une optimisation du temps de lien en ce sens qu'elle fonctionne sur plusieurs fonctions, mais en un sens, Binaryen est toujours "LTO" car il est habituellement exécuté sur le WasM lié final.
Les techniques d'optimisation avancées dans l'optimiseur Binaryen comprennent la ssaification, l'IR plate et la pile / pavot IR.
Voir la page Wiki Cookbook Optimizer pour en savoir plus sur la façon d'utiliser efficacement l'Optimizer.
Binaryen contient également divers passes qui font d'autres choses que les optimisations, comme la légalisation pour JavaScript, Asyncify, etc.
Binaryen utilise des sous-modules Git (au moment de la rédaction juste pour GTEST), donc avant de construire, vous devrez initialiser les sous-modules:
git submodule init
git submodule updateAprès cela, vous pouvez construire avec Cmake:
cmake . && make Un compilateur C ++ 17 est requis. Sur MacOS, vous devez installer cmake , par exemple, via brew install cmake . Notez que vous pouvez également utiliser ninja comme générateur: cmake -G Ninja . && ninja .
Pour éviter la dépendance GTest, vous pouvez passer -DBUILD_TESTS=OFF à cmake.
Binaryen.js peut être construit à l'aide d'Emscripten, qui peut être installé via le SDK.
emcmake cmake . && emmake make binaryen_jsemcmake cmake -DBUILD_FOR_BROWSER=ON . && emmake makeÀ l'aide du programme d'installation de Microsoft Visual Studio, installez le composant "Visual C ++ Tools for CMake".
Générez les projets:
mkdir build
cd build
" %VISUAL_STUDIO_ROOT%Common7IDECommonExtensionsMicrosoftCMakeCMakebincmake.exe " ..Remplacez Visual_studio_root avec le chemin de votre installation Visual Studio. Dans le cas où vous utilisez les outils de construction Visual Studio, le chemin sera "C: Program Files (x86) Microsoft Visual Studio 2017 BuildTools".
À partir de l'invite de commande du développeur, construisez les projets souhaités:
msbuild binaryen.vcxprojCMake génère un projet nommé "all_build.vcxproj" pour construire facilement tous les projets.
Les builds sont distribués par les différentes chaînes d'outils qui utilisent Binaryen, comme Emscripten, wasm-pack , etc. Il existe également des versions officielles sur GitHub:
https://github.com/webassembly/binaryen/releases
Actuellement, les constructions des plates-formes suivantes sont incluses:
Linux-x86_64Linux-arm64MacOS-x86_64MacOS-arm64Windows-x86_64Node.js (expérimental): un port de wasm-opt à JavaScript + WebAssembly. Exécutez node wasm-opt.js en remplacement de dépôt pour une construction native de wasm-opt , sur n'importe quelle plate-forme sur laquelle Node.js fonctionne. Nécessite Node.js 18+ (pour les threads WasM EH et WasM). (Notez que cette version peut également s'exécuter dans des environnements deno, BUN ou d'autres environnements JavaScript + WebAssembly, mais est testé uniquement sur Node.js.) Courir
bin/wasm-opt [.wasm or .wat file] [options] [passes, see --help] [--help]L'optimiseur WASM reçoit WebAssembly comme entrée et peut exécuter la transformation qui s'y passe, ainsi que l'imprimer (avant et / ou après les transformations). Par exemple, essayez
bin/wasm-opt test/lit/passes/name-types.wast -all -S -o -Qui sortira l'un des cas de test dans la suite de tests. Pour exécuter une passe de transformation dessus, essayez
bin/wasm-opt test/lit/passes/name-types.wast --name-types -all -S -o - Le PASS name-types garantit que chaque type a un nom et des noms de noms de type exceptionnellement longs. Vous pouvez voir le changement que la transformation provoque en comparant la sortie des deux commandes.
Il est facile d'ajouter vos propres passes de transformation au shell, d'ajouter simplement des fichiers .cpp en src/passes et de reconstruire le shell. Par exemple, le code, jetez un œil à la réussite des name-types .
Quelques autres notes:
bin/wasm-opt --help pour la liste complète des options et des passes.--debug émettra des informations de débogage. Les canaux de débogage individuels (définis dans le code source via #define DEBUG_TYPE xxx ) peuvent être activés en les faisant passer comme la liste des chaînes séparées par les virgules. Par exemple: bin/wasm-opt --debug=binary . Ces canaux de débogage peuvent également être activés via la variable d'environnement BINARYEN_DEBUG .Courir
bin/wasm2js [input.wasm file]Cela imprimera JavaScript à la console.
Par exemple, essayez
bin/wasm2js test/hello_world.watCette sortie contient
function add ( x , y ) {
x = x | 0 ;
y = y | 0 ;
return x + y | 0 | 0 ;
}comme traduction de
( func $add (; 0 ;) ( type $0 ) ( param $x i32 ) ( param $y i32 ) ( result i32 )
( i32.add
( local.get $x )
( local.get $y )
)
)La sortie de WasM2JS est au format du module ES6 - en gros, il convertit un module WASM en un module ES6 (pour exécuter sur des navigateurs plus anciens et des versions Node.js, vous pouvez utiliser Babel, etc. pour le convertir en ES5). Regardons un exemple complet d'appeler ce Hello World Wat; Créez d'abord le fichier JS principal:
// main.mjs
import { add } from "./hello_world.mjs" ;
console . log ( 'the sum of 1 and 2 is:' , add ( 1 , 2 ) ) ;L'exécute ceci (notez que vous avez besoin d'un nouveau Node.js suffisamment avec le support du module ES6):
$ bin/wasm2js test/hello_world.wat -o hello_world.mjs
$ node --experimental-modules main.mjs
the sum of 1 and 2 is: 3Les choses gardent à l'esprit avec la sortie de Wasm2js:
-O ou un autre niveau d'optimisation. Cela optimisera le long de tout le pipeline (WASM et JS). Il ne fera pas tout ce qu'un minifer JS serait, cependant, comme Minify Whitespace, vous devriez donc toujours exécuter un minifer JS normal par la suite. wasm-ctor-eval exécute des fonctions, ou des parties d'entre elles, au moment de la compilation. Après cela, il sérialise l'état d'exécution dans le WASM, ce qui est comme prendre un "instantané". Lorsque le WASM est plus tard chargé et exécuté dans une machine virtuelle, elle continuera l'exécution à partir de ce point, sans refaire le travail qui a déjà été exécuté.
Par exemple, considérez ce petit programme:
( 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 ))
)
)Nous pouvons évaluer une partie de celui-ci au moment de la compilation comme ceci:
wasm-ctor-eval input.wat --ctors=main -S -o - Cela indique qu'il existe une seule fonction que nous voulons exécuter ("CTOR" est court pour "Global Constructor", un nom qui provient du code qui est exécuté avant le point d'entrée d'un programme), puis de l'imprimer en texte à stdout . Le résultat est le suivant:
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)
)
)
) L'enregistrement nous montre en train de gérer une partie de main() , mais pas tout, comme prévu: nous pouvons évaluer le premier global.get , mais nous nous arrêtons ensuite à l'appel à la fonction importée (parce que nous ne savons pas quelle sera cette fonction lorsque le WASM sera réellement exécuté dans une machine virtuelle plus tard). Remarque comment dans la sortie WasM, la valeur du global a été mise à jour de 0 à 1, et que le premier global.get a été supprimé: le WASM est maintenant dans un état que, lorsque nous l'exécutons dans une machine virtuelle, continuera de manière transparente à courir à partir du point où wasm-ctor-eval s'est arrêté.
Dans ce petit exemple, nous venons d'économiser une petite quantité de travail. La quantité de travail peut être enregistrée dépend de votre programme. (Il peut aider à faire un calcul pur à l'avance et laisser les appels aux importations aussi tard que possible.)
Notez que le nom de wasm-ctor-eval est lié aux fonctions de constructeur global, comme mentionné précédemment, mais il n'y a aucune limitation à ce que vous pouvez exécuter ici. Toute exportation du WASM peut être exécutée, si son contenu convient. Par exemple, dans Emscripten wasm-ctor-eval est même exécuté sur main() lorsque cela est possible.
wasm-merge combine les fichiers WASM ensemble. Par exemple, imaginez que vous avez un projet qui utilise des fichiers WASM à partir de plusieurs chaînes d'outils. Ensuite, il peut être utile de les fusionner tous dans un seul fichier WASM avant l'expédition, car dans un seul fichier WASM, les appels entre les modules deviennent juste des appels normaux à l'intérieur d'un module, ce qui leur permet d'être inclus, le code mort éliminé, etc., améliorant potentiellement la vitesse et la taille.
wasm-merge fonctionne sur des fichiers WASM normaux. Il diffère de wasm-ld à cet égard, comme wasm-ld fonctionne sur des fichiers d'objets WASM. wasm-merge peut aider dans des situations multi-outils où au moins une des chaînes d'outils n'utilise pas de fichiers d'objet WasM.
Par exemple, imaginez que nous avons ces deux fichiers 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 )
)
)
) Les noms de fichiers sur votre lecteur local sont a.wasm et b.wasm , mais à des fins de fusion / de regroupement, disons que le premier est connu sous le nom de "first" et le second en "second" . Autrement dit, nous voulons que l'importation du premier module de "second.bar" appelle la fonction $func dans le deuxième module. Voici une commande wasm-merge pour cela:
wasm-merge a.wasm first b.wasm second -o output.wasmNous lui donnons le premier fichier wasm, puis son nom, puis le deuxième fichier wasm puis son nom. La sortie fusionnée est la suivante:
( 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 a combiné les deux fichiers en un, fusionnant leurs fonctions, importations, etc., tout en fixant les conflits de noms et en connectant les importations correspondantes aux exportations. En particulier, notez comment $func appelle $func_2 , ce qui est exactement ce que nous voulions: $func_2 est la fonction du deuxième module (renommé pour éviter une collision de nom).
Notez que la sortie WASM dans cet exemple pourrait bénéficier d'une optimisation supplémentaire. Tout d'abord, l'appel à $func_2 peut désormais être facilement incliné, afin que nous puissions exécuter wasm-opt -O3 pour le faire pour nous. De plus, nous n'avons peut-être pas besoin de toutes les importations et exportations, pour lesquelles nous pouvons exécuter WasM-Metadce. Un bon flux de travail pourrait être de courir wasm-merge , puis wasm-metadce , puis terminer avec wasm-opt .
wasm-merge est un peu comme un bundler pour les fichiers WASM, dans le sens d'un "bundler JS" mais pour Wasm. Autrement dit, avec les fichiers WASM ci-dessus, imaginez que nous avions ce code JS pour instancier et les connecter à l'exécution:
// 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 ( ) ; Ce que fait wasm-merge c'est essentiellement ce que fait JS: il connecte les importations aux exportations, la résolution de noms en utilisant les noms de module que vous avez fournis. Autrement dit, en exécutant wasm-merge nous déplaçons le travail de connexion des modules de l'exécution à la compilation du temps. En conséquence, après l'exécution wasm-merge nous avons besoin de beaucoup moins de JS pour obtenir le même résultat:
// 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 ( ) ;Nous devons encore récupérer et compiler le wasm fusionné, et lui fournir l'importation JS, mais le travail pour connecter deux modules WASM n'est plus nécessaire.
Par défaut, les erreurs wasm-merge s'il y a des noms d'exportation qui se chevauchent. Autrement dit, wasm-merge gérera automatiquement les noms de fonction qui se chevauchent et ainsi de suite, car ceux-ci ne sont pas visibles à l'extérieur (le code se comporte toujours de la même manière), mais si nous renommes les exportations, l'extérieur devrait être modifié pour s'attendre aux nouveaux noms d'exportation, et donc nous errons plutôt sur de tels conflits de nom.
Si vous voulez que les exportations soient renommées, exécutez wasm-merge avec --rename-export-conflicts . Les exportations ultérieures auront un suffixe qui leur est ajouté pour s'assurer qu'ils ne chevauchent pas les exportations précédentes. Les suffixes sont déterministes, donc une fois que vous voyez comment vous pouvez les appeler de l'extérieur.
Une autre option consiste à utiliser --skip-export-conflicts qui sautera simplement les exportations ultérieures qui ont des noms contradictoires. Par exemple, cela peut être utile dans le cas où le premier module est le seul qui interagit avec l'extérieur et les modules ultérieurs interagissent simplement avec le premier module.
wasm-merge utilise les fonctionnalités multi-mémoire et multipliques. Autrement dit, si plusieurs modules d'entrée ont chacun une mémoire, la sortie WasM aura plusieurs souvenirs et dépendra de la fonctionnalité multi-mémoire, ce qui signifie que les VMS WASM plus anciennes pourraient ne pas être en mesure d'exécuter le WASM. (En tant que solution de contournement pour ces VM plus anciennes, vous pouvez exécuter wasm-opt --multi-memory-lowering pour abaisser plusieurs souvenirs en un seul.)
./check.py (ou python check.py ) exécutera wasm-shell , wasm-opt , etc. sur les cas de test dans test/ , et vérifiez leurs sorties.
Le script check.py prend en charge certaines options:
./check.py [--interpreter = /path/to/interpreter] [TEST1] [TEST2].../check.py --list-suites .emcc ou nodejs dans le chemin. Ils ne fonctionneront pas si l'outil ne peut être trouvé et vous verrez un avertissement.tests/spec , dans les sous-modules GIT. Exécuter ./check.py devrait les mettre à jour. Notez que nous essayons de porter progressivement les tests Wasm-OPT hérités pour utiliser lit et filecheck lorsque nous les modifions. Pour les tests passes qui gaspillent, cela peut être fait automatiquement avec scripts/port_passes_tests_to_lit.py et pour les tests de non- passes qui gaspillent, voir # 4779 pour un exemple de la façon de faire un port manuel simple.
Pour les tests allumés, les attentes du test (les lignes de contrôle) peuvent souvent être automatiquement mises à jour à mesure que les modifications sont apportées à Binaryen. Voir scripts/update_lit_checks.py .
Les tests non éclairés peuvent également être automatiquement mis à jour dans la plupart des cas. Voir scripts/auto_update_tests.py .
./third_party/setup.py [mozjs | v8 | wabt | all] (ou python third_party/setup.py ) Installe les dépendances requises comme le shell SpiderMonkey JS, le shell V8 JS et WABT dans third_party/ . D'autres scripts les ramassent automatiquement lorsqu'ils sont installés.
Exécutez pip3 install -r requirements-dev.txt pour obtenir les exigences pour les tests lit . Notez que vous devez avoir l'installation de l'emplacement pip sur votre $PATH (sur Linux, ~/.local/bin ).
./scripts/fuzz_opt.py [--binaryen-bin = build/bin] (ou python scripts/fuzz_opt.py ) exécutera divers modes de fuzzing sur des entrées aléatoires avec des passes aléatoires jusqu'à ce qu'elle trouve un bug possible. Voir la page Wiki pour tous les détails.
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".
Yes, it does. 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.