
Binaryen é uma biblioteca de infraestrutura de compilador e ferramenta para o WebAssembly, escrita em C ++. Ele pretende fazer a compilação de montagem web fácil, rápida e eficaz :
Fácil : o Binaryen possui uma API C simples em um único cabeçalho e também pode ser usado no JavaScript. Ele aceita a entrada em forma de WebAssembly, mas também aceita um gráfico de fluxo de controle geral para compiladores que preferem isso.
Fast : o IR interno da Binaryen usa estruturas de dados compactas e foi projetado para coden e otimização completamente paralelo, usando todos os núcleos de CPU disponíveis. O IR da Binaryen também se compõe à WebAssembly de maneira extremamente fácil e rápida, porque é essencialmente um subconjunto de WebAssembly.
Efetivo : o otimizador da Binaryen tem muitos passes (consulte uma visão geral posteriormente) que pode melhorar o tamanho e a velocidade do código. Essas otimizações têm como objetivo tornar o Binaryen poderoso o suficiente para ser usado como back -end do compilador por si só. Uma área de foco específica é as otimizações específicas da WebAssembly (que os compiladores de uso geral podem não fazer), que você pode pensar como minificação de WASM, semelhante à minificação para JavaScript, CSS etc., todos específicos da linguagem.
As cadeias de ferramentas que usam o binaryen como um componente (normalmente em execução wasm-opt ) incluem:
Emscripten (C/C ++)wasm-pack (ferrugem)J2CL (Java; J2Wasm )Kotlin (Kotlin/WASM)Dart (vibração)wasm_of_ocaml (ocaml)Para saber mais sobre como alguns desses funcionam, consulte as partes da arquitetura da cadeia de ferramentas do V8 WASMGC Porting BlogPost.
Os compiladores que usam o Binaryen como uma biblioteca incluem:
AssemblyScript que compila uma variante do TypeScript para WebAssemblywasm2js que compila WebAssembly para JSAsterius que compila Haskell a WebAssheblyGrain que compilam grãos com o WebAssemblyBinaryen também fornece um conjunto de utilitários de cadeia de ferramentas que podem
Consulte as instruções contribuintes se você estiver interessado em participar.
O IR interno da Binaryen foi projetado para ser
Existem algumas diferenças entre o Binaryen IR e a linguagem WebAssembly:
catch no recurso de manuseio de exceção) são representados como subexpressões pop .--generate-stack-ir --print-stack-ir , que imprime a pilha de IR, isso é garantido para ser válido para os analistas WASM.)ref.func deve estar na tabela ou declarado por meio de um (elem declare func $..) . O Binaryen emitirá esses dados quando necessário, mas não o representa no IR. Ou seja, o IR pode ser trabalhado sem precisar pensar em declarar referências de funções.local.set a especificação do WASM (que foi historicamente apelidada de "1A"), na qual um local.get . Apesar de estar alinhado com a especificação do WASM, há alguns detalhes menores que você pode notar:Block sem nome no Binaryen IR não interfere na validação. Os blocos sem nome nunca são emitidos no formato binário (apenas emitimos seu conteúdo), por isso os ignoramos para fins de moradores não nulos. Como resultado, se você ler o texto do WASM emitido pelo Binaryen, poderá ver o que parece ser um código que não deve validar de acordo com a especificação (e pode não validar nos analisadores de texto do WASM), mas essa diferença não existirá no formato binário (os binários emitidos por Binaryen sempre funcionarão em todos os lugares, além de bugs).requiresNonNullableLocalFixups() em pass.h e a classe LocalStructuralDominance .ref.func é sempre um tipo de função específico e não é funcref simples. Também é não indicável.try_table envia em ramificações (se ramificarmos, um nulo nunca será enviado), ou seja, ele envia (ref Exn) e não (Ref null exn). Nos dois casos, se o GC não estiver ativado, emitimos o tipo menos refinado no binário. Ao ler um binário, os tipos mais refinados serão aplicados à medida que construímos o IR.br_if são mais refinados no Binário IR: eles têm o tipo de valor, quando um valor flui. Na especificação WASM, o tipo é o do alvo da ramificação, que pode ser menos refinado. O uso do tipo mais refinado aqui garante que otimizemos da melhor maneira possível, usando todas as informações do tipo, mas isso significa que algumas operações de arrasto podem parecer um pouco diferentes. Em particular, quando emitimos um br_if cujo tipo é mais refinado no Binaryen IR do que emitimos um elenco logo após ele, para que a saída tenha o tipo certo na especificação do WASM. Isso pode causar alguns bytes de tamanho extra em casos raros (evitamos essa sobrecarga no caso comum em que o valor br_if não é utilizado).stringview_wtf16 etc.) sejam fundidas usando ref.cast . Isso simplifica o IR, pois permite que ref.cast seja sempre usado em todos os lugares (e é reduzido para ref.as_non_null sempre que possível no otimizador). A especificação StringRef não parece permitir isso, e para corrigir que o escritor binário substituirá ref.cast que lança uma exibição de string a um tipo não nulo para ref.as_non_null . Um ref.cast de uma visualização de string que é um não-OP é totalmente ignorado.Como resultado, você pode notar que as conversões de ida e volta (WASM => binaryen ir => wasm) mudam um pouco em alguns casos de canto.
src/wasm-stack.h ). A pilha IR permite um monte de otimizações adaptadas para a forma de máquina de pilha do formato binário da WebAssembly (mas a pilha IR é menos eficiente para otimizações gerais do que o principal IR do binário). Se você possui um arquivo WASM que foi particularmente bem otimizado, uma simples conversão de ida e volta (basta ler e escrever, sem otimização) pode causar diferenças mais perceptíveis, pois o Binaryen se encaixa no formato mais estruturado do Binário IR. Se você também otimizar durante a conversão de ida e volta, o IR OPTS será executado e o WASM final será melhor otimizado.Notas ao trabalhar com o Binaryen IR:
Funções intrínsecas binaryen parecem chamadas para importações, por exemplo,
( import " binaryen-intrinsics " " foo " ( func $foo ))Implementá -los dessa maneira permite que eles sejam lidos e escritos por outras ferramentas, e evita erros confusos em um erro de formato binário que pode acontecer nessas ferramentas se tivéssemos uma extensão de formato binário personalizado.
Um método intrínseco pode ser otimizado pelo otimizador. Caso contrário, ele deve ser reduzido antes do envio do FASM, caso contrário, parecerá uma chamada para uma importação que não existe (e as VMs mostrarão um erro em não ter um valor adequado para essa importação). Essa redução final não é feita automaticamente. Um usuário de intrínsecas deve executar o passe para isso explicitamente, porque as ferramentas não sabem quando o usuário pretende terminar de otimizar, pois o usuário pode ter um pipeline de várias etapas de otimização ou pode estar fazendo experimentação local ou fuzzing/redução, etc. Somente o usuário sabe que quando a otimização final acontece antes que o WASM seja "final" e pronto para ser usado. Observe que, em geral, algumas otimizações adicionais podem ser possíveis após a redução final e, portanto, um padrão útil é otimizar uma vez normalmente com a Intrinsics, depois abaixá -las e depois otimizar depois disso, por exemplo:
wasm-opt input.wasm -o output.wasm -O --intrinsic-lowering -OCada intrínseco define sua semântica, que inclui o que o otimizador pode fazer com ele e o que a redução final o transformará. Consulte Intrinsics.h para as definições detalhadas. Um resumo rápido aparece aqui:
call.without.effects : Semelhante a um call_ref , pois recebe parâmetros e uma referência a uma função a ser chamada, e chama essa função com esses parâmetros, exceto que o otimizador pode assumir que a chamada não tem efeitos colaterais e pode ser capaz de otimizá -lo (se não tiver resultado que é usado, geralmente). Este repositório contém código que constrói as seguintes ferramentas no bin/ (consulte as instruções de construção):
wasm-opt : Carrega o WebAssembly e executa o Binaryen IR passa nele.wasm-as : monta WebAssembly no formato de texto (atualmente formato de expressão S) em formato binário (passando pelo Binário IR).wasm-dis : UN-Monthembles WebAssembly em formato binário no formato de texto (passando pelo Binário IR).wasm2js : um compilador WebAssembly-to-JS. Isso é usado pelo EMSCRIPTEN para gerar JavaScript como uma alternativa ao WebAssembly.wasm-reduce : um redutor de teste para arquivos WebAssembly. Dado um arquivo WASM que é interessante por algum motivo (digamos, trava uma VM específica), o Reduce WASM pode encontrar um arquivo WASM menor que tenha a mesma propriedade, que geralmente é mais fácil de depurar. Veja os documentos para obter mais detalhes.wasm-shell : um shell que pode carregar e interpretar o código WebAssembly. Ele também pode executar o conjunto de testes especiais.wasm-emscripten-finalize : pega um binário WASM produzido pelo LLVM+LLD e executa passes específicos do EMSCRIPTEN sobre ele.wasm-ctor-eval : Uma ferramenta que pode executar funções (ou partes das funções) no momento da compilação.wasm-merge : mescla vários arquivos WASM em um único arquivo, conectando as importações correspondentes às exportações como isso. Como um empurrador para JS, mas para Wasm.wasm-metadce : Uma ferramenta para remover partes dos arquivos WASM de uma maneira flexível que depende de como o módulo é usado.binaryen.js : Uma biblioteca JavaScript independente que expõe os métodos binaryen para criar e otimizar os módulos WASM. Para compilações, consulte Binaryen.js no NPM (ou faça o download diretamente do GitHub ou Unpkg). Requisitos mínimos: Node.js v15.8 ou Chrome V75 ou Firefox V78.Todas as ferramentas binárias são determinísticas, ou seja, dadas as mesmas entradas que você sempre deve obter as mesmas saídas. (Se você vir um caso que se com outra forma, registre um problema.)
As instruções de uso para cada uma estão abaixo.
O Binaryen contém muitos passes de otimização para tornar o WebAssobly menor e mais rápido. Você pode executar o otimizador binaryen usando wasm-opt , mas também eles podem ser executados enquanto usam outras ferramentas, como wasm2js e wasm-metadce .
addDefaultFunctionOptimizationPasses .wasm-opt --help para como defini-los e outros detalhes.Veja cada otimização passa para detalhes do que faz, mas aqui está uma rápida visão geral de alguns dos relevantes:
if os braços tiverem algumas instruções compartilhadas no final).block para um externo sempre que possível, reduzindo seu número.local.set de um valor que já está presente em um local. (Sobrepõe -se aos Coalescelocals; isso alcança a operação específica mencionada sem todo o outro trabalho Coalescelocals e, portanto, é útil em outros lugares do pipeline de otimização.)br ou br_table (como girar um block com um br no meio em um if possível).local.get/set/tee " Otimização passa, fazendo coisas como substituir um conjunto e obter o valor do valor do conjunto para o GET (e criando um tee) sempre que possível. Também cria valores de retorno block/if/loop em vez de usar um local para passar o valor.if não tem conteúdo, uma gota de um valor constante sem efeitos colaterais, um block com uma única criança, etc."LTO", no exposto, significa que uma otimização é a otimização do tempo de link, na medida em que funciona em várias funções, mas, em certo sentido, o binaryen é sempre "LTO", pois geralmente é executado no WASM vinculado final.
As técnicas avançadas de otimização no otimizador binário incluem Ssafation, Flat IR e Stack/Poppy IR.
Consulte a página Wiki do Livro de receitas do Optimizer para obter mais informações sobre como usar o otimizador de maneira eficaz.
Binaryen também contém vários passes que fazem outras coisas além do otimizações, como a legalização para JavaScript, assíncico, etc.
Binaryen usa submódulos Git (no momento da redação apenas para o GTEST); portanto, antes de construir, você terá que inicializar os submódulos:
git submodule init
git submodule updateDepois disso, você pode construir com cmake:
cmake . && make Um compilador C ++ 17 é necessário. No MacOS, você precisa instalar cmake , por exemplo, via brew install cmake . Observe que você também pode usar ninja como seu gerador: cmake -G Ninja . && ninja .
Para evitar a dependência do GTEST, você pode passar -DBUILD_TESTS=OFF para cmake.
O binaryen.js pode ser construído usando o EMSCRIPTEN, que pode ser instalado através do SDK.
emcmake cmake . && emmake make binaryen_jsemcmake cmake -DBUILD_FOR_BROWSER=ON . && emmake makeUsando o instalador do Microsoft Visual Studio, instale o componente "Visual C ++ Ferramentas para CMake".
Gerar os projetos:
mkdir build
cd build
" %VISUAL_STUDIO_ROOT%Common7IDECommonExtensionsMicrosoftCMakeCMakebincmake.exe " ..Substitua Visual_studio_root pelo caminho para a instalação do Visual Studio. Caso você esteja usando as ferramentas de construção do Visual Studio, o caminho será "C: Arquivos de Programas (x86) Microsoft Visual Studio 2017 BuildTools".
No prompt de comando do desenvolvedor, construa os projetos desejados:
msbuild binaryen.vcxprojO CMake gera um projeto chamado "all_build.vcxproj" para criar convenientemente todos os projetos.
As compilações são distribuídas pelas várias cadeias de ferramentas que usam Binaryen, como Emscriptten, wasm-pack etc. Também existem lançamentos oficiais no GitHub:
https://github.com/webassembly/binaryen/releases
Atualmente, as compilações das seguintes plataformas estão incluídas:
Linux-x86_64Linux-arm64MacOS-x86_64MacOS-arm64Windows-x86_64Node.js (Experimental): Uma porta de wasm-opt para JavaScript+WebAssembly. Executar node wasm-opt.js como substituto de uma compilação nativa do wasm-opt , em qualquer plataforma em que o Node.js use. Requer node.js 18+ (para fios WASM EH e WASM). (Observe que essa construção também pode ser executada em Deno, Bun ou outros ambientes JavaScript+WebAssembly, mas é testado apenas no Node.js.) Correr
bin/wasm-opt [.wasm or .wat file] [options] [passes, see --help] [--help]O otimizador do WASM recebe o WebAssembly como entrada e pode executar passes de transformação, além de imprimi -lo (antes e/ou depois das transformações). Por exemplo, tente
bin/wasm-opt test/lit/passes/name-types.wast -all -S -o -Isso produzirá um dos casos de teste no conjunto de testes. Para executar um passe de transformação, tente
bin/wasm-opt test/lit/passes/name-types.wast --name-types -all -S -o - O passe name-types garante que cada tipo tenha um nome e renomeie nomes de tipos excepcionalmente longos. Você pode ver a mudança que a transformação causa comparando a saída dos dois comandos.
É fácil adicionar seus próprios passes de transformação ao shell, basta adicionar arquivos .cpp em src/passes e reconstruir o shell. Por exemplo, código, dê uma olhada no passe dos name-types .
Mais algumas notas:
bin/wasm-opt --help para obter a lista completa de opções e passes.--debug emitirá algumas informações de depuração. Os canais de depuração individuais (definidos no código-fonte via #define DEBUG_TYPE xxx ) podem ser ativados passando-os como lista de strings separadas por vírgula. Por exemplo: bin/wasm-opt --debug=binary . Esses canais de depuração também podem ser ativados através da variável BINARYEN_DEBUG Environment.Correr
bin/wasm2js [input.wasm file]Isso imprimirá JavaScript para o console.
Por exemplo, tente
bin/wasm2js test/hello_world.watEssa saída contém
function add ( x , y ) {
x = x | 0 ;
y = y | 0 ;
return x + y | 0 | 0 ;
}como uma tradução de
( func $add (; 0 ;) ( type $0 ) ( param $x i32 ) ( param $y i32 ) ( result i32 )
( i32.add
( local.get $x )
( local.get $y )
)
)A saída do WASM2JS está no formato do módulo ES6 - basicamente, ele converte um módulo WASM em um módulo ES6 (para ser executado em navegadores mais antigos e versões Node.js, você pode usar Babel etc. para convertê -lo em ES5). Vejamos um exemplo completo de chamar aquele olá mundial Wat; Primeiro, crie o arquivo JS principal:
// main.mjs
import { add } from "./hello_world.mjs" ;
console . log ( 'the sum of 1 and 2 is:' , add ( 1 , 2 ) ) ;A execução é (observe que você precisa de um novo node.js o suficiente com suporte do 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: 3As coisas lembram -se da saída do WASM2JS:
-O ou outro nível de otimização. Isso otimizará ao longo de todo o pipeline (WASM e JS). No entanto, não fará tudo o que um JS Minifer, como Minify Whitespace, então você ainda deve executar um JS Minifer normal depois. wasm-ctor-eval executa funções, ou partes delas, no momento da compilação. Depois de fazer isso, isso serializa o estado de tempo de execução no WASM, que é como tirar um "instantâneo". Quando a WASM for posteriormente carregada e executada em uma VM, ela continuará a execução a partir desse ponto, sem refazer o trabalho que já foi executado.
Por exemplo, considere este pequeno 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 avaliar parte dele em tempo de compilação como este:
wasm-ctor-eval input.wat --ctors=main -S -o - Isso diz que existe uma única função que queremos executar ("ctor" é abrevante para "Construtor Global", um nome que vem do código que é executado antes do ponto de entrada de um programa) e, em seguida, imprimi -lo como texto para stdout . O resultado é o seguinte:
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)
)
)
) O registro mostra -nos conseguindo avaliar parte de main() , mas não de tudo, como esperado: podemos avaliar o primeiro global.get , mas então paramos na chamada para a função importada (porque não sabemos qual será essa função quando o WASM for realmente executado em uma VM mais tarde). Observe como na saída o valor do global foi atualizado de 0 a 1 e que o wasm-ctor-eval global.get
Neste pequeno exemplo, apenas salvamos uma pequena quantidade de trabalho. Quanto trabalho pode ser salvo depende do seu programa. (Pode ajudar a fazer computação pura antecipadamente e deixar chamadas para as importações o mais tarde possível.)
Observe que o nome do wasm-ctor-eval está relacionado às funções do construtor global, como mencionado anteriormente, mas não há limitação sobre o que você pode executar aqui. Qualquer exportação da BASM pode ser executada, se seu conteúdo for adequado. Por exemplo, no EMSCRIPTEN wasm-ctor-eval é executado em main() quando possível.
wasm-merge combina arquivos WASM. Por exemplo, imagine que você tem um projeto que usa arquivos WASM de várias cadeias de ferramentas. Em seguida, pode ser útil mesclá -los todos em um único arquivo de WASM antes do envio, pois em um único arquivo WASM as chamadas entre os módulos se tornam apenas chamadas normais dentro de um módulo, o que lhes permite que sejam inlinados, o código morto eliminado e assim por diante, melhorando potencialmente a velocidade e o tamanho.
wasm-merge opera em arquivos Normais WASM. Difere do wasm-ld a esse respeito, pois wasm-ld opera em arquivos de objeto WASM. wasm-merge pode ajudar em situações de múltiplas toolchain, onde pelo menos uma das cadeias de ferramentas não usa arquivos de objeto WASM.
Por exemplo, imagine que temos esses dois arquivos 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 )
)
)
) Os nomes de arquivos em sua unidade local são a.wasm e b.wasm , mas para fins de fusão / agrupamento, digamos que o primeiro é conhecido como "first" e o segundo como "second" . Ou seja, queremos que a importação do primeiro módulo de "second.bar" chame a função $func no segundo módulo. Aqui está um comando WASM-Merge para isso:
wasm-merge a.wasm first b.wasm second -o output.wasmDamos o primeiro arquivo WASM, depois seu nome e, em seguida, o segundo arquivo WASM e, em seguida, seu nome. A saída mesclada é a seguinte:
( 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 combinou os dois arquivos em um, mesclando suas funções, importações etc., enquanto conserta conflitos de nome e conecta as importações correspondentes às exportações. Em particular, observe como $func chama $func_2 , que é exatamente o que queríamos: $func_2 é a função do segundo módulo (renomeado para evitar uma colisão de nome).
Observe que a saída do WASM neste exemplo pode se beneficiar da otimização adicional. Primeiro, a chamada para $func_2 agora pode ser facilmente inlagada, para que possamos executar wasm-opt -O3 para fazer isso por nós. Além disso, podemos não precisar de todas as importações e exportações, para as quais podemos executar o WASM-Metadce. Um bom fluxo de trabalho pode ser executar wasm-merge , depois wasm-metadce , depois terminar com wasm-opt .
wasm-merge é como um empurrador para arquivos WASM, no sentido de um "JS Bundler", mas para a WASM. Ou seja, com os arquivos WASM acima, imagine que tivemos esse código JS para instanciá -los e conectá -los em tempo de execução:
// 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 ( ) ; O que wasm-merge faz é basicamente o que o JS faz: ele liga as importações para as exportações, resolvendo nomes usando os nomes do módulo que você forneceu. Ou seja, ao executar wasm-merge estamos movendo o trabalho de conectar os módulos do tempo de execução ao tempo de compilação. Como resultado, depois de executar wasm-merge precisamos de muito menos JS para obter o mesmo 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 ( ) ;Ainda precisamos buscar e compilar o WASM mesclado e fornecer a importação do JS, mas o trabalho para conectar dois módulos WASM não é mais necessário.
Por padrão, erros wasm-merge se houver nomes de exportação sobrepostos. Ou seja, wasm-merge lidará automaticamente com nomes de funções sobrepostos e assim por diante, porque eles não são visíveis externamente (o código ainda se comporta o mesmo), mas se renomearmos as exportações, o exterior precisará ser modificado para esperar os novos nomes de exportação e, portanto, erramos esses conflitos de nome.
Se você deseja que as exportações sejam renomeadas, execute wasm-merge com --rename-export-conflicts . As exportações posteriores terão um sufixo anexado a eles para garantir que não se sobreponham às exportações anteriores. Os sufixos são determinísticos; portanto, depois de ver o que são, você pode chamá -los de fora.
Outra opção é usar --skip-export-conflicts , que simplesmente pularão as exportações posteriores que possuem nomes conflitantes. Por exemplo, isso pode ser útil no caso em que o primeiro módulo é o único que interage com o exterior e os módulos posteriores apenas interagem com o primeiro módulo.
wasm-merge usa os recursos multimemoria e multi-tabela. Ou seja, se vários módulos de entrada tiverem uma memória, o WASM de saída terá várias memórias e dependerá do recurso multimemoria, o que significa que as VMs de WASM mais antigas podem não ser capazes de executar o WASM. (Como uma solução alternativa para VMs tão mais antigas, você pode executar wasm-opt --multi-memory-lowering para reduzir várias memórias em uma única.)
./check.py (ou python check.py ) Executará wasm-shell , wasm-opt , etc. nas casas de teste em test/ , e verificará suas saídas.
O script check.py suporta algumas opções:
./check.py [--interpreter = /path/to/interpreter] [TEST1] [TEST2].../check.py --list-suites .emcc ou nodejs no caminho. Eles não serão executados se a ferramenta não puder ser encontrada e você verá um aviso.tests/spec , em submódulos Git. Em execução ./check.py deve atualizá -los. Observe que estamos tentando portar gradualmente os testes de WASM-OPT Legacy para usar lit e filecheck à medida que os modificamos. Para testes passes que saem de saída, isso pode ser feito automaticamente com scripts/port_passes_tests_to_lit.py e para testes que não são passes que saídas de saída, consulte #4779 para obter um exemplo de como fazer uma porta manual simples.
Para testes LIT, as expectativas do teste (as linhas de verificação) geralmente podem ser atualizadas automaticamente à medida que as alterações são feitas para o Binaryen. Consulte scripts/update_lit_checks.py .
Os testes sem iluminação também podem ser atualizados automaticamente na maioria dos casos. Consulte scripts/auto_update_tests.py .
./third_party/setup.py [mozjs | v8 | wabt | all] (ou python third_party/setup.py ) instala dependências necessárias como o shell Spidermonkey JS, o shell JS V8 e o WABT em third_party/ . Outros scripts pegam -os automaticamente quando instalados.
Execute pip3 install -r requirements-dev.txt para obter os requisitos para os testes lit Observe que você precisa ter o local pip instalar no seu $PATH (no Linux, ~/.local/bin ).
./scripts/fuzz_opt.py [--binaryen-bin = build/bin] (ou python scripts/fuzz_opt.py ) executará vários modos de fuzzing em entradas aleatórias com passes aleatórios até encontrar um bug possível. Veja a página do Wiki para todos os detalhes.
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".
Sim, sim. 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.