Iniciando com WebAssembly - Parte 1
Olá, esta é uma série inédita de artigos sobre WebAssembly. Vejo que você está na primeira parte. Vá com calma, essa é uma tecnologia relativamente nova e em constante evolução, porém, com um potencial enorme. Pegue um café ☕️ e bons estudos 📒!
Há pouco mais de 2 anos escrevi o artigo "WebAssembly e o futuro da Web" no meu blog pessoal.
Este foi um texto introdutório para uma tecnologia nova e muito promissora que estava surgindo, o WebAssembly.
Pouco tempo depois estou aqui novamente para falar sobre um futuro que já chegou!
Isso mesmo, o suporte a WebAssembly já está em quase 60% dos principais navegadores (Chrome, Firefox, Safari, Brave, Opera), sendo que no Microsoft Edge é possível habilita-lo via flags (experimental) e em breve deve estar estável para uso.
Em resumo, o que antes parecia algo muito legal, mas longe de ser alcançado (já passamos por isso muitas vezes, né?), agora já é realidade.
Se você já ouviu falar, mas não sabe exatamente como e quando o WebAssemlby irá te ajudar, ou se já ouviu falar, mas acha que é algo muito complicado, te convido a mergulhar nesta série de artigos :)
Mas o que é esse WebAssembly
Em um tweet e direto do site oficial: O WebAssembly, ou somente wasm, é um novo formato, portável, leve e com tempo de carregamento eficiente, adequado para compilação na Web.
1) Novo formato
Assim como o nosso velho conhecido .js
, agora temos o .wasm
.
O WebAssembly define um novo formato de texto padrão que encoda um módulo WebAssembly com todas as definições para o seu formato binário.
Não precisamos nos aprofundar nas entranhas do WebAssembly, mas é importante (e muito legal) entender como algumas coisas funcionam.
O formato de texto do WebAssembly utilza S-expressions, que é uma notação para listas de dados encadeadas inventada para utilização no Lisp.
Mais uma vez o Lisp influenciando uma tecnologia que o Brendan Eich está ativamente envolvido :)
Para animar as coisas já no início, vamos ver código?
Como o objetivo final é a compilação de um programa escrito em outra linguagem (C/C++, Rust, por exemplo) para wasm
, vamos ver um simples programa em C
, que apenas incrementa um valor:
/* counter.c */ int counter = 100; int count() { counter += 1; return counter; }
Após a compilação do programa counter.c
acima (veremos como fazer isso mais adiante), o resultado final, que será consumido pelo navegador (ou outra plataforma), será o arquivo counter.wasm
:
0061 736d 0100 0000 000c 0664 796c 696e 6b90 80c0 0200 0188 8080 8000 0260 0001 7f60 0000 02c1 8080 8000 0403 656e 760a 6d65 6d6f 7279 4261 7365 037f 0003 656e 7606 6d65 6d6f 7279 0200 8002 0365 6e76 0574 6162 6c65 0170 0000 0365 6e76 0974 6162 6c65 4261 7365 037f 0003 8480 8080 0003 0001 0106 9080 8080 0003 7f01 4100 0b7f 0141 000b 7f00 4100 0b07 b880 8080 0004 065f 636f 756e 7400 0012 5f5f 706f 7374 5f69 6e73 7461 6e74 6961 7465 0002 0b72 756e 506f 7374 5365 7473 0001 085f 636f 756e 7465 7203 0409 8180 8080 0000 0ac3 8080 8000 0398 8080 8000 0101 7f02 7f23 0023 0028 0200 4101 6a22 0036 0200 2000 0b0b 8380 8080 0000 010b 9880 8080 0000 0240 2300 4110 6a24 0223 0241 8080 c002 6a24 0310 010b 0b0b 8780 8080 0001 0023 000b 0164
Não muito útil para nós, né? Mas é legal de saber exatamente o que está acontecendo, e é este arquivo que será de fato consumido.
Mas @Jaydson, WebAssembly não é um formato binário?
Muito bem notado, caro leitor. O resultado do arquivo counter.wasm
está em formato hexadecimal. Para ir mais fundo ainda, e para provar que não estou somente jogando números aleatórios por aqui, vamos ver o resultado binário do programa counter.c
:
0000000001100001 0111001101101101 0000000100000000 0000000000000000 0000000000001100 0000011001100100 0111100101101100 0110100101101110 0110101110010000 1000000011000000 0000001000000000 0000000110001000 1000000010000000 1000000000000000 0000001001100000 0000000000000001 0111111101100000 0000000000000000 0000001011000001 1000000010000000 1000000000000000 0000010000000011 0110010101101110 0111011000001010 0110110101100101 0110110101101111 0110110101101111 0100001001100001 0111001101100101 0000001101111111 0000000000000011 0110010101101110 0111011000000110 0110110101100101 0110110101101111 0111001001111001 0000001000000000 1000000000000010 0000001101100101 0110111001110110 0111011000000110 0110110101100101 0110110101101111 0111001001111001 0000001000000000 1000000000000010 0000001101100101 0110111001110110 0000010101110100 0110000101100010 0110110001100101 0000000101110000 0000000000000000 0000001101100101 0110111001110110 0000100101110100 0110000101100010 0110110001100101 0100001001100001 0111001101100101 0000001101111111 0000000000000011 1000010010000000 1000000010000000 0000000000000011 0000000000000001 0000000100000110 1001000010000000 1000000010000000 0000000000000011 0111111100000001 0100000100000000 0000101101111111 0000000101000001 0000000000001011 0111111100000000 0100000100000000 0000101100000111 1011100010000000 1000000010000000 0000000000000100 0000011001011111 0110001101101111 0111010101101110 0111010000000000 0000000000010010 0101111101011111 0111000001101111 0111001101110100 0101111101101001 0110111001110011 0111010001100001 0110111001110100 0110100101100001 0111010001100101 0000000000000010 0000101101110010 0111010101101110 0101000001101111 0111001101110100 0101001101100101 0111010001110011 0000000000000001 0000100001011111 0110001101101111 0111010101101110 0111010001100101 0111001000000011 0000010000001001 1000000110000000 1000000010000000 0000000000000000 0000101011000011 1000000010000000 1000000000000000 0000001110011000 1000000010000000 1000000000000000 0000000100000001 0111111100000010 0111111100100011 0000000000100011 0000000000101000 0000001000000000 0100000100000001 0110101000100010 0000000000110110 0000001000000000 0010000000000000 0000101100001011 1000001110000000 1000000010000000 0000000000000000 0000000100001011 1001100010000000 1000000010000000 0000000000000000 0000001001000000 0010001100000000 0100000100010000 0110101000100100 0000001000100011 0000001001000001 1000000010000000 1100000000000010 0110101000100100 0000001100010000 0000000100001011 0000101100001011 1000011110000000 1000000010000000 0000000000000001 0000000000100011 0000000000001011 0000000101100100
Ainda não muito útil, mas agora temos o conhecimento geral de como as coisas funcionam.
Ainda faltou mostrar um código interessante, o nosso programa counter.c
, compilado para wasm e representado em S-expression.
(module (type $t0 (func (result i32))) (type $t1 (func)) (import "env" "memoryBase" (global $g0 i32)) (import "env" "memory" (memory $M0 256)) (import "env" "table" (table $T0 0 anyfunc)) (import "env" "tableBase" (global $g1 i32)) (func $f0 (export "_count") (type $t0) (result i32) (local $l0 i32) (block $B0 (result i32) (i32.store (get_global $g0) (tee_local $l0 (i32.add (i32.load (get_global $g0)) (i32.const 1)))) (get_local $l0))) (func $f1 (export "runPostSets") (type $t1) (nop)) (func $f2 (export "__post_instantiate") (type $t1) (block $B0 (set_global $g2 (i32.add (get_global $g0) (i32.const 16))) (set_global $g3 (i32.add (get_global $g2) (i32.const 5242880))) (call $f1))) (global $g2 (mut i32) (i32.const 0)) (global $g3 (mut i32) (i32.const 0)) (global $g4 (export "_counter") i32 (i32.const 0)) (data (get_global 0) "d"))
Note que o código acima não é um .wasm
, mas sim um .wat
, que é a extensão recomendada para código WebAssembly em formato de texto.
Mas entenda que este código dificilmente será escrito por "humanos".
O ponto chave é lembrar que o objetivo do WebAssembly é ser um formato de fácil compilação, ou seja, nós não iremos escrever módulos WebAssembly, mas sim portar códigos já existentes, ou até mesmo criar soluções que exigem alta performance em outras linguagens.
Um exemplo legal, citado pela Lim Clark, na excelente série sobre WebAssembly na MDN, é o caso do React, onde por exemplo, seria possível portar toda aquela parte complexa reponsável pelo virualDOM para WebAssembly.
Para quem usa React, essa mudança não afetaria em nada, mas o ganho de performance seria perceptível.
A Lin inclusive deu uma palestra na última ReactEurope, falando justamente sobre WebAssembly e React: https://www.youtube.com/watch?v=3GHJ4cbxsVQ
2) Portável
O WebAssembly foi desenvolvido para rodar de maneira eficiente em diferentes tipos de sistemas operacionais e diferentes arquiteturas, na Web e fora da Web.
Um ponto interessante é o que o WebAssembly foi criado para rodar na Web (em navegadores), mas é possível rodar wasm em outros ambientes, como servidores, dispositivos IoT, apps desktop, apps mobile, etc.
Obviamente o Node.js já vem a cabeça, certo? E sim, é possível rodar WebAssembly no ambiente Node.js, ou em qualquer plataforma semelhante, porém, o WebAssembly vai além e é capaz de ser executado inclusive em ambientes sem um interpretador JavaScript.
3) Leve e tempo de carregamento eficiente
O WebAssembly já nasceu com o objetivo de ser muito leve, e por leve entenda o tamanho do arquivo trafegado na rede.
Por ser um formato binário e otimizado, arquivos wasm são muito mais leves do que arquivos JavaScript, por exemplo.
Este é um dos fatores que contribuem bastante para um carregamento mais eficiente.
Outro ponto importante do WebAssembly é em relação ao parsing.
Engines JavaScript interpretam uma representação intermediária resultante de um AST que foi previamente parseado.
Meio nebuloso? Pois é, acontece um monte de coisa até aquele seu código lindo ser executado.
O importante aqui é o fato de que o WebAssembly não "sofre" com esses passos, pois não há necessidade de executa-los, visto que não existe transformação.
O código WebAssembly já é a própria representação intermediária, de maneira que basta o decode para tudo funcionar.
WebAssembly é de fato muito rápido, e ainda estamos nos early days.
Ainda existem muitos pontos que podem ser melhorados, mas dependendo do tipo de software que você esteja desenvolvendo ou portando para WebAssembly, é possível ter de 10% até 800% de ganho de velocidade. Wow!
Mas e o JavaScript?
Calma calma pessoal! Muita gente já me perguntou: "Mas então o WebAssembly é o substituto do JavaScript"?
Pelo contrário, o WebAssembly é mais uma camada super importante da plataforma Web, que trabalha de mãos dadas com o JavaScript.
Módulos wasm
só funcionam sendo invocados pela API JavaScript disponível nos navegadores.
É um trabalho em conjunto, assim como acontece com o HTML, CSS, SVG, WebGL, etc.
Veja um exemplo de integração:
var importObj = {js: { import1: () => console.log("hello,"), import2: () => console.log("world!") }}; fetch('demo.wasm').then(response => response.arrayBuffer() ).then(buffer => WebAssembly.instantiate(buffer, importObj) ).then(({module, instance}) => instance.exports.f() );
Após feita a compilação para wasm
(aquele código hexadecimal mostrado acima), podemos importar o módulo com um simples fetch.
A API ainda não é das mais human readable, mas estamos chegando lá.
Não se preocupe em entender este código agora, abordaremos em mais detalhes nos próximos artigos da série.
Conclusão
E assim terminamos a primeira parte da série de artigos sobre essa nova e incrível tecnologia capaz de nos proporcionar uma Web muito mais rápida.
Vimos que o WebAssembly já está disponível em quase 60% dos principais navegadores.
Fizemos uma breve revisão sobre os objetivos da tecnologia, vimos que o WebAssembly introduz um novo formato de texto que representa o seu formato binário.
Também vimos exemplos de código em seus possíveis formatos, para termos uma real noção de tudo que acontece por trás dessa mágica toda.
Também foi possível notar que o WebAssembly é mais leve e mais performático que o JavaScript, não esquecendo que a tecnologia não substitui o JavaScript, pelo contrário, trabalha junto.
Na próxima parte da série vamos sujar um pouco as mãos, fazer um programa em C rodar no navegador e conhecer um pouco das ferramentas disponíveis para fazer tudo isso acontecer.
Até lá 👋👋👋