No Chrome Dev Summit 2020, demonstramos pela primeira vez o suporte à depuração do Chrome para aplicativos WebAssembly na Web. Desde então, a equipe investiu muito para ampliar a experiência do desenvolvedor para aplicativos grandes e até mesmo enormes. Nesta postagem, vamos mostrar as opções que adicionamos (ou funcionam) nas diferentes ferramentas e como usá-las.
Depuração escalonável
Vamos continuar de onde paramos na nossa postagem de 2020. Aqui está o exemplo que estávamos analisando:
#include <SDL2/SDL.h>
#include <complex>
int main() {
// Init SDL.
int width = 600, height = 600;
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window;
SDL_Renderer* renderer;
SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
&renderer);
// Generate a palette with random colors.
enum { MAX_ITER_COUNT = 256 };
SDL_Color palette[MAX_ITER_COUNT];
srand(time(0));
for (int i = 0; i < MAX_ITER_COUNT; ++i) {
palette[i] = {
.r = (uint8_t)rand(),
.g = (uint8_t)rand(),
.b = (uint8_t)rand(),
.a = 255,
};
}
// Calculate and draw the Mandelbrot set.
std::complex<double> center(0.5, 0.5);
double scale = 4.0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
std::complex<double> point((double)x / width, (double)y / height);
std::complex<double> c = (point - center) * scale;
std::complex<double> z(0, 0);
int i = 0;
for (; i < MAX_ITER_COUNT - 1; i++) {
z = z * z + c;
if (abs(z) > 2.0)
break;
}
SDL_Color color = palette[i];
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
SDL_RenderDrawPoint(renderer, x, y);
}
}
// Render everything we've drawn to the canvas.
SDL_RenderPresent(renderer);
// SDL_Quit();
}
Ainda é um exemplo pequeno e provavelmente você não veria nenhum dos problemas reais que encontraria em um aplicativo muito grande, mas ainda podemos mostrar quais são os novos recursos. É rápido e fácil de configurar e você mesmo pode testar!
Na última postagem, discutimos como compilar e depurar esse exemplo. Vamos fazer isso novamente, mas também vamos dar uma olhada no //performance//:
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH
Esse comando produz um binário wasm de 3 MB. Como se pode imaginar, a maior parte disso são informações de depuração. É possível verificar isso com a ferramenta llvm-objdump
[1], por exemplo:
$ llvm-objdump -h mandelbrot.wasm
mandelbrot.wasm: file format wasm
Sections:
Idx Name Size VMA Type
0 TYPE 0000026f 00000000
1 IMPORT 00001f03 00000000
2 FUNCTION 0000043e 00000000
3 TABLE 00000007 00000000
4 MEMORY 00000007 00000000
5 GLOBAL 00000021 00000000
6 EXPORT 0000014a 00000000
7 ELEM 00000457 00000000
8 CODE 0009308a 00000000 TEXT
9 DATA 0000e4cc 00000000 DATA
10 name 00007e58 00000000
11 .debug_info 000bb1c9 00000000
12 .debug_loc 0009b407 00000000
13 .debug_ranges 0000ad90 00000000
14 .debug_abbrev 000136e8 00000000
15 .debug_line 000bb3ab 00000000
16 .debug_str 000209bd 00000000
Essa saída mostra todas as seções que estão no arquivo Wasm gerado. A maioria delas são seções WebAssembly padrão, mas também há várias seções personalizadas com nome que começa com .debug_
. É aí que o binário contém nossas informações de depuração. Se somarmos todos os tamanhos, vemos que as informações de depuração compõem aproximadamente 2,3 MB do arquivo de 3 MB. Se também usarmos time
no comando emcc
, veremos que a máquina demorou cerca de 1,5 segundo para ser executada. Esses números representam uma pequena linha de base, mas são tão pequenos que provavelmente ninguém impressionaria eles. No entanto, em aplicativos reais, o binário de depuração pode facilmente atingir um tamanho em GB e levar minutos para ser criado!
Como ignorar Binaryen
Ao criar um aplicativo Wasm com Emscripten, uma das etapas de compilação finais é executar o otimizador Binaryen. O Binaryen é um kit de ferramentas do compilador que otimiza e legaliza binários do WebAssembly (semelhantes a). A execução do Binaryen como parte do build é bastante cara, mas só é necessária sob certas condições. Para builds de depuração, podemos acelerar significativamente o tempo de compilação se evitarmos a necessidade de passagens Binaryen. A passagem binária exigida mais comum é para legalizar assinaturas de funções que envolvem valores inteiros de 64 bits. Ao ativar a integração do BigInt do WebAssembly usando -sWASM_BIGINT
, podemos evitar isso.
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
Geramos a flag -sERROR_ON_WASM_CHANGES_AFTER_LINK
por uma boa medida. Ele ajuda a detectar quando o Binaryen está em execução e reescreve o binário inesperadamente. Dessa forma, podemos ter certeza de que estamos no caminho mais rápido.
Embora nosso exemplo seja bastante pequeno, ainda podemos notar o efeito de pular o Binaryen. De acordo com time
, esse comando é executado um pouco menos de 1 segundo, então meio segundo mais rápido do que antes.
Ajustes avançados
Ignorando a verificação do arquivo de entrada
Normalmente, ao vincular um projeto Emscripten, o emcc
verifica todos os arquivos e bibliotecas do objeto de entrada. Ele faz isso para implementar dependências precisas entre as funções da biblioteca JavaScript e os símbolos nativos no seu programa. Para projetos maiores, essa verificação extra dos arquivos de entrada (usando llvm-nm
) pode aumentar significativamente o tempo de vinculação.
É possível executar com -sREVERSE_DEPS=all
, que instrui o emcc
a incluir todas as dependências nativas possíveis das funções JavaScript. Isso tem uma pequena sobrecarga no tamanho do código, mas pode acelerar os tempos de vinculação e pode ser útil para builds de depuração.
Para um projeto tão pequeno quanto o nosso exemplo, isso não faz diferença real, mas se você tiver centenas ou até milhares de arquivos de objeto no projeto, isso poderá melhorar significativamente os tempos de link.
Remoção da seção "nome"
Em projetos grandes, especialmente aqueles que usam muito o modelo C++, a seção “nome” do WebAssembly pode ser muito grande. No nosso exemplo, ele é apenas uma pequena fração do tamanho geral do arquivo (confira a saída de llvm-objdump
acima), mas em alguns casos pode ser muito significativo. Se a seção de “nome” do aplicativo for muito grande e as informações de depuração do anão forem suficientes para suas necessidades de depuração, pode ser vantajoso remover a seção “nome”:
$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm
Isso removerá a seção “name” do WebAssembly, preservando as seções de depuração do DWARF.
Fissão de depuração
Binários com muitos dados de depuração não pressionam apenas o tempo de compilação, mas também o tempo de depuração. O depurador precisa carregar os dados e criar um índice para eles, para que possa responder rapidamente a consultas, como "Qual é o tipo da variável local x?".
A fissão de depuração permite dividir as informações de depuração de um binário em duas partes: uma, que permanece no binário, e a outra, contida em um arquivo separado, chamado de objeto DWARF (.dwo
). Ela pode ser ativada transmitindo a flag -gsplit-dwarf
para a Emscripten:
$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
Mostramos abaixo os diferentes comandos e quais arquivos são gerados pela compilação sem dados de depuração, com dados de depuração e, por fim, com os dados de depuração e a fissão de depuração.
Ao dividir os dados DWARF, uma parte dos dados de depuração reside com o binário, enquanto a parte grande é colocada no arquivo mandelbrot.dwo
(como ilustrado acima).
Para mandelbrot
, temos apenas um arquivo de origem, mas geralmente os projetos são maiores do que isso e incluem mais de um arquivo. A fissão de depuração gera um arquivo .dwo
para cada um deles. Para que a versão Beta atual do depurador (0.1.6.1615) possa carregar essas informações de depuração divididas, precisamos agrupar todas elas em um pacote chamado DWARF (.dwp
) assim:
$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp
A criação do pacote DWARF a partir de objetos individuais tem a vantagem de que você só precisa disponibilizar um arquivo extra. Estamos trabalhando para também carregar todos os objetos individuais em uma versão futura.
O que tem com DWARF 5?
Você deve ter notado que inserimos outra flag no comando emcc
acima, -gdwarf-5
. Ativar a versão 5 dos símbolos DWARF, que atualmente não é o padrão, é outro truque para nos ajudar a começar a depuração mais rapidamente. Com ele, algumas informações são armazenadas no binário principal que a versão 4 padrão deixou de fora. Especificamente, podemos determinar o conjunto completo de arquivos de origem apenas do binário principal. Isso permite que o depurador realize ações básicas, como mostrar a árvore de origem completa e definir pontos de interrupção sem carregar e analisar os dados completos do símbolo. Isso torna a depuração com símbolos de divisão muito mais rápida. Por isso, sempre estamos usando as sinalizações de linha de comando -gsplit-dwarf
e -gdwarf-5
juntas.
Com o formato de depuração DWARF5, também obtemos acesso a outro recurso útil. Ele introduz um índice de nome nos dados de depuração que serão gerados ao transmitir a sinalização -gpubnames
:
$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
Durante uma sessão de depuração, as pesquisas de símbolos geralmente acontecem pesquisando uma entidade pelo nome, por exemplo, ao procurar uma variável ou um tipo. O índice de nome acelera essa pesquisa, apontando diretamente para a unidade de compilação que define esse nome. Sem um índice de nome, seria necessária uma pesquisa exaustiva de todos os dados de depuração para encontrar a unidade de compilação correta que define a entidade nomeada que estamos procurando.
Para os curiosos: análise dos dados de depuração
Você pode usar llvm-dwarfdump
para ter uma prévia dos dados do DWARF. Vamos fazer um teste:
llvm-dwarfdump mandelbrot.wasm
Isso nos dá uma visão geral das “Unidades de compilação” (mais ou menos, os arquivos de origem) para as quais temos informações de depuração. Neste exemplo, só temos as informações de depuração para mandelbrot.cc
. As informações gerais vão informar que temos uma unidade esqueleto, o que significa apenas que temos dados incompletos nesse arquivo, e que há um arquivo .dwo
separado contendo as informações de depuração restantes:
Também é possível conferir outras tabelas nesse arquivo, por exemplo, a tabela de linhas que mostra o mapeamento do bytecode do Wasm para as linhas C++ (tente usar llvm-dwarfdump -debug-line
).
Também podemos conferir as informações de depuração contidas no arquivo .dwo
separado:
llvm-dwarfdump mandelbrot.dwo
Texto longo, leia o resumo: Qual é a vantagem de usar a fissão de depuração?
Há várias vantagens em dividir as informações de depuração quando se trabalha com aplicativos grandes:
Vinculação mais rápida: o vinculador não precisa mais analisar todas as informações de depuração. Os vinculadores geralmente precisam analisar todos os dados DWARF que estão no binário. Ao retirar grandes partes das informações de depuração em arquivos separados, os vinculadores lidam com binários menores, o que resulta em tempos de vinculação mais rápidos (especialmente para aplicativos grandes).
Depuração mais rápida: o depurador pode pular a análise dos outros símbolos em arquivos
.dwo
/.dwp
para algumas pesquisas de símbolos. Para algumas pesquisas (como solicitações no mapeamento de linha de arquivos wasm-to-C++), não precisamos analisar os dados de depuração adicionais. Isso economiza tempo, sem precisar carregar e analisar os dados de depuração adicionais.
1: se você não tiver uma versão recente do llvm-objdump
no sistema e estiver usando o emsdk
, poderá encontrá-la no diretório emsdk/upstream/bin
.
Fazer o download dos canais de visualização
Use o Chrome Canary, Dev ou Beta como seu navegador de desenvolvimento padrão. Esses canais de pré-lançamento oferecem acesso aos recursos mais recentes do DevTools, testam as APIs modernas de plataformas da Web e encontram problemas no site antes dos usuários.
Entrar em contato com a equipe do Chrome DevTools
Use as opções a seguir para discutir os novos recursos e mudanças na publicação ou qualquer outra coisa relacionada ao DevTools.
- Envie uma sugestão ou feedback em crbug.com.
- Informe um problema do DevTools em Mais opções > Ajuda > Informar problemas no DevTools.
- Envie um tweet em @ChromeDevTools.
- Deixe comentários nos nossos vídeos do YouTube sobre a ferramenta DevTools ou nos vídeos do YouTube com dicas sobre o DevTools.