<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="pt-BR"><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://codesilva.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://codesilva.com/" rel="alternate" type="text/html" hreflang="pt-BR" /><updated>2026-05-23T17:08:14+00:00</updated><id>https://codesilva.com/feed.xml</id><title type="html">CodeSilva</title><subtitle>Bem-vindo ao meu blog pessoal, CodeSilva! Como um aspirante a Engenheiro de Software, estou aqui para compartilhar minhas experiências, percepções e ocasionais desventuras de programação.</subtitle><entry xml:lang="pt-BR"><title type="html">O Claude fez meu compilador em 3 minutos. E isso deveria me preocupar?</title><link href="https://codesilva.com/programacao/2026/03/29/o-claude-fez-meu-compilador-em-3-minutos-e-isso-deveria-me-preocupar.html" rel="alternate" type="text/html" title="O Claude fez meu compilador em 3 minutos. E isso deveria me preocupar?" /><published>2026-03-29T00:00:00+00:00</published><updated>2026-03-29T00:00:00+00:00</updated><id>https://codesilva.com/programacao/2026/03/29/o-claude-fez-meu-compilador-em-3-minutos-e-isso-deveria-me-preocupar</id><content type="html" xml:base="https://codesilva.com/programacao/2026/03/29/o-claude-fez-meu-compilador-em-3-minutos-e-isso-deveria-me-preocupar.html"><![CDATA[<p>Eu passei semanas construindo o <a href="https://github.com/geeksilva97/brainjuck">BrainJuck</a>. Um compilador de Brainfuck pra JVM, escrito na mão, em Node.js, sem dependência nenhuma. Semanas lendo hex dump, brigando com a StackMapTable, calculando offset de jump errado às duas da manhã. Eu <a href="/programacao/2026/03/09/eu-criei-um-compilador-para-jvm-so-para-provar-um-ponto">já escrevi sobre isso</a>.</p>

<p>Ontem eu abri o Claude Code e mandei um prompt. Um prompt só. Sem ir e voltar, sem corrigir, sem guiar. Colei e fui fazer café:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create a compiler from Brainfuck to JVM in Node.js with no dependencies. Full Node.js.

the interface should be ./compiler somefile.bf SomeFile

i should be able to run SomeFile just by running "java SomeFile"

every brainfuck command should be accepted and the produced .class must not have a warning.
You must test everything, from the parser to the generation of the .class.
</code></pre></div></div>

<p>Quando voltei, três minutos depois, o compilador estava pronto. 51 testes passando, <code class="language-plaintext highlighter-rouge">.class</code> válido, Hello World rodando sem um warning no stderr. Um prompt. Três minutos.</p>

<p>Minha primeira reação foi rir. A segunda foi ficar quieto.</p>

<h2 id="o-que-ele-gerou">O que ele gerou</h2>

<p>O compilador do Claude é um arquivo só, ~300 linhas. Parser, constant pool, geração de bytecode, StackMapTable, montagem do <code class="language-plaintext highlighter-rouge">.class</code> - tudo junto. Funciona. Os oito comandos do Brainfuck compilam corretamente pra bytecode JVM. Os loops geram <code class="language-plaintext highlighter-rouge">ifeq</code>/<code class="language-plaintext highlighter-rouge">goto</code> com offsets corretos. O <code class="language-plaintext highlighter-rouge">append_frame</code> na StackMapTable declara as variáveis locais certas. O <code class="language-plaintext highlighter-rouge">javap -v</code> mostra uma classe válida.</p>

<p>A suíte de testes é decente: testa o parser isolado, o constant pool, a geração de bytecode, a StackMapTable, a estrutura do <code class="language-plaintext highlighter-rouge">.class</code>, e faz 19 testes de integração que compilam Brainfuck, rodam com <code class="language-plaintext highlighter-rouge">java</code>, e verificam a saída. Hello World, loops aninhados, input, wrapping de célula, todos os 8 comandos juntos.</p>

<p>Funciona. Sem truque, sem gambiarra. Ele até acertou o <code class="language-plaintext highlighter-rouge">append_frame</code> de primeira - ok, na segunda tentativa. Na primeira ele usou <code class="language-plaintext highlighter-rouge">same_frame</code> e tomou um <code class="language-plaintext highlighter-rouge">VerifyError</code>, igual aconteceu comigo. Mas corrigiu sozinho em segundos.</p>

<h2 id="onde-o-meu-é-melhor">Onde o meu é melhor</h2>

<p>O BrainJuck não é só um compilador que funciona. Ele é um compilador que <strong>pensa</strong>.</p>

<p>O parser do Claude pega <code class="language-plaintext highlighter-rouge">+++</code> e gera três <code class="language-plaintext highlighter-rouge">iadd</code> separados. O meu parser combina em um <code class="language-plaintext highlighter-rouge">increment(3)</code> e gera um <code class="language-plaintext highlighter-rouge">sipush 3</code> + <code class="language-plaintext highlighter-rouge">iadd</code>. Menos bytecode, menos trabalho pra JVM.</p>

<p>O BrainJuck tem uma camada de IR entre o parsing e a geração de código. Isso significa que eu posso adicionar otimizações sem mexer no gerador de bytecode. O Claude foi direto do parsing pro bytecode - funciona, mas é rígido.</p>

<p>O meu rastreia a posição do ponteiro em tempo de compilação. <code class="language-plaintext highlighter-rouge">&gt;&gt;&gt;&lt;&lt;</code> vira <code class="language-plaintext highlighter-rouge">move_head(1)</code> com posição absoluta. O do Claude gera cinco <code class="language-plaintext highlighter-rouge">iinc</code> separados. De novo - funciona, mas é ingênuo.</p>

<p>A arquitetura tem separação real: <code class="language-plaintext highlighter-rouge">index.js</code> pro parser, <code class="language-plaintext highlighter-rouge">class_generator.js</code> pra montagem do <code class="language-plaintext highlighter-rouge">.class</code>, <code class="language-plaintext highlighter-rouge">helpers/jvm.js</code> pros opcodes. O do Claude enfiou tudo num arquivo. Pra 300 linhas até que dá. Pra evoluir, não dá.</p>

<h2 id="onde-o-dele-é-melhor">Onde o dele é melhor</h2>

<p>A cobertura de testes. Tenho que admitir.</p>

<p>Eu tenho 6 testes unitários e 1 teste de integração. O Claude gerou 51 testes cobrindo cada camada individualmente. Testa edge cases que eu nem pensei - loop vazio <code class="language-plaintext highlighter-rouge">[]</code>, loops consecutivos <code class="language-plaintext highlighter-rouge">[][]</code>, wrapping de célula, programa vazio.</p>

<p>Meu teste de integração é sólido - compila o Hello World, roda com <code class="language-plaintext highlighter-rouge">java</code>, verifica stdout e stderr. Mas é um cenário só. O Claude testou 19 cenários diferentes de integração.</p>

<h2 id="o-que-realmente-importa">O que realmente importa</h2>

<p>Aqui é onde a coisa fica honesta.</p>

<p>O compilador do Claude funciona. Se alguém me pedisse “preciso de um compilador de Brainfuck pra JVM até amanhã” e eu usasse o Claude, o trabalho estaria entregue. Ninguém olharia pro <code class="language-plaintext highlighter-rouge">.class</code> gerado e saberia que foi feito em 3 minutos.</p>

<p>Mas eu não saberia nada.</p>

<p>Eu não saberia que o <code class="language-plaintext highlighter-rouge">constant pool</code> é 1-indexed e que a entrada 0 não existe. Não saberia que <code class="language-plaintext highlighter-rouge">baload</code> faz sign-extend pra int. Não saberia que o slot 0 das variáveis locais é reservado pros argumentos do método. Não saberia que o <code class="language-plaintext highlighter-rouge">offset_delta</code> da StackMapTable é calculado em relação ao frame anterior, não ao início do método. Não saberia a diferença entre <code class="language-plaintext highlighter-rouge">same_frame</code> e <code class="language-plaintext highlighter-rouge">append_frame</code>, nem por que o primeiro frame de um método com branches precisa ser <code class="language-plaintext highlighter-rouge">append_frame</code> se você declarou variáveis locais depois da assinatura.</p>

<p>Nada disso. Minha cabeça estaria vazia.</p>

<p>Quando eu estava debugando o BrainJuck às duas da manhã, comparando hex dump com a spec da JVM, errando cálculo de offset pela décima vez - aquilo era conhecimento entrando. Cada <code class="language-plaintext highlighter-rouge">VerifyError</code> era uma lição. Cada byte errado no hex dump que eu encontrava era uma conexão nova no meu cérebro.</p>

<p>O Claude não precisou debugar nada disso. Ele já sabia. Ele foi treinado com a spec da JVM, com milhares de implementações parecidas, com décadas de conhecimento acumulado. Pra ele, gerar uma StackMapTable correta é interpolação. Pra mim, foi o chefe de fase do projeto.</p>

<h2 id="não-é-sobre-a-ia-ser-ruim">Não é sobre a IA ser ruim</h2>

<p>Eu uso IA pra programar. Uso Claude Code praticamente todo dia. Não sou contra, não acho que vai destruir a profissão, não tenho medo de perder o emprego. Que fique claro.</p>

<p>Mas tem uma diferença entre usar a IA pra acelerar um trabalho que você entende e usar a IA pra fazer um trabalho que você não entende. No primeiro caso, você ganha tempo. No segundo, você ganha uma ilusão.</p>

<p>O BrainJuck demorou semanas. O compilador do Claude demorou 3 minutos. O resultado final é parecido - os dois geram <code class="language-plaintext highlighter-rouge">.class</code> válido, os dois compilam Hello World, os dois passam no verificador da JVM. Mas depois daquelas semanas, eu sei como a JVM funciona por dentro. Sei ler bytecode, sei o que um <code class="language-plaintext highlighter-rouge">VerifyError</code> significa, sei dissecar um <code class="language-plaintext highlighter-rouge">.class</code> com <code class="language-plaintext highlighter-rouge">xxd</code>. Essas semanas me deram algo que nenhum prompt dá.</p>

<p>O compilador do Claude é melhor testado que o meu. É mais rápido de produzir. Se eu quisesse, eu poderia pegar o código dele e melhorar - adicionar as otimizações que o meu tem, separar em módulos, evoluir a arquitetura.</p>

<p>Mas eu nunca teria a base pra fazer isso se não tivesse passado por aquelas semanas primeiro.</p>

<h2 id="o-teste-real">O teste real</h2>

<p>Se eu te der o compilador do Claude e pedir pra você adicionar uma otimização - combinar <code class="language-plaintext highlighter-rouge">[-]</code> num <code class="language-plaintext highlighter-rouge">clear cell</code> direto no bytecode - você conseguiria? Se aparecer um <code class="language-plaintext highlighter-rouge">VerifyError</code> novo, você saberia por onde começar?</p>

<p>Se você construiu o seu, a resposta é sim. Se a IA construiu pra você, a resposta honesta é provavelmente não.</p>

<p>Esse é o ponto. Não é que a IA faz código ruim. O código é bom. É que código bom que você não entende é tão útil quanto código ruim que você não entende. Nos dois casos, quando quebrar, você tá perdido.</p>

<h2 id="pra-quem-tá-aprendendo">Pra quem tá aprendendo</h2>

<p>Se você é dev e quer entender compiladores, máquinas virtuais, bytecode - faz na mão. Pelo menos uma vez. Não precisa ser Brainfuck, não precisa ser JVM. Pega qualquer linguagem simples e compila pra qualquer target. O importante é passar pelo processo.</p>

<p>Depois que você entendeu, aí sim - usa a IA pra ir mais rápido, pra testar mais cenários, pra explorar variações. A IA é uma ferramenta absurda quando você sabe o que tá fazendo. Quando você não sabe, ela é só um gerador de confiança falsa.</p>

<p>O <a href="https://github.com/geeksilva97/brainjuck">BrainJuck</a> tá no GitHub. Lê o código, brinque, quebre. E se der vontade, faz o seu.</p>

<p>Por hoje é só. Abraços.</p>]]></content><author><name></name></author><category term="programacao" /><category term="compiladores" /><category term="jvm" /><category term="brainfuck" /><category term="ia" /><category term="claude" /><summary type="html"><![CDATA[Eu passei semanas construindo o BrainJuck. Um compilador de Brainfuck pra JVM, escrito na mão, em Node.js, sem dependência nenhuma. Semanas lendo hex dump, brigando com a StackMapTable, calculando offset de jump errado às duas da manhã. Eu já escrevi sobre isso.]]></summary></entry><entry xml:lang="en-US"><title type="html">AbortSignal.any(), FinalizationRegistry, and the WeakCell Leak</title><link href="https://codesilva.com/nodejs-apocrypha/node-internals/2026/03/21/nodejs-apocrypha-abortsignal-any-and-the-weakcell-leak.html" rel="alternate" type="text/html" title="AbortSignal.any(), FinalizationRegistry, and the WeakCell Leak" /><published>2026-03-21T00:00:00+00:00</published><updated>2026-03-21T00:00:00+00:00</updated><id>https://codesilva.com/nodejs-apocrypha/node-internals/2026/03/21/nodejs-apocrypha-abortsignal-any-and-the-weakcell-leak</id><content type="html" xml:base="https://codesilva.com/nodejs-apocrypha/node-internals/2026/03/21/nodejs-apocrypha-abortsignal-any-and-the-weakcell-leak.html"><![CDATA[<p>How a one-word change in V8’s source code caused <code class="language-plaintext highlighter-rouge">AbortSignal.any()</code> to leak memory in Node.js 26.</p>

<p>This is the story of a bug that looked like a Node.js regression but turned out to be a V8 garbage collection change. It took bisecting across Node commits, rebuilding with different V8 versions, analyzing heap snapshots, and finally patching V8’s Torque source to prove the root cause.</p>

<h2 id="the-bug">The bug</h2>

<p>The report was straightforward. This code leaks memory on Node 24+ but works fine on Node 22:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ac</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AbortController</span><span class="p">();</span>

<span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">function</span> <span class="nf">run</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">AbortSignal</span><span class="p">.</span><span class="nf">any</span><span class="p">([</span><span class="nx">ac</span><span class="p">.</span><span class="nx">signal</span><span class="p">]);</span>

  <span class="k">if </span><span class="p">(</span><span class="o">++</span><span class="nx">i</span> <span class="o">%</span> <span class="mi">100</span><span class="nx">_000</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">mem</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nf">memoryUsage</span><span class="p">().</span><span class="nx">rss</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="p">;</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">i</span><span class="p">}</span><span class="s2"> - </span><span class="p">${</span><span class="nx">mem</span><span class="p">.</span><span class="nf">toFixed</span><span class="p">(</span><span class="mi">2</span><span class="p">)}</span><span class="s2"> MiB`</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nf">setImmediate</span><span class="p">(</span><span class="nx">run</span><span class="p">);</span>
<span class="p">}</span>

<span class="nf">run</span><span class="p">();</span>
</code></pre></div></div>

<p>On Node 22 (V8 12.4): memory stable at ~97 MiB.
On Node 26 (V8 14.3): memory grows linearly past 2 GB until the process crashes.</p>

<h2 id="how-abortsignalany-works-internally">How AbortSignal.any() works internally</h2>

<p>When you call <code class="language-plaintext highlighter-rouge">AbortSignal.any([signal1, signal2])</code>, Node creates a new “composite” signal that aborts when any of the source signals abort. Internally, this involves:</p>

<ol>
  <li>Create a new <code class="language-plaintext highlighter-rouge">AbortSignal</code> (the composite result)</li>
  <li>For each source signal, add a <code class="language-plaintext highlighter-rouge">WeakRef</code> to the result in <code class="language-plaintext highlighter-rouge">kDependantSignals</code></li>
  <li>Register with a <code class="language-plaintext highlighter-rouge">FinalizationRegistry</code> so that when the composite signal is GC’d, the dead <code class="language-plaintext highlighter-rouge">WeakRef</code> gets cleaned up from the source’s <code class="language-plaintext highlighter-rouge">kDependantSignals</code></li>
</ol>

<p>Step 3 is where the problem lives. Each call to <code class="language-plaintext highlighter-rouge">AbortSignal.any()</code> calls <code class="language-plaintext highlighter-rouge">FinalizationRegistry.register()</code>, which creates a V8-internal <code class="language-plaintext highlighter-rouge">WeakCell</code> object.</p>

<p><img src="/assets/images/nodejs-apocrypha/abortsignal-any-leak-flow.png" alt="How AbortSignal.any() accumulates WeakRefs and WeakCells over repeated calls" /></p>

<h2 id="what-a-weakcell-is">What a WeakCell is</h2>

<p><code class="language-plaintext highlighter-rouge">FinalizationRegistry</code> is a JavaScript API that lets you register a callback to run when an object is garbage collected. Under the hood, V8 tracks this with <code class="language-plaintext highlighter-rouge">WeakCell</code> objects - internal structures that hold a weak reference to the target, the held value, and links into the registry’s internal lists.</p>

<p>When you call <code class="language-plaintext highlighter-rouge">registry.register(target, heldValue)</code>, V8 creates a <code class="language-plaintext highlighter-rouge">WeakCell</code> and adds it to the registry. When <code class="language-plaintext highlighter-rouge">target</code> is GC’d, V8 moves the <code class="language-plaintext highlighter-rouge">WeakCell</code> to the “cleared cells” list and schedules the cleanup callback.</p>

<p>The key question is: where does V8 allocate this <code class="language-plaintext highlighter-rouge">WeakCell</code>?</p>

<h2 id="the-v8-change">The V8 change</h2>

<p>In V8 12.4, the <code class="language-plaintext highlighter-rouge">WeakCell</code> allocation in <code class="language-plaintext highlighter-rouge">src/builtins/finalization-registry.tq</code> looked like this:</p>

<pre><code class="language-torque">// Allocate the WeakCell object in the old space, because 1) WeakCell weakness
// handling is only implemented in the old space 2) they're supposedly
// long-living. TODO(marja, gsathya): Support WeakCells in Scavenger.
const cell = new (Pretenured) WeakCell{
    map: GetWeakCellMap(),
    finalization_registry: finalizationRegistry,
    target: target,
    holdings: heldValue,
    // ...
};
</code></pre>

<p><code class="language-plaintext highlighter-rouge">Pretenured</code> means “allocate directly in old space, skip the nursery.”</p>

<p>In September 2025, V8 commit <a href="https://chromium.googlesource.com/v8/v8/+/5abdd62d579b7f6e1403dc1c41619f736d7cdc62"><code class="language-plaintext highlighter-rouge">5abdd62d579b</code></a> by Omer Katz removed this flag:</p>

<pre><code class="language-torque">const cell = new WeakCell{
    map: GetWeakCellMap(),
    finalization_registry: finalizationRegistry,
    target: target,
    holdings: heldValue,
    // ...
};
</code></pre>

<p>The commit message: <em>“Young WeakCells are supported out of the box and there’s no correctness need to pretenure them. Having WeakCell allocated as young also simplifies followup planned changes.”</em></p>

<p>The associated bug is <a href="https://issues.chromium.org/issues/340777103">chromium:340777103</a>: “FinalizationRegistry-s are treated as strong roots on minor GCs.”</p>

<h2 id="young-generation-vs-old-generation">Young generation vs old generation</h2>

<p>V8’s heap is split into two regions with different garbage collection strategies.</p>

<p><img src="/assets/images/nodejs-apocrypha/v8-heap-generations.png" alt="V8 heap layout showing young generation and old generation" /></p>

<p>The <strong>young generation</strong> (also called the nursery) is small - a few megabytes. Most objects are allocated here. V8 collects it with the <strong>scavenger</strong> (minor GC), which runs fast and frequently. Objects that survive a scavenge get <strong>promoted</strong> to old generation.</p>

<p>The <strong>old generation</strong> is large - up to whatever <code class="language-plaintext highlighter-rouge">--max-old-space-size</code> allows. It’s collected by the <strong>mark-compact</strong> collector (major GC), which is slower but thorough. Long-lived objects end up here.</p>

<p>Most of the time, short-lived objects are born in young space, die there, and the scavenger reclaims them cheaply. That’s the generational hypothesis: most objects die young.</p>

<p><code class="language-plaintext highlighter-rouge">Pretenured</code> bypasses this entirely. The object goes straight to old generation, skipping the nursery. V8 originally did this for <code class="language-plaintext highlighter-rouge">WeakCell</code> because <em>“WeakCell weakness handling is only implemented in the old space”</em> - the scavenger didn’t know how to deal with them. The comment in the code had a TODO saying to fix this eventually.</p>

<p>When V8 did add scavenger support for WeakCells, they removed <code class="language-plaintext highlighter-rouge">Pretenured</code>. WeakCells now land in young generation like everything else.</p>

<p><img src="/assets/images/nodejs-apocrypha/weakcell-pretenured-vs-young.png" alt="Comparison of WeakCell allocation: Pretenured going to old space vs young generation pileup" /></p>

<h2 id="why-this-causes-the-leak">Why this causes the leak</h2>

<p>The bug title says it: <em>“FinalizationRegistry-s are treated as strong roots on minor GCs.”</em></p>

<p>During a scavenge (minor GC), the <code class="language-plaintext highlighter-rouge">FinalizationRegistry</code> treats its active <code class="language-plaintext highlighter-rouge">WeakCell</code> entries as <strong>strong roots</strong>. This means the scavenger won’t collect them - it promotes them to old generation instead. So the cycle looks like:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">AbortSignal.any()</code> is called</li>
  <li><code class="language-plaintext highlighter-rouge">FinalizationRegistry.register()</code> creates a <code class="language-plaintext highlighter-rouge">WeakCell</code> in young generation</li>
  <li>Minor GC runs - the <code class="language-plaintext highlighter-rouge">WeakCell</code> is a strong root, so it survives and gets promoted</li>
  <li>The composite signal (the <code class="language-plaintext highlighter-rouge">WeakCell</code>’s target) also can’t be collected while the <code class="language-plaintext highlighter-rouge">WeakCell</code> is alive in young space</li>
  <li>Eventually a major GC runs and clears everything</li>
  <li>But at high call rates, steps 1-4 repeat faster than step 5</li>
</ol>

<p>With <code class="language-plaintext highlighter-rouge">Pretenured</code>, step 2 put the <code class="language-plaintext highlighter-rouge">WeakCell</code> directly in old space. There was no young-generation pressure, no promotion overhead, and old-space GC handled everything efficiently.</p>

<h2 id="proving-it">Proving it</h2>

<p>We can prove this by patching V8 14.3 to restore <code class="language-plaintext highlighter-rouge">Pretenured</code>. One word change in <code class="language-plaintext highlighter-rouge">deps/v8/src/builtins/finalization-registry.tq</code>:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">-  const cell = new WeakCell{
</span><span class="gi">+  const cell = new (Pretenured) WeakCell{
</span></code></pre></div></div>

<p>Rebuild Node and run the reproduction:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>V8 14.3 (stock):           V8 14.3 (Pretenured restored):
100k: 159 MiB              100k: 147 MiB
200k: 207 MiB              200k: 159 MiB
300k: 254 MiB              300k: 161 MiB
400k: 304 MiB              400k: 163 MiB
500k: 359 MiB (growing)    500k: 162 MiB (stable)
</code></pre></div></div>

<p>One word. That’s the difference between a stable 162 MiB and unbounded growth past 2 GB.</p>

<h2 id="the-heap-snapshot">The heap snapshot</h2>

<p>A heap snapshot after 200k calls + explicit GC on stock V8 14.3 shows what’s being retained:</p>

<table>
  <thead>
    <tr>
      <th>Size</th>
      <th>Count</th>
      <th>Object</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>27.5 MiB</td>
      <td>400,011</td>
      <td><code class="language-plaintext highlighter-rouge">system / WeakCell</code> (V8 internal)</td>
    </tr>
    <tr>
      <td>16.8 MiB</td>
      <td>200,002</td>
      <td><code class="language-plaintext highlighter-rouge">AbortSignal</code></td>
    </tr>
    <tr>
      <td>12.2 MiB</td>
      <td>400,026</td>
      <td><code class="language-plaintext highlighter-rouge">Map</code> (EventTarget internals)</td>
    </tr>
    <tr>
      <td>12.2 MiB</td>
      <td>400,013</td>
      <td><code class="language-plaintext highlighter-rouge">WeakRef</code></td>
    </tr>
    <tr>
      <td>6.1 MiB</td>
      <td>200,022</td>
      <td><code class="language-plaintext highlighter-rouge">Set</code> (kSourceSignals)</td>
    </tr>
  </tbody>
</table>

<p>200k <code class="language-plaintext highlighter-rouge">AbortSignal</code> objects survive GC. They should be unreachable, but the <code class="language-plaintext highlighter-rouge">WeakCell</code> entries keep them alive through the strong-root treatment during scavenges.</p>

<h2 id="what-to-take-away">What to take away</h2>

<ol>
  <li>
    <p><code class="language-plaintext highlighter-rouge">FinalizationRegistry</code> is not free. Each <code class="language-plaintext highlighter-rouge">register()</code> call creates a V8-internal <code class="language-plaintext highlighter-rouge">WeakCell</code>. On current V8, these WeakCells are young-generation objects treated as strong roots during minor GC.</p>
  </li>
  <li>
    <p>Don’t use <code class="language-plaintext highlighter-rouge">FinalizationRegistry</code> in hot paths. If you’re calling <code class="language-plaintext highlighter-rouge">register()</code> thousands of times per second, you’ll outpace the garbage collector. Use it as a safety net, not as your primary cleanup mechanism.</p>
  </li>
  <li>
    <p>When debugging memory leaks across Node versions, check the V8 version. The same JavaScript code can behave differently because of GC implementation changes that aren’t visible from the JS side.</p>
  </li>
  <li>
    <p>Heap snapshots show V8 internals. The <code class="language-plaintext highlighter-rouge">system / WeakCell</code> entries in a heap snapshot are not visible from JavaScript - they’re V8’s internal bookkeeping. But they consume real memory.</p>
  </li>
</ol>]]></content><author><name></name></author><category term="[&quot;nodejs-apocrypha&quot;, &quot;node-internals&quot;]" /><category term="node-internals" /><category term="v8" /><category term="memory" /><category term="gc" /><summary type="html"><![CDATA[How a one-word change in V8’s source code caused AbortSignal.any() to leak memory in Node.js 26.]]></summary></entry><entry xml:lang="pt-BR"><title type="html">O Event Loop do Node não é o que te ensinaram</title><link href="https://codesilva.com/nodejs/2026/03/20/o-event-loop-do-node-nao-e-o-que-te-ensinaram.html" rel="alternate" type="text/html" title="O Event Loop do Node não é o que te ensinaram" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://codesilva.com/nodejs/2026/03/20/o-event-loop-do-node-nao-e-o-que-te-ensinaram</id><content type="html" xml:base="https://codesilva.com/nodejs/2026/03/20/o-event-loop-do-node-nao-e-o-que-te-ensinaram.html"><![CDATA[<p>Era 2025 e eu queria ver o <code class="language-plaintext highlighter-rouge">event loop</code> funcionando com meus próprios olhos. Não num diagrama, não num artigo - no código.</p>

<p>Eu já era colaborador do Node na época. Mas tinha uma diferença entre saber explicar o <code class="language-plaintext highlighter-rouge">event loop</code> e ter visto ele rodar. Eu queria a segunda coisa.</p>

<p>Daí eu fiz o que qualquer pessoa razoável faria: compilei o Node do zero, compilei o libuv do zero, enfiei <code class="language-plaintext highlighter-rouge">console.log</code> e <code class="language-plaintext highlighter-rouge">std::cout</code> em tudo que é canto do código fonte e fiquei olhando o que acontecia.</p>

<h2 id="o-que-todo-mundo-te-ensina-e-o-que-tá-errado">O que todo mundo te ensina (e o que tá errado)</h2>

<p>Se você pesquisar “Node.js event loop” vai encontrar mil diagramas bonitos. A maioria te mostra uma caixa com uma fila de eventos, uma <code class="language-plaintext highlighter-rouge">call stack</code>, e setas girando. Parece simples. Parece limpo.</p>

<p><strong>É mentira.</strong></p>

<p align="center">
  <img src="/assets/images/event-loop-common-diagram.png" alt="Diagrama simplificado do event loop que a maioria dos artigos ensina" />
</p>

<p>Não é uma fila simples. Não é uma pilha girando. E muitas operações de rede nem passam por threads separadas - rodam direto no kernel do sistema operacional.</p>

<p>Quem me abriu os olhos pra isso foi o Bert Belder - um dos criadores do libuv - numa palestra de 2017:</p>

<blockquote>
  <p><a href="https://www.youtube.com/watch?v=PNa9OMajw9w">Morning Keynote: Everything You Need to Know About Node.js Event Loop - Bert Belder, IBM</a></p>
</blockquote>

<p>Tem 9 anos. Ele começa corrigindo exatamente esses equívocos que a gente repete sem questionar. Eu já tinha assistido dezenas de vídeos sobre <code class="language-plaintext highlighter-rouge">event loop</code> e nenhum chegou perto desse.</p>

<h2 id="a-estrutura-real-do-event-loop">A estrutura real do event loop</h2>

<p>O que o Belder explica - e que eu confirmei enfiando prints no código - é que o <code class="language-plaintext highlighter-rouge">event loop</code> do Node tem fases bem definidas. Não é uma fila genérica. Cada volta do loop passa por:</p>

<ol>
  <li><strong>Timers</strong> - verifica se algum <code class="language-plaintext highlighter-rouge">setTimeout</code> ou <code class="language-plaintext highlighter-rouge">setInterval</code> expirou</li>
  <li><strong>I/O</strong> (o Belder chama de “Unicorn”) - a fase principal, onde o libuv lida com rede, disco e processos filhos</li>
  <li><strong>setImmediate</strong> - executa os callbacks agendados com <code class="language-plaintext highlighter-rouge">setImmediate</code></li>
  <li><strong>Close handlers</strong> - limpeza de sockets fechados</li>
</ol>

<p>E aqui vem o que pra mim é o maior gotcha do <code class="language-plaintext highlighter-rouge">event loop</code>: <strong>entre cada fase, código JavaScript é executado</strong>. O Node drena a fila de <code class="language-plaintext highlighter-rouge">microtasks</code> do V8 (Promises resolvidas) e também os callbacks de <code class="language-plaintext highlighter-rouge">process.nextTick</code>. Ou seja, entre Timers e I/O, entre I/O e <code class="language-plaintext highlighter-rouge">setImmediate</code>, entre <code class="language-plaintext highlighter-rouge">setImmediate</code> e Close handlers - sempre tem esse passo intermediário onde JS roda.</p>

<p>A maioria dos diagramas que você encontra por aí não mostra isso. Eles te dão as 4 fases bonitinhas e pronto. Mas na prática, o libuv não sabe nada sobre <code class="language-plaintext highlighter-rouge">microtasks</code> - isso é conceito do V8. O libuv cuida das fases do loop, e o Node é quem costura as duas coisas: depois que cada fase do libuv termina, o Node pede pro V8 drenar as <code class="language-plaintext highlighter-rouge">microtasks</code> pendentes e drena o <code class="language-plaintext highlighter-rouge">nextTick</code> também. É nessa costura que mora a confusão.</p>

<p align="center">
  <img src="/assets/images/event-loop-simplified-diagram.png" alt="Diagrama real do event loop com as fases e microtasks entre cada fase" />
</p>

<blockquote>
  <p><strong>NOTA:</strong> O <code class="language-plaintext highlighter-rouge">thread pool</code> do libuv (geralmente 4 threads) só entra em ação pra operações que o sistema operacional não consegue fazer de forma assíncrona nativamente - como manipulação de arquivos e pesquisas DNS. Operações de rede usam mecanismos do kernel como <code class="language-plaintext highlighter-rouge">epoll</code> (Linux) e <code class="language-plaintext highlighter-rouge">kqueue</code> (macOS) diretamente.</p>
</blockquote>

<h2 id="prints-em-c-e-javascript">Prints em C++ e JavaScript</h2>

<p>O jeito “certo” de fazer isso seria com <code class="language-plaintext highlighter-rouge">lldb</code> (no macOS) - breakpoints, inspecionar a stack, seguir o fluxo sem modificar o código. Hoje em dia com IA fica ainda mais fácil: você pode pedir pro Claude usar <code class="language-plaintext highlighter-rouge">lldb</code> pra debugar, analisar <code class="language-plaintext highlighter-rouge">stack traces</code>, navegar pelo código fonte. Eu mesmo usei IA assim pra resolver <code class="language-plaintext highlighter-rouge">segmentation faults</code> enquanto trabalhava em implementações no Node.</p>

<p>Mas pra esse experimento eu escolhi <code class="language-plaintext highlighter-rouge">console.log</code> e <code class="language-plaintext highlighter-rouge">std::cout</code>. A técnica mais primitiva que existe. Qualquer pessoa sabe colocar um print - e pra entender o fluxo do <code class="language-plaintext highlighter-rouge">event loop</code> era mais que suficiente.</p>

<p>Em <code class="language-plaintext highlighter-rouge">src/api/embed_helpers.cc</code>, no coração do loop:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">do</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">env</span><span class="o">-&gt;</span><span class="n">is_stopping</span><span class="p">())</span> <span class="k">break</span><span class="p">;</span>
  <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"node called uv_run() in SpinEventLoopInternal"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
  <span class="n">uv_run</span><span class="p">(</span><span class="n">env</span><span class="o">-&gt;</span><span class="n">event_loop</span><span class="p">(),</span> <span class="n">UV_RUN_DEFAULT</span><span class="p">);</span>

  <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Gotta drain tasks"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
  <span class="n">platform</span><span class="o">-&gt;</span><span class="n">DrainTasks</span><span class="p">(</span><span class="n">isolate</span><span class="p">);</span>

  <span class="n">more</span> <span class="o">=</span> <span class="n">uv_loop_alive</span><span class="p">(</span><span class="n">env</span><span class="o">-&gt;</span><span class="n">event_loop</span><span class="p">());</span>
  <span class="c1">// ...</span>
<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">more</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">env</span><span class="o">-&gt;</span><span class="n">is_stopping</span><span class="p">());</span>
</code></pre></div></div>

<p>Em <code class="language-plaintext highlighter-rouge">src/api/callback.cc</code>, onde os ticks são processados:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">tick_info</span><span class="o">-&gt;</span><span class="n">has_tick_scheduled</span><span class="p">())</span> <span class="p">{</span>
  <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"No tick scheduled, draining microtask queue"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
  <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetMicrotaskQueue</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">PerformCheckpoint</span><span class="p">(</span><span class="n">isolate</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// ...</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Tick callback being called from C++"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
</code></pre></div></div>

<p>No lado JavaScript, em <code class="language-plaintext highlighter-rouge">lib/internal/process/task_queues.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">processTicksAndRejections</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">tock</span><span class="p">;</span>
  <span class="c1">// all the next tick callbacks are processed here</span>
  <span class="k">do</span> <span class="p">{</span>
    <span class="k">while </span><span class="p">((</span><span class="nx">tock</span> <span class="o">=</span> <span class="nx">queue</span><span class="p">.</span><span class="nf">shift</span><span class="p">())</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// ...</span>
      <span class="nf">callback</span><span class="p">();</span>
      <span class="nx">qLength</span><span class="o">--</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nf">runMicrotasks</span><span class="p">();</span>
  <span class="p">}</span> <span class="k">while </span><span class="p">(</span><span class="o">!</span><span class="nx">queue</span><span class="p">.</span><span class="nf">isEmpty</span><span class="p">()</span> <span class="o">||</span> <span class="nf">processPromiseRejections</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Daí eu criei scripts de teste pra observar a ordem de execução. Que nem esse aqui:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span> <span class="nx">styleText</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">node:util</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">function</span> <span class="nf">printMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nf">styleText</span><span class="p">([</span><span class="dl">'</span><span class="s1">cyanBright</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">bold</span><span class="dl">'</span><span class="p">],</span> <span class="s2">`\t\t\t\t</span><span class="p">${</span><span class="nx">message</span><span class="p">}</span><span class="s2">\n`</span><span class="p">)</span>
  <span class="nx">process</span><span class="p">.</span><span class="nx">stdout</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">new</span> <span class="nc">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(0) Promise constructor was called</span><span class="dl">'</span><span class="p">)</span>
  <span class="nf">resolve</span><span class="p">(</span><span class="dl">'</span><span class="s1">(1) Promise resolved</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}).</span><span class="nf">then</span><span class="p">(</span><span class="nx">printMessage</span><span class="p">);</span>

<span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(2) settimeout was called</span><span class="dl">'</span><span class="p">);</span>
  <span class="nx">process</span><span class="p">.</span><span class="nf">nextTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(2.1) nextTick inside setTimeout was called</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">},</span> <span class="mi">100</span><span class="p">);</span>

<span class="nf">setImmediate</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(3) setImmediate was called</span><span class="dl">'</span><span class="p">);</span>
  <span class="nx">process</span><span class="p">.</span><span class="nf">nextTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(3.1) nextTick inside setImmediate was called</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">});</span>

<span class="nx">process</span><span class="p">.</span><span class="nf">nextTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(4) nextTick was called</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>

<span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(5) This log comes first</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>

<p>Executando com meu Node compilado cheio de prints eu conseguia ver o que tava acontecendo por baixo. Cada <code class="language-plaintext highlighter-rouge">std::cout</code> no C++ casava com o que eu via no JavaScript. A ficha caiu.</p>

<h2 id="nexttick-o-nome-mais-mentiroso-do-node">nextTick: o nome mais mentiroso do Node</h2>

<p>Agora a parte que me incomodou.</p>

<p>O <code class="language-plaintext highlighter-rouge">process.nextTick</code> não executa no “próximo tick”. Ele executa <strong>no tick atual</strong>. O nome é uma mentira descarada.</p>

<p>Olha o que acontece quando você aninha <code class="language-plaintext highlighter-rouge">nextTick</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">process</span><span class="p">.</span><span class="nf">nextTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(1) nextTick was called</span><span class="dl">'</span><span class="p">);</span>

  <span class="nx">process</span><span class="p">.</span><span class="nf">nextTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(2) inner nextTick was called</span><span class="dl">'</span><span class="p">);</span>

    <span class="nx">process</span><span class="p">.</span><span class="nf">nextTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(4) inner-inner nextTick was called</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">});</span>

<span class="nf">setImmediate</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(3) setImmediate was called</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">setImmediate</code> ali no (3)? Ele não executa enquanto tiver <code class="language-plaintext highlighter-rouge">nextTick</code> pendente. Porque cada <code class="language-plaintext highlighter-rouge">nextTick</code> novo que é agendado dentro de outro <code class="language-plaintext highlighter-rouge">nextTick</code> entra na mesma fila que tá sendo drenada naquele momento. É que nem ir ao supermercado e a cada item que você coloca no carrinho aparecer mais dois na lista - você nunca sai do corredor.</p>

<p>Isso é por design, claro. O <code class="language-plaintext highlighter-rouge">nextTick</code> drena completamente antes de qualquer fase do <code class="language-plaintext highlighter-rouge">event loop</code> continuar. Mas chamar isso de “<strong>next</strong> tick”? É como chamar o freio de mão de “acelerador reserva”.</p>

<h2 id="eu-corrigi-o-node">Eu “corrigi” o Node</h2>

<p>Já que eu tava com o código aberto na minha frente, pensei: por que não?</p>

<p>Criei o <code class="language-plaintext highlighter-rouge">process.nesteTick</code>. Faz a mesma coisa que o <code class="language-plaintext highlighter-rouge">nextTick</code>, mas com um nome honesto.</p>

<p>“Neste” em português significa “in this” - ou seja, <code class="language-plaintext highlighter-rouge">nesteTick</code> = “neste tick” = “neste ciclo atual”. Que é exatamente o que ele faz.</p>

<p>Em <code class="language-plaintext highlighter-rouge">lib/internal/process/task_queues.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">nesteTick</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
  <span class="nf">nextTick</span><span class="p">(</span><span class="nx">callback</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>E registrei em <code class="language-plaintext highlighter-rouge">lib/internal/bootstrap/node.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span> <span class="nx">nextTick</span><span class="p">,</span> <span class="nx">runNextTicks</span><span class="p">,</span> <span class="nx">nesteTick</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">setupTaskQueue</span><span class="p">();</span>
<span class="nx">process</span><span class="p">.</span><span class="nx">nextTick</span> <span class="o">=</span> <span class="nx">nextTick</span><span class="p">;</span>
<span class="nx">process</span><span class="p">.</span><span class="nx">nesteTick</span> <span class="o">=</span> <span class="nx">nesteTick</span><span class="p">;</span>
</code></pre></div></div>

<p align="center">
  <img src="/assets/images/event-loop-terminal-output.jpg" alt="Executando o script i-fixed-node-next-tick.js no Node compilado - nesteTick callbacks executam antes do setImmediate" />
</p>

<p>Daí o script de demonstração:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">process</span><span class="p">.</span><span class="nf">nesteTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(1) nesteTick was called</span><span class="dl">'</span><span class="p">);</span>

  <span class="nx">process</span><span class="p">.</span><span class="nf">nesteTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(2) inner nesteTick was called</span><span class="dl">'</span><span class="p">);</span>

    <span class="nx">process</span><span class="p">.</span><span class="nf">nesteTick</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(4) inner-inner nesteTick was called</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">});</span>

<span class="nf">setImmediate</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">printMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">(3) setImmediate was called</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Funciona igualzinho. Mas agora o nome não te engana.</p>

<h2 id="o-que-ficou-depois">O que ficou depois</h2>

<p>Eu poderia ter lido mais 10 artigos sobre <code class="language-plaintext highlighter-rouge">event loop</code>. Poderia ter assistido mais 20 vídeos. Mas nada substituiu abrir o código, compilar, e ver as coisas acontecendo.</p>

<p>Colocar uns prints no código fonte e observar o fluxo? Você para de repetir frases decoradas e começa a saber.</p>

<p>Se você trabalha com Node todo dia e nunca olhou o código fonte - nem que seja com <code class="language-plaintext highlighter-rouge">grep</code> pra achar onde as coisas acontecem - tenta. O código do Node é mais legível do que você imagina.</p>

<h2 id="recursos">Recursos</h2>

<ul>
  <li><a href="https://www.youtube.com/watch?v=PNa9OMajw9w">Morning Keynote: Everything You Need to Know About Node.js Event Loop - Bert Belder</a></li>
  <li><a href="https://github.com/geeksilva97/node/tree/playing-with-node">Meu branch com os experimentos</a> - o código com todos os prints e o <code class="language-plaintext highlighter-rouge">nesteTick</code>
Por hoje é só. Abraços.</li>
</ul>]]></content><author><name></name></author><category term="nodejs" /><category term="nodejs" /><category term="event-loop" /><category term="internals" /><summary type="html"><![CDATA[Era 2025 e eu queria ver o event loop funcionando com meus próprios olhos. Não num diagrama, não num artigo - no código.]]></summary></entry><entry xml:lang="pt-BR"><title type="html">Compilando Brainfuck pra JVM, parte 1: o interpretador</title><link href="https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-1-o-interpretador.html" rel="alternate" type="text/html" title="Compilando Brainfuck pra JVM, parte 1: o interpretador" /><published>2026-03-16T00:00:00+00:00</published><updated>2026-03-16T00:00:00+00:00</updated><id>https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-1-o-interpretador</id><content type="html" xml:base="https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-1-o-interpretador.html"><![CDATA[<p>Quando eu decidi aprender como a JVM funciona por dentro, eu precisava de uma linguagem simples o suficiente pra não atrapalhar o aprendizado. Algo onde eu pudesse focar na mecânica do compilador sem me perder na complexidade da linguagem fonte.</p>

<p>Brainfuck foi a escolha óbvia.</p>

<p>Esse é o primeiro post de uma série de três onde a gente vai construir, do zero, um compilador que transforma código Brainfuck em bytecode JVM executável. Sem dependências externas, sem framework, só Node.js puro. No final da série, você vai ter um compilador que gera arquivos <code class="language-plaintext highlighter-rouge">.class</code> válidos que rodam direto no <code class="language-plaintext highlighter-rouge">java</code>.</p>

<p>O código completo tá no <a href="https://github.com/geeksilva97/brainjuck">GitHub</a>. Nesse primeiro post, a gente vai construir o interpretador - que é a base pra tudo que vem depois.</p>

<h2 id="o-que-é-brainfuck">O que é Brainfuck</h2>

<p>Brainfuck é uma linguagem de programação esotérica criada em 1993 por Urban Müller. Ela tem <strong>8 comandos</strong>. Oito. E ainda assim é Turing-completa - ou seja, em teoria, você pode computar qualquer coisa que qualquer outra linguagem computa.</p>

<p>O modelo de execução é simples:</p>

<ul>
  <li>Uma fita de memória com 30.000 células, cada uma armazenando um byte (0-255)</li>
  <li>Um ponteiro que aponta pra célula atual</li>
  <li>Entrada e saída (stdin/stdout)</li>
</ul>

<p>Os 8 comandos:</p>

<table>
  <thead>
    <tr>
      <th>Comando</th>
      <th>O que faz</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">+</code></td>
      <td>Incrementa o valor da célula atual</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-</code></td>
      <td>Decrementa o valor da célula atual</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&gt;</code></td>
      <td>Move o ponteiro uma célula pra direita</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&lt;</code></td>
      <td>Move o ponteiro uma célula pra esquerda</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.</code></td>
      <td>Imprime o valor da célula atual como caractere ASCII</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">,</code></td>
      <td>Lê um byte da entrada e armazena na célula atual</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">[</code></td>
      <td>Se a célula atual é zero, pula pro <code class="language-plaintext highlighter-rouge">]</code> correspondente</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">]</code></td>
      <td>Se a célula atual não é zero, volta pro <code class="language-plaintext highlighter-rouge">[</code> correspondente</td>
    </tr>
  </tbody>
</table>

<p>Qualquer outro caractere é ignorado - o que significa que você pode escrever comentários livremente no meio do código.</p>

<h3 id="um-exemplo-simples">Um exemplo simples</h3>

<p>Pra imprimir a letra “A” (código ASCII 65), você precisa colocar o valor 65 na célula e usar <code class="language-plaintext highlighter-rouge">.</code>:</p>

<div class="language-brainfuck highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++</span><span class="c1"> </span><span class="nf">.</span><span class="c1">
</span></code></pre></div></div>

<p>São 65 sinais de <code class="language-plaintext highlighter-rouge">+</code> seguidos de um <code class="language-plaintext highlighter-rouge">.</code>. Funciona, mas é feio. Uma forma mais elegante:</p>

<div class="language-brainfuck highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">++++++++</span><span class="p">[</span><span class="nb">&gt;</span><span class="nf">++++++++</span><span class="nb">&lt;</span><span class="nf">-</span><span class="p">]</span><span class="nb">&gt;</span><span class="nf">+.</span><span class="c1">
</span></code></pre></div></div>

<p>O que isso faz:</p>

<ol>
  <li>Coloca 8 na célula 0</li>
  <li>Entra no loop <code class="language-plaintext highlighter-rouge">[...]</code></li>
  <li>Move pra célula 1, soma 8, volta pra célula 0, subtrai 1</li>
  <li>O loop roda 8 vezes, então célula 1 fica com 64 (8 x 8)</li>
  <li>Move pra célula 1, soma 1 (agora é 65)</li>
  <li>Imprime: “A”</li>
</ol>

<p>Esse padrão de multiplicação com loops é a base de qualquer programa Brainfuck não-trivial.</p>

<h2 id="fase-1-o-tokenizer">Fase 1: o tokenizer</h2>

<p>O tokenizer é a parte mais simples de todo o projeto. A gente precisa pegar o código fonte e extrair só os caracteres válidos, descartando tudo que não é um dos 8 comandos.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">VALID_TOKENS</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Set</span><span class="p">([</span><span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">&gt;</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">&lt;</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">[</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">]</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="p">]);</span>

<span class="kd">function</span> <span class="nf">tokenizeBrainfuck</span><span class="p">(</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">tokens</span> <span class="o">=</span> <span class="p">[];</span>

  <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">code</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">VALID_TOKENS</span><span class="p">.</span><span class="nf">has</span><span class="p">(</span><span class="nx">code</span><span class="p">[</span><span class="nx">i</span><span class="p">]))</span> <span class="p">{</span>
      <span class="nx">tokens</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">code</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">tokens</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Dado o input <code class="language-plaintext highlighter-rouge">++[&gt;+&lt;-] este texto é ignorado .</code>, o tokenizer retorna:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">[</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">&gt;</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">&lt;</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">]</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">]</span>
</code></pre></div></div>

<p>Simples assim. Todos os espaços, letras e pontuação que não são comandos Brainfuck somem.</p>

<h2 id="fase-2-o-parser-e-a-representação-intermediária">Fase 2: o parser e a Representação Intermediária</h2>

<p>Em vez de executar os tokens diretamente, a gente vai transformar eles numa <strong>Representação Intermediária</strong> (IR). A IR é uma lista de instruções que descrevem o programa de forma mais estruturada.</p>

<p>Por que não executar os tokens diretamente? Dois motivos:</p>

<ol>
  <li><strong>Otimização</strong>: podemos combinar operações consecutivas. <code class="language-plaintext highlighter-rouge">++++</code> não precisa ser 4 instruções separadas - é uma instrução só com valor 4.</li>
  <li><strong>Desacoplamento</strong>: a IR permite que a gente separe o <code class="language-plaintext highlighter-rouge">parser</code> do <code class="language-plaintext highlighter-rouge">backend</code>. Hoje o backend é um interpretador JavaScript, mas na <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-3-gerando-bytecode">parte 3</a> da série vai ser um gerador de bytecode JVM. A IR é a mesma.</li>
</ol>

<h3 id="as-instruções-da-ir">As instruções da IR</h3>

<table>
  <thead>
    <tr>
      <th>Instrução</th>
      <th>O que faz</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">increment n</code></td>
      <td>Soma n à célula atual (n pode ser negativo)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">move_head h</code></td>
      <td>Move o ponteiro pra posição absoluta h</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">jump_eqz i</code></td>
      <td>Se célula atual == 0, pula pra instrução i</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">jump_neqz i</code></td>
      <td>Se célula atual != 0, pula pra instrução i</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">input</code></td>
      <td>Lê um byte da entrada</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">output</code></td>
      <td>Escreve a célula atual na saída</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">halt</code></td>
      <td>Fim do programa</td>
    </tr>
  </tbody>
</table>

<h3 id="combinando-operações-consecutivas">Combinando operações consecutivas</h3>

<p>A otimização mais importante do parser é combinar operações do mesmo tipo. A função <code class="language-plaintext highlighter-rouge">handleSubsequent</code> faz isso:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">handleSubsequent</span><span class="p">(</span><span class="nx">tokens</span><span class="p">,</span> <span class="nx">charMap</span><span class="p">,</span> <span class="nx">token</span><span class="p">,</span> <span class="nx">pos</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">charMap</span><span class="p">[</span><span class="nx">token</span><span class="p">];</span>
  <span class="kd">let</span> <span class="nx">newPos</span> <span class="o">=</span> <span class="nx">pos</span><span class="p">;</span>

  <span class="k">while </span><span class="p">(</span><span class="nx">newPos</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">&lt;</span> <span class="nx">tokens</span><span class="p">.</span><span class="nx">length</span> <span class="o">&amp;&amp;</span> <span class="nx">tokens</span><span class="p">[</span><span class="nx">newPos</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="k">in</span> <span class="nx">charMap</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">newPos</span><span class="o">++</span><span class="p">;</span>
    <span class="nx">value</span> <span class="o">+=</span> <span class="nx">charMap</span><span class="p">[</span><span class="nx">tokens</span><span class="p">[</span><span class="nx">newPos</span><span class="p">]];</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="p">{</span> <span class="na">inc</span><span class="p">:</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">newPos</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">charMap</code> mapeia cada token pro seu valor numérico. Pra incrementos: <code class="language-plaintext highlighter-rouge">{'+': 1, '-': -1}</code>. Pra movimentos: <code class="language-plaintext highlighter-rouge">{'&gt;': 1, '&lt;': -1}</code>.</p>

<p>O que isso nos dá na prática:</p>

<table>
  <thead>
    <tr>
      <th>Tokens</th>
      <th>IR gerada</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">++++</code></td>
      <td><code class="language-plaintext highlighter-rouge">{ type: 'increment', inc: 4 }</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">+++--</code></td>
      <td><code class="language-plaintext highlighter-rouge">{ type: 'increment', inc: 1 }</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">++--</code></td>
      <td>Nenhuma instrução (se cancela)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&gt;&gt;&gt;&gt;</code></td>
      <td><code class="language-plaintext highlighter-rouge">{ type: 'move_head', head: 4 }</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&gt;&gt;&lt;&lt;</code></td>
      <td>Nenhuma instrução (volta pro mesmo lugar)</td>
    </tr>
  </tbody>
</table>

<p>Essa otimização é simples mas faz diferença. Um “Hello World” em Brainfuck tem centenas de <code class="language-plaintext highlighter-rouge">+</code> e <code class="language-plaintext highlighter-rouge">-</code> consecutivos.</p>

<h3 id="resolvendo-os-jumps">Resolvendo os jumps</h3>

<p>A parte mais complicada do parser é resolver os pares <code class="language-plaintext highlighter-rouge">[</code> e <code class="language-plaintext highlighter-rouge">]</code>. A gente usa uma pilha (<code class="language-plaintext highlighter-rouge">loopStack</code>) pra rastrear os colchetes abertos:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">parseBrainfuck</span><span class="p">(</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">tokens</span> <span class="o">=</span> <span class="nf">tokenizeBrainfuck</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">instructions</span> <span class="o">=</span> <span class="p">[];</span>
  <span class="kd">const</span> <span class="nx">loopStack</span> <span class="o">=</span> <span class="p">[];</span>
  <span class="kd">let</span> <span class="nx">pointer</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

  <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">pos</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">pos</span> <span class="o">&lt;</span> <span class="nx">tokens</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">pos</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">tokens</span><span class="p">[</span><span class="nx">pos</span><span class="p">];</span>

    <span class="k">switch </span><span class="p">(</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">:</span>
      <span class="k">case</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nf">handleSubsequent</span><span class="p">(</span><span class="nx">tokens</span><span class="p">,</span> <span class="p">{</span><span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">},</span> <span class="nx">token</span><span class="p">,</span> <span class="nx">pos</span><span class="p">);</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">inc</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">instructions</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">increment</span><span class="dl">'</span><span class="p">,</span> <span class="na">inc</span><span class="p">:</span> <span class="nx">result</span><span class="p">.</span><span class="nx">inc</span> <span class="p">});</span>
        <span class="p">}</span>
        <span class="nx">pos</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">newPos</span><span class="p">;</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">&gt;</span><span class="dl">'</span><span class="p">:</span>
      <span class="k">case</span> <span class="dl">'</span><span class="s1">&lt;</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nf">handleSubsequent</span><span class="p">(</span><span class="nx">tokens</span><span class="p">,</span> <span class="p">{</span><span class="dl">'</span><span class="s1">&gt;</span><span class="dl">'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">&lt;</span><span class="dl">'</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">},</span> <span class="nx">token</span><span class="p">,</span> <span class="nx">pos</span><span class="p">);</span>
        <span class="nx">pointer</span> <span class="o">+=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">inc</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">inc</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">instructions</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">move_head</span><span class="dl">'</span><span class="p">,</span> <span class="na">head</span><span class="p">:</span> <span class="nx">pointer</span> <span class="p">});</span>
        <span class="p">}</span>
        <span class="nx">pos</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">newPos</span><span class="p">;</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">:</span>
        <span class="nx">instructions</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">output</span><span class="dl">'</span> <span class="p">});</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="p">:</span>
        <span class="nx">instructions</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">input</span><span class="dl">'</span> <span class="p">});</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">[</span><span class="dl">'</span><span class="p">:</span>
        <span class="nx">loopStack</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">instructions</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
        <span class="nx">instructions</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">jump_eqz</span><span class="dl">'</span><span class="p">,</span> <span class="na">jmp</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span> <span class="p">});</span> <span class="c1">// placeholder</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">]</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">openIndex</span> <span class="o">=</span> <span class="nx">loopStack</span><span class="p">.</span><span class="nf">pop</span><span class="p">();</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">openIndex</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Unmatched ]</span><span class="dl">'</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="nx">instructions</span><span class="p">[</span><span class="nx">openIndex</span><span class="p">].</span><span class="nx">jmp</span> <span class="o">=</span> <span class="nx">instructions</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
        <span class="nx">instructions</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">jump_neqz</span><span class="dl">'</span><span class="p">,</span> <span class="na">jmp</span><span class="p">:</span> <span class="nx">openIndex</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">});</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">if </span><span class="p">(</span><span class="nx">loopStack</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Unmatched [</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nx">instructions</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">halt</span><span class="dl">'</span> <span class="p">});</span>
  <span class="k">return</span> <span class="nx">instructions</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>O truque é o seguinte:</p>

<ol>
  <li>Quando encontra <code class="language-plaintext highlighter-rouge">[</code>, empilha o índice da instrução atual e emite um <code class="language-plaintext highlighter-rouge">jump_eqz</code> com destino <code class="language-plaintext highlighter-rouge">-1</code> (placeholder).</li>
  <li>Quando encontra <code class="language-plaintext highlighter-rouge">]</code>, desempilha o índice do <code class="language-plaintext highlighter-rouge">[</code> correspondente.</li>
  <li>Atualiza o <code class="language-plaintext highlighter-rouge">jmp</code> do <code class="language-plaintext highlighter-rouge">[</code> pra apontar pra instrução depois do <code class="language-plaintext highlighter-rouge">]</code>.</li>
  <li>O <code class="language-plaintext highlighter-rouge">]</code> aponta de volta pra instrução depois do <code class="language-plaintext highlighter-rouge">[</code>.</li>
</ol>

<p>Visualmente:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Instruções:  [0]  [1]  [2]  [3]  [4]  [5]  [6]
             inc  jeqz inc  inc  jneqz inc  halt
                  jmp→5          jmp→2
</code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">jump_eqz</code> na posição 1 pula pra posição 5 (saída do loop) se a célula é zero. O <code class="language-plaintext highlighter-rouge">jump_neqz</code> na posição 4 volta pra posição 2 (início do corpo do loop) se a célula não é zero.</p>

<h3 id="o-ponteiro-absoluto">O ponteiro absoluto</h3>

<p>Uma decisão de design que vale mencionar: o <code class="language-plaintext highlighter-rouge">move_head</code> usa <strong>posição absoluta</strong>, não relativa. O parser mantém uma variável <code class="language-plaintext highlighter-rouge">pointer</code> que rastreia a posição virtual do ponteiro durante o parsing.</p>

<p>Isso simplifica a geração de bytecode lá na <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-3-gerando-bytecode">parte 3</a>, porque na JVM a gente pode simplesmente fazer <code class="language-plaintext highlighter-rouge">sipush valor; istore_2</code> pra definir o ponteiro, sem precisar carregar o valor atual e somar.</p>

<h2 id="fase-3-o-interpretador">Fase 3: o interpretador</h2>

<p>Com a IR pronta, o interpretador é direto. É um loop de fetch-decode-execute:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">executeBrainfuck</span><span class="p">(</span><span class="nx">code</span><span class="p">,</span> <span class="nx">memory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Uint8Array</span><span class="p">(</span><span class="mi">30000</span><span class="p">))</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">instructions</span> <span class="o">=</span> <span class="nf">parseBrainfuck</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span>
  <span class="kd">let</span> <span class="nx">pointer</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">pc</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">output</span> <span class="o">=</span> <span class="p">[];</span>

  <span class="k">while </span><span class="p">(</span><span class="nx">pc</span> <span class="o">&lt;</span> <span class="nx">instructions</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">instruction</span> <span class="o">=</span> <span class="nx">instructions</span><span class="p">[</span><span class="nx">pc</span><span class="p">];</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">instruction</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">halt</span><span class="dl">'</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>

    <span class="k">switch </span><span class="p">(</span><span class="nx">instruction</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">'</span><span class="s1">increment</span><span class="dl">'</span><span class="p">:</span>
        <span class="nx">memory</span><span class="p">[</span><span class="nx">pointer</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">memory</span><span class="p">[</span><span class="nx">pointer</span><span class="p">]</span> <span class="o">+</span> <span class="nx">instruction</span><span class="p">.</span><span class="nx">inc</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">;</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">move_head</span><span class="dl">'</span><span class="p">:</span>
        <span class="nx">pointer</span> <span class="o">=</span> <span class="nx">instruction</span><span class="p">.</span><span class="nx">head</span><span class="p">;</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">output</span><span class="dl">'</span><span class="p">:</span>
        <span class="nx">process</span><span class="p">.</span><span class="nx">stdout</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="nb">String</span><span class="p">.</span><span class="nf">fromCharCode</span><span class="p">(</span><span class="nx">memory</span><span class="p">[</span><span class="nx">pointer</span><span class="p">]));</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">:</span>
        <span class="c1">// lê um byte de stdin</span>
        <span class="nx">memory</span><span class="p">[</span><span class="nx">pointer</span><span class="p">]</span> <span class="o">=</span> <span class="nf">readByte</span><span class="p">();</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">jump_eqz</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">memory</span><span class="p">[</span><span class="nx">pointer</span><span class="p">]</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">pc</span> <span class="o">=</span> <span class="nx">instruction</span><span class="p">.</span><span class="nx">jmp</span><span class="p">;</span>
          <span class="k">continue</span><span class="p">;</span> <span class="c1">// pula o pc++ no final</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="k">case</span> <span class="dl">'</span><span class="s1">jump_neqz</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">memory</span><span class="p">[</span><span class="nx">pointer</span><span class="p">]</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">pc</span> <span class="o">=</span> <span class="nx">instruction</span><span class="p">.</span><span class="nx">jmp</span><span class="p">;</span>
          <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">pc</span><span class="o">++</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="p">{</span> <span class="na">cells</span><span class="p">:</span> <span class="nx">memory</span><span class="p">,</span> <span class="na">currentCell</span><span class="p">:</span> <span class="nx">pointer</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Alguns detalhes que vale notar.</p>

<p>O <code class="language-plaintext highlighter-rouge">&amp; 0xFF</code> no incremento garante que o valor fica no range de um byte (0-255). Se uma célula tá com 255 e você incrementa, ela volta pra 0. Se tá com 0 e decrementa, vai pra 255. É o comportamento esperado de Brainfuck.</p>

<p>O <code class="language-plaintext highlighter-rouge">continue</code> nos jumps é necessário porque a gente não quer incrementar o <code class="language-plaintext highlighter-rouge">pc</code> quando um salto é tomado. O <code class="language-plaintext highlighter-rouge">jmp</code> já aponta pra instrução correta. Sem o <code class="language-plaintext highlighter-rouge">continue</code>, o <code class="language-plaintext highlighter-rouge">pc++</code> no final do loop rodaria e a gente pularia uma instrução.</p>

<p>O <code class="language-plaintext highlighter-rouge">String.fromCharCode</code> transforma o valor numérico da célula no caractere ASCII correspondente. Célula com 65 imprime “A”, com 72 imprime “H”.</p>

<h2 id="testando">Testando</h2>

<p>Pra verificar que tudo funciona, a gente pode rodar o clássico “Hello, World!” em Brainfuck:</p>

<div class="language-brainfuck highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">++++++++</span><span class="p">[</span><span class="nb">&gt;</span><span class="nf">++++</span><span class="p">[</span><span class="nb">&gt;</span><span class="nf">++</span><span class="nb">&gt;</span><span class="nf">+++</span><span class="nb">&gt;</span><span class="nf">+++</span><span class="nb">&gt;</span><span class="nf">+</span><span class="nb">&lt;&lt;&lt;&lt;</span><span class="nf">-</span><span class="p">]</span><span class="nb">&gt;</span><span class="nf">+</span><span class="nb">&gt;</span><span class="nf">+</span><span class="nb">&gt;</span><span class="nf">-</span><span class="nb">&gt;&gt;</span><span class="nf">+</span><span class="p">[</span><span class="nb">&lt;</span><span class="p">]</span><span class="nb">&lt;</span><span class="nf">-</span><span class="p">]</span><span class="nb">&gt;&gt;</span><span class="nf">.</span><span class="nb">&gt;</span><span class="c1">
</span><span class="nf">---.+++++++..+++.</span><span class="nb">&gt;&gt;</span><span class="nf">.</span><span class="nb">&lt;</span><span class="nf">-.</span><span class="nb">&lt;</span><span class="nf">.+++.------.--------.</span><span class="nb">&gt;&gt;</span><span class="nf">+.</span><span class="nb">&gt;</span><span class="nf">++.</span><span class="c1">
</span></code></pre></div></div>

<p>Se tudo deu certo, o output é <code class="language-plaintext highlighter-rouge">Hello, World!</code>.</p>

<p>Você pode testar manualmente ou escrever testes automatizados. O projeto BrainJuck usa o test runner nativo do Node.js (<code class="language-plaintext highlighter-rouge">node:test</code>):</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">describe</span><span class="p">,</span> <span class="nx">it</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:test</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">assert</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:assert</span><span class="dl">'</span><span class="p">;</span>

<span class="nf">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">tokenizer</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">extrai apenas tokens válidos</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">tokens</span> <span class="o">=</span> <span class="nf">tokenizeBrainfuck</span><span class="p">(</span><span class="dl">'</span><span class="s1">++ comentario &gt;&gt; .</span><span class="dl">'</span><span class="p">);</span>
    <span class="nx">assert</span><span class="p">.</span><span class="nf">deepStrictEqual</span><span class="p">(</span><span class="nx">tokens</span><span class="p">,</span> <span class="p">[</span><span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">&gt;</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">&gt;</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">]);</span>
  <span class="p">});</span>
<span class="p">});</span>

<span class="nf">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">parser</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">combina incrementos consecutivos</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">ir</span> <span class="o">=</span> <span class="nf">parseBrainfuck</span><span class="p">(</span><span class="dl">'</span><span class="s1">++++---</span><span class="dl">'</span><span class="p">);</span>
    <span class="nx">assert</span><span class="p">.</span><span class="nf">equal</span><span class="p">(</span><span class="nx">ir</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">type</span><span class="p">,</span> <span class="dl">'</span><span class="s1">increment</span><span class="dl">'</span><span class="p">);</span>
    <span class="nx">assert</span><span class="p">.</span><span class="nf">equal</span><span class="p">(</span><span class="nx">ir</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">inc</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
  <span class="p">});</span>

  <span class="nf">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">elimina movimentos que se cancelam</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">ir</span> <span class="o">=</span> <span class="nf">parseBrainfuck</span><span class="p">(</span><span class="dl">'</span><span class="s1">&gt;&gt;&lt;&lt;</span><span class="dl">'</span><span class="p">);</span>
    <span class="nx">assert</span><span class="p">.</span><span class="nf">equal</span><span class="p">(</span><span class="nx">ir</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">type</span><span class="p">,</span> <span class="dl">'</span><span class="s1">halt</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="recapitulando">Recapitulando</h2>

<p>Até aqui a gente construiu:</p>

<ol>
  <li>Um <strong>tokenizer</strong> que extrai os 8 comandos válidos do código fonte</li>
  <li>Um <strong>parser</strong> que transforma tokens em IR, com otimizações de combinação</li>
  <li>Um <strong>interpretador</strong> que executa a IR</li>
</ol>

<p>O pipeline completo:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Código fonte (.bf) -&gt; Tokenizer -&gt; Parser -&gt; IR -&gt; Interpretador -&gt; Output
</code></pre></div></div>

<p>A IR é o que conecta o parser ao backend. No próximo post, a gente vai trocar o interpretador por um gerador de bytecode JVM. Mas antes disso, a gente precisa entender o formato <code class="language-plaintext highlighter-rouge">.class</code> da JVM - que é o assunto da <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-2-dissecando-o-formato-class">parte 2</a>.</p>

<p>O código desse post tá em <a href="https://github.com/geeksilva97/brainjuck">geeksilva97/brainjuck</a>. Os commits relevantes são os primeiros do projeto - em especial o <a href="https://github.com/geeksilva97/brainjuck/commit/a171e9a"><code class="language-plaintext highlighter-rouge">a171e9a</code> (functional brainfuck)</a> e o <a href="https://github.com/geeksilva97/brainjuck/commit/437d37c"><code class="language-plaintext highlighter-rouge">437d37c</code> (the simplest parser ever)</a>.</p>

<p>Por hoje é só. Abraços.</p>]]></content><author><name></name></author><category term="programacao" /><category term="compiladores" /><category term="jvm" /><category term="brainfuck" /><category term="nodejs" /><summary type="html"><![CDATA[Quando eu decidi aprender como a JVM funciona por dentro, eu precisava de uma linguagem simples o suficiente pra não atrapalhar o aprendizado. Algo onde eu pudesse focar na mecânica do compilador sem me perder na complexidade da linguagem fonte.]]></summary></entry><entry xml:lang="pt-BR"><title type="html">Compilando Brainfuck pra JVM, parte 2: dissecando o formato .class</title><link href="https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-2-dissecando-o-formato-class.html" rel="alternate" type="text/html" title="Compilando Brainfuck pra JVM, parte 2: dissecando o formato .class" /><published>2026-03-16T00:00:00+00:00</published><updated>2026-03-16T00:00:00+00:00</updated><id>https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-2-dissecando-o-formato-class</id><content type="html" xml:base="https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-2-dissecando-o-formato-class.html"><![CDATA[<p>Na <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-1-o-interpretador">parte 1</a> a gente construiu um interpretador de Brainfuck com tokenizer, parser e uma Representação Intermediária. Agora a gente precisa entender o formato que a JVM espera receber pra poder gerar nosso próprio bytecode.</p>

<p>Quando você roda <code class="language-plaintext highlighter-rouge">javac Hello.java</code>, o compilador gera um <code class="language-plaintext highlighter-rouge">Hello.class</code>. Esse arquivo é binário - não é texto, não é JSON, não é XML. São bytes crus numa estrutura muito específica definida na <a href="https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html">especificação da JVM</a>.</p>

<p>Nesse post, a gente vai abrir um <code class="language-plaintext highlighter-rouge">.class</code> com <code class="language-plaintext highlighter-rouge">xxd</code>, entender cada byte, e construir um gerador que monta essa estrutura do zero em Node.js.</p>

<h2 id="antes-de-tudo-big-endian">Antes de tudo: big endian</h2>

<p>A JVM usa <strong>big endian</strong> pra representar números de múltiplos bytes. Isso é importante porque o processador do seu computador (x86/ARM) provavelmente usa little endian, que é a ordem inversa.</p>

<p>Qual a diferença? Imagina o número 30.000 (em hex: <code class="language-plaintext highlighter-rouge">0x7530</code>). Ele ocupa 2 bytes:</p>

<table>
  <thead>
    <tr>
      <th>Formato</th>
      <th>Byte 1</th>
      <th>Byte 2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Big endian</td>
      <td><code class="language-plaintext highlighter-rouge">0x75</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x30</code></td>
    </tr>
    <tr>
      <td>Little endian</td>
      <td><code class="language-plaintext highlighter-rouge">0x30</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x75</code></td>
    </tr>
  </tbody>
</table>

<p>Big endian coloca o byte mais significativo primeiro. Little endian coloca o menos significativo primeiro.</p>

<p>No nosso gerador, toda vez que a gente escrever um número de 2 ou 4 bytes, precisa respeitar essa ordem. A função pra converter um número de 16 bits (2 bytes) pra big endian:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">num</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[(</span><span class="nx">num</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">,</span> <span class="nx">num</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">&gt;&gt; 8</code> desloca 8 bits pra direita, pegando o byte alto. O <code class="language-plaintext highlighter-rouge">&amp; 0xFF</code> mascara o byte baixo. Pra 4 bytes, a lógica é a mesma mas com mais shifts.</p>

<p>Se você errar a ordem dos bytes, a JVM vai ler valores completamente errados. Um <code class="language-plaintext highlighter-rouge">0x7530</code> (30.000) vira <code class="language-plaintext highlighter-rouge">0x3075</code> (12.405) se os bytes ficarem invertidos. Então se o seu <code class="language-plaintext highlighter-rouge">.class</code> gerado dá erros estranhos no <code class="language-plaintext highlighter-rouge">constant pool</code>, confere se você tá escrevendo big endian.</p>

<h2 id="dissecando-um-class">Dissecando um .class</h2>

<p>Vamos criar o programa Java mais simples possível:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Hello</span> <span class="o">{</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span><span class="o">;</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Compila com <code class="language-plaintext highlighter-rouge">javac Hello.java</code> e abre o binário com <code class="language-plaintext highlighter-rouge">xxd Hello.class</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000: cafe babe 0000 0034 000d 0a00 0200 0307  .......4........
00000010: 0004 0c00 0500 0601 0010 6a61 7661 2f6c  ..........java/l
00000020: 616e 672f 4f62 6a65 6374 0100 063c 696e  ang/Object...&lt;in
00000030: 6974 3e01 0003 2829 5607 0008 0100 0548  it&gt;...()V......H
00000040: 656c 6c6f 0100 0443 6f64 6501 0004 6d61  ello...Code...ma
00000050: 696e 0100 1628 5b4c 6a61 7661 2f6c 616e  in...([Ljava/lan
00000060: 672f 5374 7269 6e67 3b29 5600 2100 0700  g/String;)V.!...
</code></pre></div></div>

<p>Parece caótico, mas tem uma estrutura definida. Vamos ler byte por byte.</p>

<h3 id="o-magic-number">O magic number</h3>

<p>Os primeiros 4 bytes de todo <code class="language-plaintext highlighter-rouge">.class</code> são sempre <code class="language-plaintext highlighter-rouge">CA FE BA BE</code>. É o magic number que identifica o arquivo como um ClassFile da JVM. Se esses bytes não estiverem lá, a JVM recusa o arquivo imediatamente.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cafe babe
</code></pre></div></div>

<p>A história diz que os criadores do Java escolheram <code class="language-plaintext highlighter-rouge">CAFEBABE</code> porque lembravam de um café que frequentavam. Verdade ou não, é memorável.</p>

<h3 id="versão">Versão</h3>

<p>Os 4 bytes seguintes indicam a versão do formato:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0000 0034
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">0000</code> - minor version: 0</li>
  <li><code class="language-plaintext highlighter-rouge">0034</code> - major version: 52 (decimal)</li>
</ul>

<p>Major version 52 é Java 8. A versão é importante porque determina quais features o <code class="language-plaintext highlighter-rouge">.class</code> pode usar. A partir da versão 50 (Java 6), por exemplo, a <code class="language-plaintext highlighter-rouge">StackMapTable</code> é obrigatória - mas esse é assunto da <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-3-gerando-bytecode">parte 3</a>.</p>

<h3 id="o-constant-pool">O constant pool</h3>

<p>Aqui é onde mora a complexidade. O <code class="language-plaintext highlighter-rouge">constant pool</code> é uma tabela que armazena todas as constantes do programa: nomes de classes, nomes de métodos, strings, descritores de tipo. Tudo que o bytecode referencia é guardado aqui.</p>

<p>Os próximos 2 bytes indicam o tamanho:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>000d
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">0x000d</code> = 13. Mas atenção: o constant pool usa indexação começando em 1, e o count é sempre <code class="language-plaintext highlighter-rouge">n + 1</code>. Então temos 12 entries (indices 1 a 12).</p>

<p>Cada entry começa com um byte de tag que indica o tipo:</p>

<table>
  <thead>
    <tr>
      <th>Tag</th>
      <th>Tipo</th>
      <th>O que armazena</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>CONSTANT_Utf8</td>
      <td>String UTF-8 (nomes, descritores)</td>
    </tr>
    <tr>
      <td>7</td>
      <td>CONSTANT_Class</td>
      <td>Referência a uma classe (aponta pra um Utf8)</td>
    </tr>
    <tr>
      <td>9</td>
      <td>CONSTANT_Fieldref</td>
      <td>Referência a um campo (classe + nome/tipo)</td>
    </tr>
    <tr>
      <td>10</td>
      <td>CONSTANT_Methodref</td>
      <td>Referência a um método (classe + nome/tipo)</td>
    </tr>
    <tr>
      <td>12</td>
      <td>CONSTANT_NameAndType</td>
      <td>Par nome + descritor de tipo</td>
    </tr>
  </tbody>
</table>

<p>Vou destrinchar as primeiras entries do nosso <code class="language-plaintext highlighter-rouge">Hello.class</code>:</p>

<p><strong>Entry 1</strong> (começa no byte 0x0a):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0a 00 02 00 03
</code></pre></div></div>
<ul>
  <li>Tag <code class="language-plaintext highlighter-rouge">0x0a</code> = 10 = CONSTANT_Methodref</li>
  <li>Class index: <code class="language-plaintext highlighter-rouge">0x0002</code> = 2</li>
  <li>NameAndType index: <code class="language-plaintext highlighter-rouge">0x0003</code> = 3</li>
</ul>

<p>Isso é uma referência ao método <code class="language-plaintext highlighter-rouge">Object.&lt;init&gt;()V</code> - o construtor da classe pai.</p>

<p><strong>Entry 2</strong>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>07 00 04
</code></pre></div></div>
<ul>
  <li>Tag <code class="language-plaintext highlighter-rouge">0x07</code> = 7 = CONSTANT_Class</li>
  <li>Name index: <code class="language-plaintext highlighter-rouge">0x0004</code> = 4 (aponta pra um Utf8 com o nome da classe)</li>
</ul>

<p><strong>Entry 4</strong>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>01 0010 6a617661 2f6c616e 672f4f62 6a656374
</code></pre></div></div>
<ul>
  <li>Tag <code class="language-plaintext highlighter-rouge">0x01</code> = 1 = CONSTANT_Utf8</li>
  <li>Length: <code class="language-plaintext highlighter-rouge">0x0010</code> = 16 bytes</li>
  <li>Conteúdo: <code class="language-plaintext highlighter-rouge">java/lang/Object</code></li>
</ul>

<p>Percebe o padrão? O Methodref aponta pro Class, que aponta pro Utf8. É uma estrutura de referências indiretas. O bytecode nunca guarda strings diretamente - tudo passa pelo constant pool.</p>

<h3 id="os-descritores-de-tipo">Os descritores de tipo</h3>

<p>Uma coisa que confunde no começo: a JVM usa uma notação própria pra tipos. Não é <code class="language-plaintext highlighter-rouge">void main(String[] args)</code>, é <code class="language-plaintext highlighter-rouge">([Ljava/lang/String;)V</code>.</p>

<p>As regras:</p>

<table>
  <thead>
    <tr>
      <th>Tipo Java</th>
      <th>Descritor JVM</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">int</code></td>
      <td><code class="language-plaintext highlighter-rouge">I</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">byte</code></td>
      <td><code class="language-plaintext highlighter-rouge">B</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">char</code></td>
      <td><code class="language-plaintext highlighter-rouge">C</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">void</code></td>
      <td><code class="language-plaintext highlighter-rouge">V</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">String</code></td>
      <td><code class="language-plaintext highlighter-rouge">Ljava/lang/String;</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">int[]</code></td>
      <td><code class="language-plaintext highlighter-rouge">[I</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">byte[]</code></td>
      <td><code class="language-plaintext highlighter-rouge">[B</code></td>
    </tr>
  </tbody>
</table>

<p>Pra métodos, o formato é <code class="language-plaintext highlighter-rouge">(parâmetros)retorno</code>. Então:</p>

<table>
  <thead>
    <tr>
      <th>Java</th>
      <th>Descritor JVM</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">void main(String[] args)</code></td>
      <td><code class="language-plaintext highlighter-rouge">([Ljava/lang/String;)V</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">void print(char c)</code></td>
      <td><code class="language-plaintext highlighter-rouge">(C)V</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">int read()</code></td>
      <td><code class="language-plaintext highlighter-rouge">()I</code></td>
    </tr>
  </tbody>
</table>

<p>Esses descritores aparecem no constant pool e o bytecode referencia eles pelo índice.</p>

<h3 id="depois-do-constant-pool">Depois do constant pool</h3>

<p>O resto do ClassFile segue:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0021              - Access flags (ACC_PUBLIC | ACC_SUPER)
0007              - This class (índice no constant pool)
0002              - Super class (java/lang/Object)
0000              - Interfaces count: 0
0000              - Fields count: 0
0002              - Methods count: 2
</code></pre></div></div>

<p>Depois vêm os métodos (cada um com seus atributos de código) e por fim os atributos da classe.</p>

<p>Uma coisa que eu não esperava: mesmo o programa mais simples tem <strong>2 métodos</strong>. O <code class="language-plaintext highlighter-rouge">main</code> que a gente escreveu e o construtor <code class="language-plaintext highlighter-rouge">&lt;init&gt;</code> que o Java gera automaticamente. No nosso gerador, a gente também precisa criar esse construtor.</p>

<h2 id="construindo-o-gerador">Construindo o gerador</h2>

<p>Agora que a gente entende a estrutura, vamos construir um gerador em Node.js. A ideia é montar o <code class="language-plaintext highlighter-rouge">.class</code> byte por byte num buffer.</p>

<h3 id="escrevendo-bytes">Escrevendo bytes</h3>

<p>Primeiro, as primitivas de escrita:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ClassFileGenerator</span> <span class="p">{</span>
  <span class="nf">constructor</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span> <span class="o">=</span> <span class="p">[];</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">constantPool</span> <span class="o">=</span> <span class="p">[];</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">constantPoolMap</span> <span class="o">=</span> <span class="p">{};</span>
  <span class="p">}</span>

  <span class="nf">writeU1</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">value</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nf">writeU2</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nf">push</span><span class="p">((</span><span class="nx">value</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">value</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nf">writeU4</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nf">push</span><span class="p">((</span><span class="nx">value</span> <span class="o">&gt;&gt;</span> <span class="mi">24</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nf">push</span><span class="p">((</span><span class="nx">value</span> <span class="o">&gt;&gt;</span> <span class="mi">16</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nf">push</span><span class="p">((</span><span class="nx">value</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">value</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nf">writeBytes</span><span class="p">(</span><span class="nx">bytes</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">b</span> <span class="k">of</span> <span class="nx">bytes</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">b</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">U1</code>, <code class="language-plaintext highlighter-rouge">U2</code>, <code class="language-plaintext highlighter-rouge">U4</code> - 1, 2 e 4 bytes sem sinal. Tudo em big endian, que é o que a JVM espera. Repara que <code class="language-plaintext highlighter-rouge">writeU2</code> e <code class="language-plaintext highlighter-rouge">writeU4</code> usam shifts pra separar os bytes na ordem correta - byte mais significativo primeiro.</p>

<h3 id="gerenciando-o-constant-pool">Gerenciando o constant pool</h3>

<p>O constant pool precisa de deduplicação. Se dois métodos referenciam a mesma string <code class="language-plaintext highlighter-rouge">"java/lang/Object"</code>, ela deve aparecer uma vez só. A gente usa um mapa pra controlar isso:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">addUtf8Constant</span><span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">key</span> <span class="o">=</span> <span class="s2">`utf8:</span><span class="p">${</span><span class="nx">str</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
  <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">constantPoolMap</span><span class="p">[</span><span class="nx">key</span><span class="p">])</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">constantPoolMap</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span>
  <span class="p">}</span>

  <span class="k">this</span><span class="p">.</span><span class="nx">constantPool</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">tag</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">value</span><span class="p">:</span> <span class="nx">str</span> <span class="p">});</span>
  <span class="kd">const</span> <span class="nx">index</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">constantPool</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
  <span class="k">this</span><span class="p">.</span><span class="nx">constantPoolMap</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">index</span><span class="p">;</span>
  <span class="k">return</span> <span class="nx">index</span><span class="p">;</span>
<span class="p">}</span>

<span class="nf">addClassConstant</span><span class="p">(</span><span class="nx">nameIndex</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">key</span> <span class="o">=</span> <span class="s2">`class:</span><span class="p">${</span><span class="nx">nameIndex</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
  <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">constantPoolMap</span><span class="p">[</span><span class="nx">key</span><span class="p">])</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">constantPoolMap</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span>
  <span class="p">}</span>

  <span class="k">this</span><span class="p">.</span><span class="nx">constantPool</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="na">tag</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span> <span class="nx">nameIndex</span> <span class="p">});</span>
  <span class="kd">const</span> <span class="nx">index</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">constantPool</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
  <span class="k">this</span><span class="p">.</span><span class="nx">constantPoolMap</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">index</span><span class="p">;</span>
  <span class="k">return</span> <span class="nx">index</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>O mesmo padrão se repete pra <code class="language-plaintext highlighter-rouge">addMethodrefConstant</code>, <code class="language-plaintext highlighter-rouge">addFieldrefConstant</code>, <code class="language-plaintext highlighter-rouge">addNameAndTypeConstant</code>. Cada tipo tem seu tag e seus campos, mas a lógica de deduplicação é a mesma.</p>

<p>O índice retornado é a posição no array + 1, porque o constant pool da JVM é 1-indexed.</p>

<h3 id="montando-o-classfile">Montando o ClassFile</h3>

<p>Pra gerar o <code class="language-plaintext highlighter-rouge">.class</code> do nosso compilador Brainfuck, a gente precisa de várias entries no constant pool. Especificamente, pra fazer <code class="language-plaintext highlighter-rouge">System.out.print(char)</code> e <code class="language-plaintext highlighter-rouge">System.in.read()</code>, que são as operações de I/O do Brainfuck:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// System.out (pra output do brainfuck)</span>
<span class="kd">const</span> <span class="nx">systemClassName</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addUtf8Constant</span><span class="p">(</span><span class="dl">'</span><span class="s1">java/lang/System</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">systemClass</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addClassConstant</span><span class="p">(</span><span class="nx">systemClassName</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">outFieldName</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addUtf8Constant</span><span class="p">(</span><span class="dl">'</span><span class="s1">out</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">printStreamDesc</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addUtf8Constant</span><span class="p">(</span><span class="dl">'</span><span class="s1">Ljava/io/PrintStream;</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">outNaT</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addNameAndTypeConstant</span><span class="p">(</span><span class="nx">outFieldName</span><span class="p">,</span> <span class="nx">printStreamDesc</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">outFieldRef</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addFieldrefConstant</span><span class="p">(</span><span class="nx">systemClass</span><span class="p">,</span> <span class="nx">outNaT</span><span class="p">);</span>

<span class="c1">// PrintStream.print(char)</span>
<span class="kd">const</span> <span class="nx">printStreamClassName</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addUtf8Constant</span><span class="p">(</span><span class="dl">'</span><span class="s1">java/io/PrintStream</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">printStreamClass</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addClassConstant</span><span class="p">(</span><span class="nx">printStreamClassName</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">printName</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addUtf8Constant</span><span class="p">(</span><span class="dl">'</span><span class="s1">print</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">printDesc</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addUtf8Constant</span><span class="p">(</span><span class="dl">'</span><span class="s1">(C)V</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">printNaT</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addNameAndTypeConstant</span><span class="p">(</span><span class="nx">printName</span><span class="p">,</span> <span class="nx">printDesc</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">printMethodRef</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">addMethodrefConstant</span><span class="p">(</span><span class="nx">printStreamClass</span><span class="p">,</span> <span class="nx">printNaT</span><span class="p">);</span>
</code></pre></div></div>

<p>Parece bastante coisa, e é. Cada referência de método ou campo na JVM exige essa cadeia: Utf8, depois Class, depois NameAndType, depois Methodref ou Fieldref. Mas depois que você monta uma vez, o padrão fica automático.</p>

<h3 id="o-construtor">O construtor</h3>

<p>Todo <code class="language-plaintext highlighter-rouge">.class</code> precisa de um construtor, mesmo que ele não faça nada. O construtor default em bytecode é:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aload_0             // carrega 'this' (local_0)
invokespecial #X    // chama Object.&lt;init&gt;()V
return              // retorna
</code></pre></div></div>

<p>Onde <code class="language-plaintext highlighter-rouge">#X</code> é o índice do Methodref pro construtor de <code class="language-plaintext highlighter-rouge">Object</code> no constant pool.</p>

<p>Em bytes:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// bytecode do construtor</span>
<span class="kd">const</span> <span class="nx">constructorCode</span> <span class="o">=</span> <span class="p">[</span>
  <span class="mh">0x2a</span><span class="p">,</span>       <span class="c1">// aload_0</span>
  <span class="mh">0xb7</span><span class="p">,</span>       <span class="c1">// invokespecial</span>
  <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">objectInitMethodRef</span><span class="p">),</span>
  <span class="mh">0xb1</span>        <span class="c1">// return</span>
<span class="p">];</span>
</code></pre></div></div>

<h3 id="o-método-main">O método main</h3>

<p>O main é onde o bytecode do Brainfuck vai ficar. A declaração dele no ClassFile:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// access flags: ACC_PUBLIC | ACC_STATIC</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="mh">0x0009</span><span class="p">);</span>
<span class="c1">// name index: "main"</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="nx">mainNameIndex</span><span class="p">);</span>
<span class="c1">// descriptor index: "([Ljava/lang/String;)V"</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="nx">mainDescIndex</span><span class="p">);</span>
<span class="c1">// attributes count: 1 (o atributo Code)</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</code></pre></div></div>

<p>O atributo <code class="language-plaintext highlighter-rouge">Code</code> contém o bytecode real:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// atributo Code</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="nx">codeAttrNameIndex</span><span class="p">);</span>   <span class="c1">// nome "Code" no constant pool</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU4</span><span class="p">(</span><span class="nx">codeAttrLength</span><span class="p">);</span>      <span class="c1">// tamanho total do atributo</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>                   <span class="c1">// max_stack</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>                   <span class="c1">// max_locals (args, cells, pointer)</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU4</span><span class="p">(</span><span class="nx">codeLength</span><span class="p">);</span>          <span class="c1">// tamanho do bytecode</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeBytes</span><span class="p">(</span><span class="nx">jvmInstructions</span><span class="p">);</span>  <span class="c1">// o bytecode em si</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>                   <span class="c1">// exception table length</span>
<span class="k">this</span><span class="p">.</span><span class="nf">writeU2</span><span class="p">(</span><span class="nx">stackMapEntries</span><span class="p">);</span>     <span class="c1">// attributes count (0 ou 1)</span>
<span class="c1">// se tiver StackMapTable, escreve aqui</span>
</code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">max_locals</code> é 3 porque temos: slot 0 pro <code class="language-plaintext highlighter-rouge">String[] args</code>, slot 1 pro <code class="language-plaintext highlighter-rouge">byte[]</code> (memória do Brainfuck), e slot 2 pro <code class="language-plaintext highlighter-rouge">int</code> (ponteiro). Eu descobri isso na marra - quando coloquei 1, a JVM deu <code class="language-plaintext highlighter-rouge">VerifyError: Local variable table overflow</code>.</p>

<h3 id="gerando-o-arquivo">Gerando o arquivo</h3>

<p>No final, a gente junta tudo e escreve:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">classBytes</span> <span class="o">=</span> <span class="nx">generator</span><span class="p">.</span><span class="nf">generateHelloWorldClass</span><span class="p">(</span>
  <span class="nx">className</span><span class="p">,</span>
  <span class="p">({</span> <span class="nx">symbolicConstantPool</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nf">brainfuckIRToJVM</span><span class="p">(</span><span class="nx">ir</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">input</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* refs do System.in */</span> <span class="p">},</span>
      <span class="na">output</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* refs do System.out */</span> <span class="p">}</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">);</span>

<span class="nx">fs</span><span class="p">.</span><span class="nf">writeFileSync</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">className</span><span class="p">}</span><span class="s2">.class`</span><span class="p">,</span> <span class="k">new</span> <span class="nc">Uint8Array</span><span class="p">(</span><span class="nx">classBytes</span><span class="p">));</span>
</code></pre></div></div>

<p>O callback <code class="language-plaintext highlighter-rouge">makeInstructions</code> recebe o constant pool já montado e retorna o bytecode gerado. Essa separação permite que o gerador de ClassFile não saiba nada sobre Brainfuck - ele só sabe montar a estrutura do <code class="language-plaintext highlighter-rouge">.class</code>.</p>

<h2 id="inspecionando-com-javap">Inspecionando com javap</h2>

<p>Depois de gerar o <code class="language-plaintext highlighter-rouge">.class</code>, você pode inspecionar ele com <code class="language-plaintext highlighter-rouge">javap -v</code> pra confirmar que a estrutura tá correta:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>javap <span class="nt">-v</span> CompiledBrainfuck.class
</code></pre></div></div>

<p>Isso mostra o constant pool, os métodos, o bytecode decodificado, e os atributos. É a melhor ferramenta de debug que você vai ter nesse projeto. Quando algo der errado (e vai dar), roda o <code class="language-plaintext highlighter-rouge">javap</code> e compara com um <code class="language-plaintext highlighter-rouge">.class</code> gerado pelo <code class="language-plaintext highlighter-rouge">javac</code>.</p>

<p>Outra ferramenta que eu usei muito: <code class="language-plaintext highlighter-rouge">xxd</code> com offsets específicos. Quando o <code class="language-plaintext highlighter-rouge">javap</code> reclamava de algo, eu ia direto no byte:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xxd <span class="nt">-s</span> 270 <span class="nt">-l</span> 4 CompiledBrainfuck.class
</code></pre></div></div>

<p>Isso mostra 4 bytes a partir da posição 270. Útil pra conferir se um valor específico tá sendo escrito certo.</p>

<h2 id="recapitulando">Recapitulando</h2>

<p>Nesse post a gente viu:</p>

<ul>
  <li>O formato binário do ClassFile da JVM, byte por byte</li>
  <li>Por que big endian importa e como escrever números de múltiplos bytes na ordem certa</li>
  <li>Como o constant pool funciona (Utf8, Class, Methodref, NameAndType)</li>
  <li>Os descritores de tipo da JVM (<code class="language-plaintext highlighter-rouge">([Ljava/lang/String;)V</code>)</li>
  <li>Como montar um gerador de <code class="language-plaintext highlighter-rouge">.class</code> do zero em Node.js</li>
</ul>

<p>Na <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-3-gerando-bytecode">parte 3</a>, a gente vai gerar bytecode JVM a partir da IR do Brainfuck. E vai ter que lidar com a StackMapTable, que quase me fez desistir.</p>

<p>Commits relevantes: <a href="https://github.com/geeksilva97/brainjuck/commit/234b285"><code class="language-plaintext highlighter-rouge">234b285</code> (class generator creates base structure)</a>, <a href="https://github.com/geeksilva97/brainjuck/commit/31dd14d"><code class="language-plaintext highlighter-rouge">31dd14d</code> (massive refactoring in the JVM bytecode reader)</a>, e <a href="https://github.com/geeksilva97/brainjuck/commit/1231ffc"><code class="language-plaintext highlighter-rouge">1231ffc</code> (beat u jvm)</a> - que foi quando eu finalmente entendi o formato.</p>

<p>Por hoje é só. Abraços.</p>]]></content><author><name></name></author><category term="programacao" /><category term="compiladores" /><category term="jvm" /><category term="brainfuck" /><category term="nodejs" /><summary type="html"><![CDATA[Na parte 1 a gente construiu um interpretador de Brainfuck com tokenizer, parser e uma Representação Intermediária. Agora a gente precisa entender o formato que a JVM espera receber pra poder gerar nosso próprio bytecode.]]></summary></entry><entry xml:lang="pt-BR"><title type="html">Compilando Brainfuck pra JVM, parte 3: gerando bytecode</title><link href="https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-3-gerando-bytecode.html" rel="alternate" type="text/html" title="Compilando Brainfuck pra JVM, parte 3: gerando bytecode" /><published>2026-03-16T00:00:00+00:00</published><updated>2026-03-16T00:00:00+00:00</updated><id>https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-3-gerando-bytecode</id><content type="html" xml:base="https://codesilva.com/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-3-gerando-bytecode.html"><![CDATA[<p>Na <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-1-o-interpretador">parte 1</a> a gente construiu o interpretador e a IR. Na <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-2-dissecando-o-formato-class">parte 2</a> a gente entendeu o formato <code class="language-plaintext highlighter-rouge">.class</code> e montou o gerador de ClassFile. Agora é hora de juntar tudo e gerar bytecode JVM de verdade.</p>

<p>Esse é o post mais denso da série. No final, você vai rodar <code class="language-plaintext highlighter-rouge">java CompiledBrainfuck</code> e ver um “Hello, World!” gerado pelo seu próprio compilador.</p>

<h2 id="o-modelo-de-memória-no-bytecode">O modelo de memória no bytecode</h2>

<p>Antes de traduzir cada instrução, a gente precisa decidir como mapear o modelo do Brainfuck (fita de memória + ponteiro) pros conceitos da JVM.</p>

<p>Na JVM, um método tem <strong>variáveis locais</strong> numeradas a partir de 0. No nosso <code class="language-plaintext highlighter-rouge">main(String[] args)</code>:</p>

<table>
  <thead>
    <tr>
      <th>Slot</th>
      <th>Tipo</th>
      <th>Uso</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td><code class="language-plaintext highlighter-rouge">String[]</code></td>
      <td>argumentos do método (args)</td>
    </tr>
    <tr>
      <td>1</td>
      <td><code class="language-plaintext highlighter-rouge">byte[]</code></td>
      <td>fita de memória (30.000 células)</td>
    </tr>
    <tr>
      <td>2</td>
      <td><code class="language-plaintext highlighter-rouge">int</code></td>
      <td>ponteiro (posição atual na fita)</td>
    </tr>
  </tbody>
</table>

<p>O slot 0 é reservado pros argumentos - eu tentei usar ele pra memória e a JVM deu <code class="language-plaintext highlighter-rouge">VerifyError</code>. Também tentei colocar o <code class="language-plaintext highlighter-rouge">byte[]</code> e o <code class="language-plaintext highlighter-rouge">int</code> no mesmo slot e descobri que a JVM não deixa misturar tipos num slot. Faz sentido quando você pensa na verificação de tipos, mas na hora foi confuso.</p>

<p>O preâmbulo do bytecode aloca essas variáveis:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// sipush 30000    - coloca 30000 na stack</span>
<span class="c1">// newarray byte   - cria byte[30000]</span>
<span class="c1">// astore_1        - guarda no slot 1</span>
<span class="c1">// iconst_0        - coloca 0 na stack</span>
<span class="c1">// istore_2        - guarda no slot 2 (ponteiro = 0)</span>

<span class="kd">const</span> <span class="nx">preamble</span> <span class="o">=</span> <span class="p">[</span>
  <span class="mh">0x11</span><span class="p">,</span> <span class="mh">0x75</span><span class="p">,</span> <span class="mh">0x30</span><span class="p">,</span>  <span class="c1">// sipush 30000</span>
  <span class="mh">0xbc</span><span class="p">,</span> <span class="mh">0x08</span><span class="p">,</span>        <span class="c1">// newarray byte (T_BYTE = 8)</span>
  <span class="mh">0x4c</span><span class="p">,</span>              <span class="c1">// astore_1</span>
  <span class="mh">0x03</span><span class="p">,</span>              <span class="c1">// iconst_0</span>
  <span class="mh">0x3d</span>               <span class="c1">// istore_2</span>
<span class="p">];</span>
</code></pre></div></div>

<p>São 8 bytes. Todo bytecode gerado começa com esse preâmbulo.</p>

<h2 id="traduzindo-cada-instrução-da-ir">Traduzindo cada instrução da IR</h2>

<p>Cada instrução da IR vira uma sequência de opcodes JVM.</p>

<h3 id="incrementn">increment(n)</h3>

<p>A operação <code class="language-plaintext highlighter-rouge">cells[head] += n</code> em bytecode:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aload_1       0x2b    // carrega o array (slot 1) na stack
iload_2       0x1c    // carrega o ponteiro (slot 2) na stack
dup2          0x5c    // duplica os dois valores no topo
baload        0x33    // pega cells[head] (consome array + index, empilha o byte)
sipush n      0x11    // coloca n na stack
iadd          0x60    // soma
i2b           0x91    // converte de int pra byte (trunca pra 8 bits)
bastore       0x54    // guarda o resultado em cells[head]
</code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">dup2</code> é o truque aqui. O <code class="language-plaintext highlighter-rouge">baload</code> consome a referência do array e o índice da stack, mas a gente ainda precisa deles pro <code class="language-plaintext highlighter-rouge">bastore</code> depois. Então duplica antes de carregar o valor.</p>

<p>Em JavaScript:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">increment</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[</span>
    <span class="mh">0x2b</span><span class="p">,</span>                    <span class="c1">// aload_1</span>
    <span class="mh">0x1c</span><span class="p">,</span>                    <span class="c1">// iload_2</span>
    <span class="mh">0x5c</span><span class="p">,</span>                    <span class="c1">// dup2</span>
    <span class="mh">0x33</span><span class="p">,</span>                    <span class="c1">// baload</span>
    <span class="mh">0x11</span><span class="p">,</span> <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">n</span><span class="p">),</span> <span class="c1">// sipush n</span>
    <span class="mh">0x60</span><span class="p">,</span>                    <span class="c1">// iadd</span>
    <span class="mh">0x91</span><span class="p">,</span>                    <span class="c1">// i2b</span>
    <span class="mh">0x54</span>                     <span class="c1">// bastore</span>
  <span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Cada <code class="language-plaintext highlighter-rouge">increment</code> gera 10 bytes de bytecode.</p>

<h3 id="move_headh">move_head(h)</h3>

<p>Essa é simples - só carrega o valor absoluto e guarda no slot 2:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">move_head</span><span class="p">(</span><span class="nx">h</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[</span>
    <span class="mh">0x11</span><span class="p">,</span> <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">h</span><span class="p">),</span> <span class="c1">// sipush h</span>
    <span class="mh">0x3d</span>                     <span class="c1">// istore_2</span>
  <span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>4 bytes. Por isso a decisão de usar posição absoluta no parser (<a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-1-o-interpretador">parte 1</a>) simplifica as coisas aqui.</p>

<h3 id="output">output</h3>

<p>Pra imprimir um caractere, a gente precisa chamar <code class="language-plaintext highlighter-rouge">System.out.print(char)</code>. Em bytecode:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">output</span><span class="p">({</span> <span class="nx">fieldRefIndex</span><span class="p">,</span> <span class="nx">methodRefIndex</span> <span class="p">})</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[</span>
    <span class="mh">0xb2</span><span class="p">,</span> <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">fieldRefIndex</span><span class="p">),</span>   <span class="c1">// getstatic System.out</span>
    <span class="mh">0x2b</span><span class="p">,</span>                                   <span class="c1">// aload_1 (array)</span>
    <span class="mh">0x1c</span><span class="p">,</span>                                   <span class="c1">// iload_2 (ponteiro)</span>
    <span class="mh">0x33</span><span class="p">,</span>                                   <span class="c1">// baload (carrega cells[head])</span>
    <span class="mh">0x92</span><span class="p">,</span>                                   <span class="c1">// i2c (converte int pra char)</span>
    <span class="mh">0xb6</span><span class="p">,</span> <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">methodRefIndex</span><span class="p">)</span>   <span class="c1">// invokevirtual print(char)</span>
  <span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">fieldRefIndex</code> e <code class="language-plaintext highlighter-rouge">methodRefIndex</code> são índices no constant pool que referenciam <code class="language-plaintext highlighter-rouge">System.out</code> e <code class="language-plaintext highlighter-rouge">PrintStream.print(C)V</code>. Esses índices vêm do gerador de ClassFile que a gente construiu na <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-2-dissecando-o-formato-class">parte 2</a>.</p>

<p>O <code class="language-plaintext highlighter-rouge">i2c</code> (int to char) é necessário porque <code class="language-plaintext highlighter-rouge">baload</code> retorna um <code class="language-plaintext highlighter-rouge">int</code> e o método <code class="language-plaintext highlighter-rouge">print</code> espera um <code class="language-plaintext highlighter-rouge">char</code>. Eu originalmente tentei com <code class="language-plaintext highlighter-rouge">(I)V</code> no descritor (print recebendo int), mas o output saía como número em vez de caractere. O descritor correto é <code class="language-plaintext highlighter-rouge">(C)V</code>.</p>

<h3 id="input">input</h3>

<p>Ler de <code class="language-plaintext highlighter-rouge">System.in.read()</code> e guardar na célula:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">input</span><span class="p">({</span> <span class="nx">fieldRefIndex</span><span class="p">,</span> <span class="nx">methodRefIndex</span> <span class="p">})</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[</span>
    <span class="mh">0xb2</span><span class="p">,</span> <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">fieldRefIndex</span><span class="p">),</span>   <span class="c1">// getstatic System.in</span>
    <span class="mh">0xb6</span><span class="p">,</span> <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">methodRefIndex</span><span class="p">),</span>  <span class="c1">// invokevirtual read()</span>
    <span class="mh">0x2b</span><span class="p">,</span>                                   <span class="c1">// aload_1 (array)</span>
    <span class="mh">0x5f</span><span class="p">,</span>                                   <span class="c1">// swap</span>
    <span class="mh">0x1c</span><span class="p">,</span>                                   <span class="c1">// iload_2 (ponteiro)</span>
    <span class="mh">0x5f</span><span class="p">,</span>                                   <span class="c1">// swap</span>
    <span class="mh">0x54</span>                                    <span class="c1">// bastore</span>
  <span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Os dois <code class="language-plaintext highlighter-rouge">swap</code> são necessários por causa da ordem da stack. O <code class="language-plaintext highlighter-rouge">bastore</code> espera <code class="language-plaintext highlighter-rouge">arrayref, index, value</code> nessa ordem, de baixo pra cima. Mas depois do <code class="language-plaintext highlighter-rouge">invokevirtual read()</code>, o valor lido tá no topo. A gente precisa reorganizar.</p>

<p>Segue o estado da stack passo a passo:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Depois do read():     [valor_lido]
Depois do aload_1:    [valor_lido, array]
Depois do swap:       [array, valor_lido]
Depois do iload_2:    [array, valor_lido, ponteiro]
Depois do swap:       [array, ponteiro, valor_lido]
bastore consome tudo: []
</code></pre></div></div>

<h3 id="jump_eqz-e-jump_neqz">jump_eqz e jump_neqz</h3>

<p>Os saltos condicionais. O Brainfuck <code class="language-plaintext highlighter-rouge">[</code> vira <code class="language-plaintext highlighter-rouge">jump_eqz</code> (pula se zero) e <code class="language-plaintext highlighter-rouge">]</code> vira <code class="language-plaintext highlighter-rouge">jump_neqz</code> (pula se não-zero):</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">jump_eqz</span><span class="p">(</span><span class="nx">offset</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[</span>
    <span class="mh">0x2b</span><span class="p">,</span>                        <span class="c1">// aload_1</span>
    <span class="mh">0x1c</span><span class="p">,</span>                        <span class="c1">// iload_2</span>
    <span class="mh">0x33</span><span class="p">,</span>                        <span class="c1">// baload (carrega cells[head])</span>
    <span class="mh">0x99</span><span class="p">,</span> <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">offset</span><span class="p">)</span> <span class="c1">// ifeq offset</span>
  <span class="p">];</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">jump_neqz</span><span class="p">(</span><span class="nx">offset</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[</span>
    <span class="mh">0x2b</span><span class="p">,</span>
    <span class="mh">0x1c</span><span class="p">,</span>
    <span class="mh">0x33</span><span class="p">,</span>
    <span class="mh">0x9a</span><span class="p">,</span> <span class="p">...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">offset</span><span class="p">)</span> <span class="c1">// ifne offset</span>
  <span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Cada jump gera 6 bytes. Mas tem um problema: na hora de gerar o bytecode, a gente ainda não sabe o offset do salto. Os offsets dependem do tamanho total do bytecode entre o <code class="language-plaintext highlighter-rouge">[</code> e o <code class="language-plaintext highlighter-rouge">]</code>, e a gente só vai saber isso depois de gerar todas as instruções entre eles.</p>

<h2 id="o-sistema-de-patches">O sistema de patches</h2>

<p>Pra resolver o problema dos offsets desconhecidos, a gente usa um sistema de dois passos.</p>

<p><strong>Passo 1</strong>: gera todo o bytecode com offsets zerados (placeholder) e registra onde cada salto tá:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">patches</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">const</span> <span class="nx">labelPC</span> <span class="o">=</span> <span class="p">{};</span>

<span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">irInstructions</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">instruction</span> <span class="o">=</span> <span class="nx">irInstructions</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
  <span class="nx">labelPC</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">jvmPc</span><span class="p">;</span> <span class="c1">// mapeia índice da IR pro PC do bytecode</span>

  <span class="k">if </span><span class="p">(</span><span class="nx">instruction</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">jump_eqz</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">bytes</span> <span class="o">=</span> <span class="nf">jump_eqz</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// offset 0 = placeholder</span>
    <span class="nx">patches</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span>
      <span class="na">at</span><span class="p">:</span> <span class="nx">jvmPc</span> <span class="o">+</span> <span class="mi">4</span><span class="p">,</span>           <span class="c1">// posição dos 2 bytes de offset</span>
      <span class="na">targetIr</span><span class="p">:</span> <span class="nx">instruction</span><span class="p">.</span><span class="nx">jmp</span><span class="p">,</span>
      <span class="na">branchPc</span><span class="p">:</span> <span class="nx">jvmPc</span> <span class="o">+</span> <span class="mi">3</span>      <span class="c1">// posição do opcode ifeq</span>
    <span class="p">});</span>
    <span class="nx">code</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nx">bytes</span><span class="p">);</span>
    <span class="nx">jvmPc</span> <span class="o">+=</span> <span class="nx">bytes</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="c1">// ... mesmo pra jump_neqz</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Passo 2</strong>: depois de gerar todo o bytecode, percorre os patches e calcula os offsets reais:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">patch</span> <span class="k">of</span> <span class="nx">patches</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">targetPc</span> <span class="o">=</span> <span class="nx">labelPC</span><span class="p">[</span><span class="nx">patch</span><span class="p">.</span><span class="nx">targetIr</span><span class="p">];</span>
  <span class="kd">const</span> <span class="nx">offset</span> <span class="o">=</span> <span class="nx">targetPc</span> <span class="o">-</span> <span class="nx">patch</span><span class="p">.</span><span class="nx">branchPc</span><span class="p">;</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">hi</span><span class="p">,</span> <span class="nx">lo</span><span class="p">]</span> <span class="o">=</span> <span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">offset</span><span class="p">);</span>
  <span class="nx">code</span><span class="p">[</span><span class="nx">patch</span><span class="p">.</span><span class="nx">at</span><span class="p">]</span> <span class="o">=</span> <span class="nx">hi</span><span class="p">;</span>
  <span class="nx">code</span><span class="p">[</span><span class="nx">patch</span><span class="p">.</span><span class="nx">at</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">lo</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>O offset é relativo à posição do opcode de salto, não ao início do bytecode. Se o <code class="language-plaintext highlighter-rouge">ifeq</code> tá na posição 35 e o destino tá na posição 70, o offset é <code class="language-plaintext highlighter-rouge">70 - 35 = 35</code>. Se é um salto pra trás (como o <code class="language-plaintext highlighter-rouge">]</code> voltando pro <code class="language-plaintext highlighter-rouge">[</code>), o offset é negativo.</p>

<p>Esse foi um dos bugs que mais demorou pra achar. No começo eu tava usando offsets absolutos e a JVM reclamava de “bytecode offset out of range”. Só depois de ler a spec com mais cuidado eu entendi que os offsets de <code class="language-plaintext highlighter-rouge">ifeq</code> e <code class="language-plaintext highlighter-rouge">ifne</code> são relativos ao próprio opcode.</p>

<h2 id="a-stackmaptable">A StackMapTable</h2>

<p>Esse é o chefe de fase do projeto.</p>

<p>A JVM a partir da versão 50 (Java 6) exige uma <code class="language-plaintext highlighter-rouge">StackMapTable</code> em todo método que tem saltos. Essa tabela descreve o estado das variáveis locais e da stack em cada ponto de destino de um salto. O verificador da JVM usa isso pra garantir type safety sem precisar executar o código.</p>

<p>Se você não gerar a StackMapTable, a JVM se recusa a carregar o <code class="language-plaintext highlighter-rouge">.class</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Expecting a stackmap frame at branch target 107
</code></pre></div></div>

<p>Dá pra contornar com <code class="language-plaintext highlighter-rouge">java -noverify</code>, mas isso desabilita a verificação inteira. É tipo desligar o alarme de incêndio porque tá apitando.</p>

<h3 id="como-a-stackmaptable-funciona">Como a StackMapTable funciona</h3>

<p>A tabela é uma sequência de “frames”, cada um descrevendo o estado num ponto específico do bytecode. Existem vários tipos de frame, mas pro Brainfuck a gente só precisa de dois:</p>

<p><strong>append_frame (tipo 253)</strong>: usado no primeiro frame. Indica que foram adicionadas variáveis locais em relação ao frame inicial do método. No nosso caso, adicionamos <code class="language-plaintext highlighter-rouge">byte[]</code> (slot 1) e <code class="language-plaintext highlighter-rouge">int</code> (slot 2).</p>

<p><strong>same_frame (tipo 0-63)</strong>: os locais não mudaram desde o frame anterior. O tipo do frame É o offset delta (se cabe em 0-63).</p>

<p><strong>same_frame_extended (tipo 251)</strong>: mesmo que same_frame, mas pra offsets maiores que 63. O offset delta vem nos 2 bytes seguintes.</p>

<h3 id="calculando-os-offset-deltas">Calculando os offset deltas</h3>

<p>O offset delta entre frames não é simplesmente a posição do destino. A fórmula:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>primeiro frame:   offset_delta = targetPc
demais frames:    offset_delta = targetPc - previousTargetPc - 1
</code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">-1</code> existe porque o frame anterior já “consome” uma posição. Eu demorei pra entender isso e o resultado era que os frames ficavam sempre 1 byte deslocados.</p>

<h3 id="gerando-a-stackmaptable">Gerando a StackMapTable</h3>

<p>Durante a geração de bytecode, a gente registra os destinos de saltos. Depois, ordena eles por posição e calcula os frames:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">computeStackMapTable</span><span class="p">(</span><span class="nx">entries</span><span class="p">,</span> <span class="nx">stackMapTableConstantIndex</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">buf</span> <span class="o">=</span> <span class="p">[];</span>

  <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">entries</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">entry</span> <span class="o">=</span> <span class="nx">entries</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">i</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// primeiro frame: append_frame com 2 locais</span>
      <span class="nx">buf</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="mi">253</span><span class="p">);</span>                              <span class="c1">// frame type</span>
      <span class="nx">buf</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">offsetDelta</span><span class="p">));</span> <span class="c1">// offset delta</span>
      <span class="nx">buf</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="mi">7</span><span class="p">);</span>                                 <span class="c1">// verification: Object</span>
      <span class="nx">buf</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">byteArrayCPIndex</span><span class="p">));</span>  <span class="c1">// index de [B no constant pool</span>
      <span class="nx">buf</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>                                 <span class="c1">// verification: Integer</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">offsetDelta</span> <span class="o">&lt;=</span> <span class="mi">63</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// same_frame: o tipo É o offset</span>
      <span class="nx">buf</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">offsetDelta</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="c1">// same_frame_extended</span>
      <span class="nx">buf</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="mi">251</span><span class="p">);</span>
      <span class="nx">buf</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">offsetDelta</span><span class="p">));</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="c1">// monta o atributo completo</span>
  <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">[];</span>
  <span class="nx">result</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">stackMapTableConstantIndex</span><span class="p">));</span> <span class="c1">// nome</span>
  <span class="nx">result</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nf">intTo4Bytes</span><span class="p">(</span><span class="mi">2</span> <span class="o">+</span> <span class="nx">buf</span><span class="p">.</span><span class="nx">length</span><span class="p">));</span>             <span class="c1">// tamanho</span>
  <span class="nx">result</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nf">intTo2Bytes</span><span class="p">(</span><span class="nx">entries</span><span class="p">.</span><span class="nx">length</span><span class="p">));</span>              <span class="c1">// num entries</span>
  <span class="nx">result</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nx">buf</span><span class="p">);</span>                                      <span class="c1">// frames</span>

  <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>O tamanho do atributo é <code class="language-plaintext highlighter-rouge">2 + buf.length</code> - 2 bytes pro número de entries, mais o conteúdo dos frames. Eu errei esse cálculo e demorei horas pra achar o bug. O <code class="language-plaintext highlighter-rouge">.class</code> passava no <code class="language-plaintext highlighter-rouge">javap</code> mas a JVM dava erro de verificação. O problema era que eu tava somando <code class="language-plaintext highlighter-rouge">entries.length</code> ao invés de <code class="language-plaintext highlighter-rouge">2</code> como os bytes fixos do campo de contagem.</p>

<h3 id="quando-funciona-sem--noverify">Quando funciona sem -noverify</h3>

<p>Quando eu finalmente acertei a StackMapTable, o programa rodou sem a flag <code class="language-plaintext highlighter-rouge">-noverify</code> pela primeira vez. O commit <a href="https://github.com/geeksilva97/brainjuck/commit/44ff6c2"><code class="language-plaintext highlighter-rouge">44ff6c2</code></a> marca esse momento. Eu atualizei o README pra remover a instrução de usar <code class="language-plaintext highlighter-rouge">-noverify</code> e foi uma das melhores sensações do projeto inteiro.</p>

<h2 id="juntando-tudo">Juntando tudo</h2>

<p>O fluxo completo de compilação:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Lê o arquivo .bf
2. Tokeniza (extrai os 8 comandos válidos)
3. Parse pra IR (com otimizações)
4. Gera bytecode JVM a partir da IR
   - Emite preâmbulo (alocação de memória)
   - Traduz cada instrução
   - Registra patches pra saltos
5. Resolve patches (calcula offsets)
6. Computa StackMapTable
7. Monta o ClassFile (constant pool + métodos + atributos)
8. Escreve o .class
</code></pre></div></div>

<p>O CLI do BrainJuck faz tudo isso em poucas linhas:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#!/usr/bin/env node
</span><span class="k">import</span> <span class="p">{</span> <span class="nx">readFileSync</span><span class="p">,</span> <span class="nx">writeFileSync</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:fs</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">parseBrainfuck</span><span class="p">,</span> <span class="nx">brainfuckIRToJVM</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./index.js</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ClassFileGenerator</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./class_generator.js</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="p">[,,</span> <span class="nx">sourceFile</span><span class="p">,</span> <span class="nx">className</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">CompiledBrainfuck</span><span class="dl">'</span><span class="p">]</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">source</span> <span class="o">=</span> <span class="nf">readFileSync</span><span class="p">(</span><span class="nx">sourceFile</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf-8</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">ir</span> <span class="o">=</span> <span class="nf">parseBrainfuck</span><span class="p">(</span><span class="nx">source</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">generator</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ClassFileGenerator</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">classBytes</span> <span class="o">=</span> <span class="nx">generator</span><span class="p">.</span><span class="nf">generateHelloWorldClass</span><span class="p">(</span>
  <span class="nx">className</span><span class="p">,</span>
  <span class="p">({</span> <span class="nx">symbolicConstantPool</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nf">brainfuckIRToJVM</span><span class="p">(</span><span class="nx">ir</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">input</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">fieldRefIndex</span><span class="p">:</span> <span class="nx">symbolicConstantPool</span><span class="p">.</span><span class="nx">input</span><span class="p">.</span><span class="nx">fieldRef</span><span class="p">,</span>
        <span class="na">methodRefIndex</span><span class="p">:</span> <span class="nx">symbolicConstantPool</span><span class="p">.</span><span class="nx">input</span><span class="p">.</span><span class="nx">readMethodrefIndex</span>
      <span class="p">},</span>
      <span class="na">output</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">fieldRefIndex</span><span class="p">:</span> <span class="nx">symbolicConstantPool</span><span class="p">.</span><span class="nx">output</span><span class="p">.</span><span class="nx">fieldRef</span><span class="p">,</span>
        <span class="na">methodRefIndex</span><span class="p">:</span> <span class="nx">symbolicConstantPool</span><span class="p">.</span><span class="nx">output</span><span class="p">.</span><span class="nx">printlnMethodrefIndex</span>
      <span class="p">}</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">);</span>

<span class="nf">writeFileSync</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">className</span><span class="p">}</span><span class="s2">.class`</span><span class="p">,</span> <span class="k">new</span> <span class="nc">Uint8Array</span><span class="p">(</span><span class="nx">classBytes</span><span class="p">));</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">className</span><span class="p">}</span><span class="s2">.class generated`</span><span class="p">);</span>
</code></pre></div></div>

<p>Compila e roda:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./brainjuck samples/helloworld.bf HelloWorld
java HelloWorld
<span class="c"># Hello, World!</span>
</code></pre></div></div>

<h2 id="testando">Testando</h2>

<p>O projeto tem testes unitários pro tokenizer, parser e geração de bytecode, e um teste de integração que compila o Hello World e roda com <code class="language-plaintext highlighter-rouge">java</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">describe</span><span class="p">,</span> <span class="nx">it</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:test</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">execSync</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:child_process</span><span class="dl">'</span><span class="p">;</span>

<span class="nf">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">integration</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">compila e executa helloworld.bf</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">execSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">./brainjuck samples/helloworld.bf CompiledHelloWorld</span><span class="dl">'</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">output</span> <span class="o">=</span> <span class="nf">execSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">java CompiledHelloWorld</span><span class="dl">'</span><span class="p">).</span><span class="nf">toString</span><span class="p">();</span>
    <span class="nf">assert</span><span class="p">(</span><span class="nx">output</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">Hello, World</span><span class="dl">'</span><span class="p">));</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Se esse teste passa, o compilador gera bytecode JVM válido que a JVM aceita, verifica, e executa corretamente.</p>

<h2 id="recapitulando-a-série">Recapitulando a série</h2>

<p>Em três posts, a gente construiu um compilador de ~700 linhas de JavaScript que transforma Brainfuck em bytecode JVM executável. Interpretador, parser com otimizações, gerador de ClassFile, tradução de IR pra bytecode, sistema de patches pra saltos, e StackMapTable. Sem dependências externas. Cada byte do <code class="language-plaintext highlighter-rouge">.class</code> escrito manualmente.</p>

<p>O código completo tá em <a href="https://github.com/geeksilva97/brainjuck">geeksilva97/brainjuck</a>. Clona, lê, modifica, quebra. Se você quiser ir mais fundo, a <a href="https://docs.oracle.com/javase/specs/jvms/se21/html/">spec da JVM</a> é a referência definitiva. E se quiser ver outra abordagem, o <a href="https://www.youtube.com/watch?v=mbFY3Rwv7XM">Tsoding fez um JIT compiler pra Brainfuck</a> compilando direto pra x86-64.</p>

<p>Por hoje é só. Abraços.</p>]]></content><author><name></name></author><category term="programacao" /><category term="compiladores" /><category term="jvm" /><category term="brainfuck" /><category term="nodejs" /><summary type="html"><![CDATA[Na parte 1 a gente construiu o interpretador e a IR. Na parte 2 a gente entendeu o formato .class e montou o gerador de ClassFile. Agora é hora de juntar tudo e gerar bytecode JVM de verdade.]]></summary></entry><entry xml:lang="pt-BR"><title type="html">Eu criei um compilador para JVM só para provar um ponto</title><link href="https://codesilva.com/programacao/2026/03/09/eu-criei-um-compilador-para-jvm-so-para-provar-um-ponto.html" rel="alternate" type="text/html" title="Eu criei um compilador para JVM só para provar um ponto" /><published>2026-03-09T00:00:00+00:00</published><updated>2026-03-09T00:00:00+00:00</updated><id>https://codesilva.com/programacao/2026/03/09/eu-criei-um-compilador-para-jvm-so-para-provar-um-ponto</id><content type="html" xml:base="https://codesilva.com/programacao/2026/03/09/eu-criei-um-compilador-para-jvm-so-para-provar-um-ponto.html"><![CDATA[<p>Um dia eu vi um vídeo de um influencer famoso de Java tentando explicar as coisas internas da linguagem. Máquina virtual, JVM, <code class="language-plaintext highlighter-rouge">class loader</code>, o processo de compilação. Tentando, porque o cara não sabia nada do que estava falando. Nada. Estava ali, com milhares de seguidores, explicando conceitos que ele claramente não entendia.</p>

<p>Eu não acho que todo mundo precisa saber JVM no nível de <code class="language-plaintext highlighter-rouge">bytecode</code>. Sério, não acho. Mas quando você <strong>se propõe a explicar</strong> - quando você senta na frente de uma câmera e fala como se fosse referência - é sua obrigação entender o que tá falando. Não precisa ser especialista, mas pelo menos saber o mínimo sobre o processo que você tá descrevendo.</p>

<p>Eu já estava curioso sobre máquinas virtuais fazia um tempo. Não o Java - a máquina virtual em si. O <code class="language-plaintext highlighter-rouge">bytecode</code>, o <code class="language-plaintext highlighter-rouge">constant pool</code>, o formato <code class="language-plaintext highlighter-rouge">.class</code>. Aquele nível que a maioria dos devs nunca precisa tocar. E eu já tinha brincado com Brainfuck antes. Daí eu vi um <a href="https://www.youtube.com/@TsodingDaily">vídeo do Tsoding Daily</a> onde ele <a href="https://www.youtube.com/watch?v=mbFY3Rwv7XM">implementa um JIT compiler pra Brainfuck</a> - traduzindo direto pra código de máquina x86-64, em tempo de execução. Ver alguém construindo algo assim na raça, sem framework, sem abstração - aquilo juntou tudo na minha cabeça.</p>

<p>E se eu pegasse a linguagem mais simples que existe e compilasse ela pra rodar na JVM? Entender a JVM por dentro e fazer algo com Brainfuck, tudo no mesmo projeto.</p>

<p>Brainfuck tem 8 comandos. Oito. <code class="language-plaintext highlighter-rouge">+</code>, <code class="language-plaintext highlighter-rouge">-</code>, <code class="language-plaintext highlighter-rouge">&gt;</code>, <code class="language-plaintext highlighter-rouge">&lt;</code>, <code class="language-plaintext highlighter-rouge">[</code>, <code class="language-plaintext highlighter-rouge">]</code>, <code class="language-plaintext highlighter-rouge">.</code>, <code class="language-plaintext highlighter-rouge">,</code>. É isso. Uma linguagem que cabe num guardanapo. Mas pra compilar ela pra <code class="language-plaintext highlighter-rouge">bytecode</code> JVM? Aí a coisa fica interessante.</p>

<p>O resultado? Um compilador de Brainfuck pra JVM escrito em Node.js. Zero dependências externas. Código escrito na mão. E o projeto mais divertido que eu já fiz.</p>

<h2 id="a-jornada">A jornada</h2>

<p>O primeiro passo foi construir um interpretador. Isso foi rápido - em um dia eu tinha um interpretador funcional em JavaScript. Brainfuck é simples de interpretar. Você tem um array de memória, um ponteiro, e vai executando os comandos. Que nem um caixa de supermercado - você processa um item de cada vez, na ordem.</p>

<p>O problema começou quando eu quis <strong>gerar</strong> bytecode.</p>

<p>Eu precisava entender o formato <code class="language-plaintext highlighter-rouge">.class</code> da JVM. E quando eu digo entender, eu digo byte por byte. O magic number <code class="language-plaintext highlighter-rouge">CAFE BABE</code>, o <code class="language-plaintext highlighter-rouge">constant pool</code>, os descritores de método, os atributos de código. Tudo em binário.</p>

<p>Minha primeira abordagem foi pegar um <code class="language-plaintext highlighter-rouge">.class</code> compilado pelo <code class="language-plaintext highlighter-rouge">javac</code> e dissecar ele com <code class="language-plaintext highlighter-rouge">xxd</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xxd <span class="nt">-s</span> 270 <span class="nt">-l</span> 4 BrainfuckProgram.class
</code></pre></div></div>

<p>Fiquei semanas lendo hex dump. Parece loucura, mas foi assim que eu comecei a entender como a JVM realmente funciona. Eu escrevia um programa Java simples, compilava, e ficava comparando o binário com a spec. Byte por byte.</p>

<blockquote>
  <p>NOTA: Eu escrevi dois artigos detalhados sobre essa parte técnica no blog da Codeminer42: <a href="https://blog.codeminer42.com/the-road-to-jvm-how-to-create-a-brainfuck-interpreter/">The Road To JVM: How To Create A Brainfuck Interpreter</a> e <a href="https://blog.codeminer42.com/the-road-to-jvm-the-jvm-specification/">The Road To JVM: The JVM Specification</a>. Se você quer o detalhe técnico, vale a leitura.</p>
</blockquote>

<h2 id="os-bugs-mais-legais-da-minha-vida">Os bugs mais legais da minha vida</h2>

<p>Eu não tô exagerando quando digo que os bugs desse projeto foram <strong>divertidos</strong>. Em qualquer outro projeto, um <code class="language-plaintext highlighter-rouge">VerifyError</code> da JVM seria frustrante. Aqui, eu ficava animado quando algo quebrava porque significava que eu ia aprender mais uma coisa.</p>

<p>Um dos primeiros: eu tentei guardar um <code class="language-plaintext highlighter-rouge">int</code> e um array de bytes no mesmo slot de variável local. A JVM não deixa. Faz sentido - ela precisa saber o tipo de cada slot pra verificação. Mas eu só descobri isso porque a JVM me mandou um erro detalhado dizendo exatamente o que estava errado:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Caused by: java.lang.VerifyError: Bad type on operand stack
Reason: Type integer is not assignable to reference type
</code></pre></div></div>

<p>Descobri que o slot 0 é reservado pros argumentos do método. Meu array de memória tinha que ir pro slot 1 e o ponteiro pro slot 2. Parece óbvio agora, mas na hora eu passei um bom tempo compilando programas Java com <code class="language-plaintext highlighter-rouge">javac -g:vars</code> pra ver a <code class="language-plaintext highlighter-rouge">LocalVariableTable</code> e confirmar minhas suspeitas.</p>

<p>Outro bug legal: os offsets de jump. No Brainfuck, <code class="language-plaintext highlighter-rouge">[</code> e <code class="language-plaintext highlighter-rouge">]</code> são instruções de loop. No bytecode JVM, isso vira <code class="language-plaintext highlighter-rouge">ifeq</code> e <code class="language-plaintext highlighter-rouge">ifne</code> com offsets em bytes. Eu estava calculando errado e a JVM reclamava de “bytecode offset out of range”. A solução foi um sistema de dois passos - primeiro calcula as posições, depois gera o bytecode com os offsets corretos.</p>

<p>E o boss final: a <strong>StackMapTable</strong>.</p>

<h2 id="o-chefe-de-fase-stackmaptable">O chefe de fase: StackMapTable</h2>

<p>A JVM moderna (versão 50+) exige que todo <code class="language-plaintext highlighter-rouge">.class</code> tenha uma <code class="language-plaintext highlighter-rouge">StackMapTable</code> nos métodos que fazem jumps. É uma estrutura que descreve o estado da stack e das variáveis locais em cada ponto de salto. Se você não gerar isso corretamente, a JVM se recusa a rodar seu código.</p>

<p>Por um bom tempo eu contornei isso rodando com <code class="language-plaintext highlighter-rouge">java -noverify</code>. Funcionava, mas era trapaça. Que nem usar <code class="language-plaintext highlighter-rouge">// @ts-ignore</code> - resolve na hora, mas você sabe que tá errado.</p>

<p>O problema é que a spec da StackMapTable é confusa. Existem vários tipos de frame (<code class="language-plaintext highlighter-rouge">same_frame</code>, <code class="language-plaintext highlighter-rouge">append_frame</code>, <code class="language-plaintext highlighter-rouge">same_frame_extended</code>), cada um com regras diferentes pra calcular o <code class="language-plaintext highlighter-rouge">offset_delta</code>. O primeiro frame é um <code class="language-plaintext highlighter-rouge">append_frame</code> (tipo 253) porque adiciona duas variáveis locais (o array de memória e o ponteiro). Os frames seguintes são <code class="language-plaintext highlighter-rouge">same_frame</code> (tipo 0-63) porque os locais não mudam.</p>

<p>A fórmula do delta: <code class="language-plaintext highlighter-rouge">target_pc - 1 - previous_target_pc</code>.</p>

<p>Quando eu achei que estava tudo certo, funcionou pro caso trivial mas quebrava com mais de dois jumps. Passei horas debugando hex dumps até perceber que o cálculo do tamanho do atributo estava errado - eu somava o número de entries ao tamanho do buffer, mas o correto era <code class="language-plaintext highlighter-rouge">2 + buffer.length</code>. Dois bytes pro número de entries, o resto pro conteúdo.</p>

<p>Quando finalmente funcionou sem <code class="language-plaintext highlighter-rouge">-noverify</code>, eu fiquei uns bons minutos olhando pro terminal sem acreditar. Meses de hex dump, de ler spec, de errar e tentar de novo. E agora o verificador da JVM aceitou meu <code class="language-plaintext highlighter-rouge">.class</code> como válido.</p>

<h2 id="o-que-a-ia-não-conseguiu-fazer">O que a IA não conseguiu fazer</h2>

<p>Uma coisa interessante aconteceu durante o projeto. Quando eu já tinha a ideia de como o bytecode deveria ser gerado, eu pedi pro ChatGPT e pro Claude implementarem. Na época eu estava usando os dois no chat, sem nenhuma ferramenta de coding. Eu tinha o design mental, só queria ver se eles conseguiam traduzir isso em código.</p>

<p>Não funcionou.</p>

<p>O código que eles geraram não rodava. Mas - e isso é importante - serviu de referência. Especialmente o sistema de <code class="language-plaintext highlighter-rouge">patches</code> pra lidar com instruções de jump. Eu peguei a ideia, entendi, e reescrevi do zero.</p>

<p>Isso pra mim é o uso correto de IA. Você usa pra pesquisar, pra ter uma ideia de direção, mas o trabalho de verdade ainda é seu. Que nem o <em>Pragmatic Programmer</em> fala sobre protótipos - você constrói pra aprender, não pra usar diretamente.</p>

<p>Hoje em dia, com Claude Code ou OpenCode rodando Opus, eu acredito que a IA daria conta. Mas mesmo que desse - e esse é o ponto - eu não teria aprendido nada. Se a IA escreve o código por você, o código funciona mas a sua cabeça continua vazia.</p>

<h2 id="por-que-isso-foi-tão-divertido">Por que isso foi tão divertido</h2>

<p>Eu trabalho com software há anos. Já fiz feature, já fiz bugfix, já fiz refactoring em código legado. Tudo isso é importante e eu gosto do que faço. Mas tem algo diferente em construir algo <strong>completamente do zero</strong>, sem framework, sem biblioteca, sem dependência externa.</p>

<p>O BrainJuck é Node.js puro. Usa <code class="language-plaintext highlighter-rouge">node:fs</code> pra ler arquivos, <code class="language-plaintext highlighter-rouge">node:test</code> pra testes, e mais nada. Cada byte do <code class="language-plaintext highlighter-rouge">.class</code> gerado é escrito manualmente. O <code class="language-plaintext highlighter-rouge">constant pool</code>, os descritores de método, as instruções - tudo.</p>

<p>A arquitetura é um pipeline de três estágios:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Brainfuck Source -&gt; Tokenizer -&gt; Parser -&gt; IR -&gt; JVM Bytecode -&gt; ClassFile
</code></pre></div></div>

<p>Não tinha deadline, não tinha sprint, não tinha ticket no Jira. Era só eu, a spec da JVM, e um editor de texto. Quando um <code class="language-plaintext highlighter-rouge">.class</code> gerado rodava de primeira, eu comemorava sozinho. Quando quebrava, eu abria o <code class="language-plaintext highlighter-rouge">xxd</code> e ia caçar o byte errado. As duas coisas eram igualmente boas.</p>

<h2 id="o-que-eu-aprendi">O que eu aprendi</h2>

<p>Além de JVM <code class="language-plaintext highlighter-rouge">bytecode</code> (que, sinceramente, eu duvido que vá usar no dia a dia), eu aprendi coisas que vão além do técnico:</p>

<ol>
  <li>
    <p><strong>Ler specs é uma habilidade.</strong> A spec da JVM é densa, mas precisa. Cada byte tem um significado definido. Aprender a ler specs te torna um programador melhor - você para de depender de tutoriais e vai direto na fonte.</p>
  </li>
  <li>
    <p><strong>Projetos pessoais não precisam ser úteis.</strong> O BrainJuck não resolve nenhum problema real. Ninguém precisa compilar Brainfuck pra JVM. E tá tudo bem. O valor tá no aprendizado. Que nem a Barbara Oakley fala em <em>A Mind for Numbers</em> - aprender coisas aparentemente desconectadas fortalece sua capacidade de resolver problemas em geral.</p>
  </li>
  <li>
    <p><strong>Se a IA escreve por você, você não aprende.</strong> A IA me deu ideias que aceleraram meu entendimento. Mas se eu tivesse deixado ela escrever o compilador, eu não teria entendido nada do que foi feito. O valor estava no processo, não no resultado.</p>
  </li>
  <li>
    <p><strong>Debug de baixo nível é meditativo.</strong> Tem algo zen em olhar hex dump e entender o que cada byte significa. É o oposto do desenvolvimento web moderno onde tudo é abstração sobre abstração.</p>
  </li>
  <li>
    <p><strong>Raiva é um combustível válido.</strong> Às vezes ver alguém falar besteira sobre algo te motiva a ir mais fundo do que a curiosidade sozinha levaria. Não é o combustível mais nobre, mas funciona.</p>
  </li>
</ol>

<h2 id="recomendações">Recomendações</h2>

<p>Se você ficou com vontade de explorar compiladores e máquinas virtuais:</p>

<ul>
  <li>A spec da JVM é gratuita e online: <a href="https://docs.oracle.com/javase/specs/jvms/se21/html/">The Java Virtual Machine Specification</a></li>
  <li><em>Crafting Interpreters</em> do Robert Nystrom - excelente livro sobre como construir linguagens de programação, do zero</li>
  <li>O vídeo do <a href="https://www.youtube.com/watch?v=mbFY3Rwv7XM">Tsoding implementando um JIT pra Brainfuck</a> - pura inspiração ver alguém construindo na raça</li>
  <li>O próprio Brainfuck como linguagem de estudo - é simples o suficiente pra você focar na mecânica do compilador sem se perder na complexidade da linguagem fonte</li>
</ul>

<p>Se você quer aprender a construir o seu próprio, eu escrevi uma série de três posts que ensina passo a passo: <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-1-o-interpretador">parte 1 (interpretador)</a>, <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-2-dissecando-o-formato-class">parte 2 (formato .class)</a>, <a href="/programacao/2026/03/16/compilando-brainfuck-pra-jvm-parte-3-gerando-bytecode">parte 3 (gerando bytecode)</a>.</p>

<p>E o BrainJuck tá <a href="https://github.com/geeksilva97/brainjuck">no GitHub</a>. Zero dependências. Leia o código, brinque, quebre. É pra isso que ele existe.</p>

<p>Por hoje é só. Abraços.</p>]]></content><author><name></name></author><category term="programacao" /><category term="compiladores" /><category term="jvm" /><category term="brainfuck" /><category term="nodejs" /><summary type="html"><![CDATA[Um dia eu vi um vídeo de um influencer famoso de Java tentando explicar as coisas internas da linguagem. Máquina virtual, JVM, class loader, o processo de compilação. Tentando, porque o cara não sabia nada do que estava falando. Nada. Estava ali, com milhares de seguidores, explicando conceitos que ele claramente não entendia.]]></summary></entry><entry xml:lang="pt-BR"><title type="html">Dissequei o OpenCode para provar que você não entende NADA de SKILLS</title><link href="https://codesilva.com/ia/2026/03/06/dissequei-o-opencode-para-provar-que-voce-nao-entende-nada-de-skills.html" rel="alternate" type="text/html" title="Dissequei o OpenCode para provar que você não entende NADA de SKILLS" /><published>2026-03-06T00:00:00+00:00</published><updated>2026-03-06T00:00:00+00:00</updated><id>https://codesilva.com/ia/2026/03/06/dissequei-o-opencode-para-provar-que-voce-nao-entende-nada-de-skills</id><content type="html" xml:base="https://codesilva.com/ia/2026/03/06/dissequei-o-opencode-para-provar-que-voce-nao-entende-nada-de-skills.html"><![CDATA[<p>Outro dia vi alguém num grupo perguntando “como eu crio uma skill pro Claude Code?”, e as respostas eram do tipo “usa esse template”, “instala esse pacote”, “segue esse guia de 20 passos”. Falam de skills como se fosse algo mágico, um recurso avançado que exige conhecimento especial.</p>

<p>Não exige. E pra te convencer disso, fui olhar o código-fonte do <a href="https://github.com/sst/opencode">OpenCode</a> pra ver o que realmente acontece por baixo dos panos.</p>

<p>Mas antes, preciso te explicar como LLMs funcionam de verdade. Sem isso, skill não faz sentido.</p>

<h2 id="llms-não-fazem-nada">LLMs não fazem nada</h2>

<p>Um LLM é uma função. Entra texto, sai texto. Ele não acessa a internet, não lê arquivos, não executa código. Ele prevê o próximo token baseado no que recebeu.</p>

<p>Isso é literalmente tudo.</p>

<p>Quando você manda uma mensagem pro Claude ou pro GPT e ele “lê um arquivo” ou “busca na web”, não é o modelo fazendo isso. É o sistema ao redor dele. O modelo só gera texto. Quem age é o programa que orquestra a conversa.</p>

<h2 id="tools-dando-mãos-ao-modelo">Tools: dando mãos ao modelo</h2>

<p>Pra que um LLM interaja com o mundo real, usamos <strong>tools</strong> (ou <code class="language-plaintext highlighter-rouge">function calling</code>). O fluxo é:</p>

<ol>
  <li>Você envia uma mensagem junto com uma <strong>lista de tools disponíveis</strong> - cada uma com nome, descrição e parâmetros</li>
  <li>O modelo analisa a mensagem e decide se precisa usar alguma tool</li>
  <li>Se sim, ele responde pedindo a execução: <code class="language-plaintext highlighter-rouge">{ "tool": "read_file", "args": { "path": "src/main.ts" } }</code></li>
  <li>O <strong>sistema host</strong> (não o modelo) executa a tool e devolve o resultado</li>
  <li>O modelo recebe o resultado e continua gerando a resposta</li>
</ol>

<p>O modelo nunca executa nada. Ele apenas <strong>pede</strong> pra executar. Quem roda é o agente.</p>

<h2 id="agentes-o-loop-que-conecta-tudo">Agentes: o loop que conecta tudo</h2>

<p>Um agente é esse loop. Simplificando ao máximo:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enquanto não terminou:
    resposta = llm.gerar(mensagens, tools)
    se resposta tem tool_call:
        resultado = executar(tool_call)
        mensagens.append(resultado)
    senão:
        retornar resposta
</code></pre></div></div>

<p>O agente mantém o histórico, injeta as definições de tools, executa as chamadas e alimenta o modelo com os resultados. O OpenCode faz exatamente isso em <a href="https://github.com/sst/opencode/blob/dev/packages/opencode/src/session/prompt.ts"><code class="language-plaintext highlighter-rouge">packages/opencode/src/session/prompt.ts</code></a>.</p>

<blockquote>
  <p>Se você quer entender agentes de verdade, estuda esse loop. Todo o resto é detalhe de implementação.</p>
</blockquote>

<h2 id="como-o-opencode-registra-tools">Como o OpenCode registra tools</h2>

<p>No OpenCode, toda tool implementa uma interface <code class="language-plaintext highlighter-rouge">Tool.Info</code> definida em <a href="https://github.com/sst/opencode/blob/dev/packages/opencode/src/tool/tool.ts"><code class="language-plaintext highlighter-rouge">packages/opencode/src/tool/tool.ts</code></a>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">Info</span><span class="o">&lt;</span><span class="nb">Parameters</span><span class="p">,</span> <span class="nx">M</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="na">id</span><span class="p">:</span> <span class="kr">string</span>
  <span class="na">init</span><span class="p">:</span> <span class="p">(</span><span class="nx">ctx</span><span class="p">?)</span> <span class="o">=&gt;</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="p">{</span>
    <span class="na">description</span><span class="p">:</span> <span class="kr">string</span>
    <span class="na">parameters</span><span class="p">:</span> <span class="nb">Parameters</span>
    <span class="nf">execute</span><span class="p">(</span><span class="nx">args</span><span class="p">,</span> <span class="nx">ctx</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="p">{</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">metadata</span><span class="p">,</span> <span class="nx">output</span> <span class="p">}</span><span class="o">&gt;</span>
  <span class="p">}</span><span class="o">&gt;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Toda tool tem um <code class="language-plaintext highlighter-rouge">id</code>, uma <code class="language-plaintext highlighter-rouge">description</code>, os <code class="language-plaintext highlighter-rouge">parameters</code> que aceita e uma função <code class="language-plaintext highlighter-rouge">execute</code>. O <code class="language-plaintext highlighter-rouge">ToolRegistry</code> em <a href="https://github.com/sst/opencode/blob/dev/packages/opencode/src/tool/registry.ts"><code class="language-plaintext highlighter-rouge">packages/opencode/src/tool/registry.ts</code></a> junta todas - built-in, custom e de plugins - e entrega pro modelo a cada interação.</p>

<p>As tools built-in são registradas assim:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="p">[</span>
  <span class="nx">ReadTool</span><span class="p">,</span> <span class="nx">GlobTool</span><span class="p">,</span> <span class="nx">GrepTool</span><span class="p">,</span> <span class="nx">EditTool</span><span class="p">,</span> <span class="nx">WriteTool</span><span class="p">,</span>
  <span class="nx">BashTool</span><span class="p">,</span> <span class="nx">TaskTool</span><span class="p">,</span> <span class="nx">WebFetchTool</span><span class="p">,</span> <span class="nx">SkillTool</span><span class="p">,</span>
  <span class="c1">// ...</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Repara nesse <code class="language-plaintext highlighter-rouge">SkillTool</code> ali no meio. Guarda esse nome.</p>

<h2 id="agora-sim-o-que-é-uma-skill">Agora sim: o que é uma skill?</h2>

<p>Uma skill no OpenCode é um arquivo markdown chamado <code class="language-plaintext highlighter-rouge">SKILL.md</code> com um frontmatter YAML:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">agents-sdk</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Build AI agents on Cloudflare Workers using the Agents SDK</span>
<span class="nn">---</span>

<span class="c1"># Cloudflare Agents SDK</span>

<span class="s">Aqui vão instruções detalhadas, exemplos de código,</span>
<span class="s">referências, boas práticas...</span>
</code></pre></div></div>

<p>Isso. Um arquivo <code class="language-plaintext highlighter-rouge">.md</code> com nome e descrição.</p>

<p>O código que descobre esses arquivos está em <a href="https://github.com/sst/opencode/blob/dev/packages/opencode/src/skill/skill.ts"><code class="language-plaintext highlighter-rouge">packages/opencode/src/skill/skill.ts</code></a>. Ele varre <code class="language-plaintext highlighter-rouge">SKILL.md</code> em diretórios globais (<code class="language-plaintext highlighter-rouge">~/.claude/skills/</code>, <code class="language-plaintext highlighter-rouge">~/.agents/skills/</code>), diretórios do projeto (<code class="language-plaintext highlighter-rouge">.opencode/skills/</code>), caminhos custom e até URLs remotas.</p>

<h2 id="o-truque-skilltool-é-só-uma-tool">O truque: SkillTool é só uma tool</h2>

<p>O <code class="language-plaintext highlighter-rouge">SkillTool</code> em <a href="https://github.com/sst/opencode/blob/dev/packages/opencode/src/tool/skill.ts"><code class="language-plaintext highlighter-rouge">packages/opencode/src/tool/skill.ts</code></a> é uma <strong>tool como qualquer outra</strong>. Na sua função <code class="language-plaintext highlighter-rouge">init()</code>, ele:</p>

<ol>
  <li>Escaneia todos os <code class="language-plaintext highlighter-rouge">SKILL.md</code> disponíveis</li>
  <li>Monta sua própria descrição listando o que encontrou:</li>
</ol>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;available_skills&gt;</span>
  <span class="nt">&lt;skill&gt;</span>
    <span class="nt">&lt;name&gt;</span>agents-sdk<span class="nt">&lt;/name&gt;</span>
    <span class="nt">&lt;description&gt;</span>Build AI agents on Cloudflare Workers...<span class="nt">&lt;/description&gt;</span>
  <span class="nt">&lt;/skill&gt;</span>
<span class="nt">&lt;/available_skills&gt;</span>
</code></pre></div></div>

<p>Essa descrição vai pro modelo junto com as outras tools. Quando o modelo decide que precisa de uma skill, faz uma chamada de tool normal:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"tool"</span><span class="p">:</span><span class="w"> </span><span class="s2">"skill"</span><span class="p">,</span><span class="w"> </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"agents-sdk"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>O <code class="language-plaintext highlighter-rouge">SkillTool</code> recebe essa chamada, lê o <code class="language-plaintext highlighter-rouge">SKILL.md</code> correspondente e devolve o conteúdo. O modelo usa essas instruções pra continuar o trabalho.</p>

<p>Releia. É o mesmo fluxo de qualquer tool. O modelo pede, o sistema lê um arquivo, o conteúdo volta pro contexto.</p>

<p>Pensa assim: se o modelo pode chamar <code class="language-plaintext highlighter-rouge">read_file</code> pra ler um arquivo de código, por que não pode chamar uma tool pra ler um arquivo de instruções? É exatamente isso que a <code class="language-plaintext highlighter-rouge">SkillTool</code> faz. A diferença é só a convenção - um lugar padronizado pra colocar instruções reutilizáveis que o modelo puxa sob demanda.</p>

<h2 id="como-criar-uma-skill-de-verdade">Como criar uma skill, de verdade</h2>

<ol>
  <li>Cria uma pasta dentro de <code class="language-plaintext highlighter-rouge">.opencode/skills/</code> (ou <code class="language-plaintext highlighter-rouge">.claude/skills/</code>, dependendo do agente)</li>
  <li>Coloca um <code class="language-plaintext highlighter-rouge">SKILL.md</code> dentro com frontmatter <code class="language-plaintext highlighter-rouge">name</code> e <code class="language-plaintext highlighter-rouge">description</code></li>
  <li>Escreve as instruções em markdown</li>
</ol>

<p>Pronto. Não tem passo 4.</p>

<p>O modelo vai ver a descrição curta da sua skill na lista de tools disponíveis. Se ele achar relevante pro que está fazendo, vai chamar a tool e ler o conteúdo completo. Se não achar, ignora. Você não força nada.</p>

<p>O conteúdo completo só entra no contexto quando o modelo pede. As descrições são leves, o markdown pesado fica de fora até ser necessário.</p>

<p>Uma dica sobre o que colocar numa skill: o Sean Goedecke escreveu sobre <a href="https://www.seangoedecke.com/generate-skills-afterwards">gerar skills depois de resolver o problema</a>, não antes. A ideia é que a LLM escreve skills melhores depois que ela já iterou na solução, porque daí ela destila o que aprendeu. Faz sentido - você não escreve documentação boa antes de entender o problema.</p>

<h2 id="conclusão">Conclusão</h2>

<p>Skill é um arquivo markdown que uma tool lê quando o modelo pede. O mesmo <code class="language-plaintext highlighter-rouge">tool calling</code> que permite o modelo ler arquivos ou executar comandos é o que permite ele carregar uma skill. Não tem framework, não tem runtime, não tem mágica.</p>

<p>Se você sabe escrever markdown, você sabe criar skills.</p>

<p>Por hoje é só.</p>]]></content><author><name></name></author><category term="ia" /><category term="ia" /><category term="agentes" /><category term="llm" /><category term="skills" /><category term="opencode" /><summary type="html"><![CDATA[Skills não são mágica. São arquivos markdown que uma tool lê quando o modelo pede. Pra entender isso, basta entender como LLMs e tools funcionam.]]></summary></entry><entry xml:lang="pt-BR"><title type="html">Por que vibecoding não é engenharia (e o que fazer no lugar)</title><link href="https://codesilva.com/ia/2026/02/24/por-que-vibecoding-nao-e-engenharia-e-o-que-fazer-no-lugar.html" rel="alternate" type="text/html" title="Por que vibecoding não é engenharia (e o que fazer no lugar)" /><published>2026-02-24T00:00:00+00:00</published><updated>2026-02-24T00:00:00+00:00</updated><id>https://codesilva.com/ia/2026/02/24/por-que-vibecoding-nao-e-engenharia-e-o-que-fazer-no-lugar</id><content type="html" xml:base="https://codesilva.com/ia/2026/02/24/por-que-vibecoding-nao-e-engenharia-e-o-que-fazer-no-lugar.html"><![CDATA[<p>Preparando uma talk sobre desenvolvimento assistido por IA, passei um bom tempo lendo o que devs estão falando online, em meetups, nos corredores de conferência. Um padrão aparece o tempo todo: a maioria ou rejeita IA completamente ou usa sem intenção nenhuma. Poucos estão no meio.</p>

<p>O primeiro grupo está escolhendo a obsolescência. O segundo está fazendo <code class="language-plaintext highlighter-rouge">vibecoding</code>: jogando prompts num agente, torcendo pro melhor, e entregando o que volta.</p>

<p>Existe um jeito melhor de trabalhar com agentes e ele tem nome: <a href="https://x.com/karpathy/status/2019137879310836075"><strong>Agentic Engineering</strong></a>.</p>

<h2 id="não-caia-na-armadilha-anti-ia">Não caia na armadilha anti-IA</h2>

<p>Era legal ser anti-IA. Você ficava no seu canto, tirando sarro de como os modelos eram ruins, de como isso era só hype, e de como você ia <strong>conseguir emprego corrigindo o código quebrado dos modelos</strong>.</p>

<p>Esse tempo passou.</p>

<p>O código gerado pelos modelos de ponta (Claude, GPT, até os open source como Kimi K2.5 e GLM 5) é bom. Com <code class="language-plaintext highlighter-rouge">review</code> adequado, você tem código pronto pra produção. Não é perfeito, mas funciona e faz sentido.</p>

<p>Ficando anti-IA você está ativamente perdendo a chance de aprender habilidades que estão em demanda, de ganhos reais de produtividade, e de participar de uma mudança tecnológica que não acontece com frequência. As últimas disrupções desse tamanho? Talvez Agile. Talvez a web em si. Talvez <code class="language-plaintext highlighter-rouge">open source</code> nos anos 90.</p>

<p>Como o <a href="https://antirez.com/news/158">antirez</a> colocou: pra maioria dos projetos, escrever o código você mesmo não faz mais sentido, a não ser pra se divertir. E ele é o cara que escreveu o Redis na mão em C.</p>

<h2 id="você-tem-que-usar-ia-pra-codar">Você TEM QUE usar IA pra codar</h2>

<p>Isso não é opcional.</p>

<p>A gente sempre soube que nosso trabalho não é digitar código. É pensar sobre problemas, tomar decisões, projetar soluções. Bom, agora a gente também não precisa digitar a maior parte.</p>

<p>Seu papel mudou pra decidir e revisar, cada um no seu escopo de tomada de decisão.</p>

<p>Se você não está usando IA, alguém com o mesmo nível que você mais um agente está te superando. É assim que é.</p>

<blockquote>
  <p>“You can’t review what you don’t understand, and you can’t understand what you haven’t done yourself.”</p>

  <p>– Matteo Collina, <a href="https://adventures.nodeland.dev/archive/yes-learning-to-code-is-still-valuable/">Yes, Learning to Code Is Still Valuable</a></p>
</blockquote>

<p>O que me leva a uma distinção que importa.</p>

<h2 id="vibecoding-vs-agentic-engineering">Vibecoding vs. Agentic Engineering</h2>

<p><code class="language-plaintext highlighter-rouge">Vibecoding</code> é o que a maioria faz quando começa com IA. Você abre um chat, descreve o que quer em termos vagos, o agente gera código, você cola em algum lugar, meio que funciona, segue a vida.</p>

<p>Tudo bem pra explorar. Tudo bem pra scripts descartáveis. Não é engenharia.</p>

<p><code class="language-plaintext highlighter-rouge">Agentic Engineering</code> é a versão deliberada. <em>Agentic</em> porque o agente gera o código. <em>Engineering</em> porque você ainda pensa, planeja, itera e revisa.</p>

<p>Pensa em <code class="language-plaintext highlighter-rouge">pair programming</code>. O agente é o <code class="language-plaintext highlighter-rouge">driver</code>. Você é o <code class="language-plaintext highlighter-rouge">navigator</code>. O <code class="language-plaintext highlighter-rouge">navigator</code> não escreve código, mas é ele quem mantém o projeto no trilho.</p>

<h2 id="como-começar">Como começar</h2>

<p>Antes das dicas, um check de realidade.</p>

<p>Você não vai integrar agentes no seu fluxo de trabalho da noite pro dia. Force-se a usar. Não estou falando de tentar por 20 minutos e desistir. Estou falando de semanas de uso consistente.</p>

<p>Você não vai ter resultados ótimos no começo. Tudo bem. Mesma curva de aprendizado que você enfrentou com toda ferramenta que já pegou.</p>

<p>O que funcionou pra mim:</p>

<h3 id="1-separe-tempo-pra-experimentar">1. Separe tempo pra experimentar</h3>

<p>Você precisa forçar isso. Assim como qualquer habilidade, precisa de tempo dedicado. 30 minutos por dia, uma hora dia sim dia não, o que funcionar. O ponto é consistência.</p>

<h3 id="2-todo-side-project-usa-ia">2. Todo side project usa IA</h3>

<p>Aquele PoC que você precisa fazer, a demo pra sua próxima talk, o projeto que você nunca começou. Todos são candidatos perfeitos. Você não precisa do projeto perfeito. Comece com o que tem.</p>

<h3 id="3-ache-tarefas-no-trabalho">3. Ache tarefas no trabalho</h3>

<p>Documentação, parsing de arquivos, traduções, scripts repetitivos. Baixo risco, bom aprendizado.</p>

<p>Quer evoluir mais rápido? Refaça tarefas que você já resolveu, mas dessa vez com um agente. Como você já sabe a solução, pode focar inteiramente em guiar o agente. Uma das formas mais rápidas de melhorar nisso.</p>

<h3 id="4-construa-projetos-inteiros-com-ia">4. Construa projetos inteiros com IA</h3>

<p>Eu fiz: <a href="https://codesilva.com/ia/2026/02/18/construi-um-app-inteiro-com-ia">61 commits, 507 testes, 12 mil linhas de código, em produção em 5 dias</a>. Não escrevi uma linha de código manualmente. Mas tomei cada decisão.</p>

<h2 id="dicas-de-agentic-engineering">Dicas de Agentic Engineering</h2>

<p>É aqui que <code class="language-plaintext highlighter-rouge">vibecoding</code> e <code class="language-plaintext highlighter-rouge">agentic engineering</code> se separam.</p>

<h3 id="tenha-um-plano-antes-de-dar-o-prompt">Tenha um plano antes de dar o prompt</h3>

<p>Antes de pedir qualquer coisa pro agente, saiba o que você quer. Um objetivo e uma sequência aproximada de passos. Não precisa ser detalhado, mas precisa existir.</p>

<p>Use <code class="language-plaintext highlighter-rouge">plan mode</code> se seu agente suporta. Deixe o agente propor uma abordagem, revise, ajuste, daí execute.</p>

<h3 id="trabalhe-passo-a-passo">Trabalhe passo a passo</h3>

<p>Não peça pro agente construir uma feature inteira de uma vez. Quebre em pedaços. Valide cada mudança pequena. Mude a rota se precisar. Interfira. Peça pra refazer.</p>

<p>Depois de cada passo validado: atualize docs, rode o <code class="language-plaintext highlighter-rouge">linter</code>, rode os testes, commite e dê push.</p>

<p>Mudanças pequenas são fáceis de reverter. Grandes são um pesadelo.</p>

<p>Isso não é ideia nova. Isso é o que todo desenvolvedor competente deveria fazer. Agile, <code class="language-plaintext highlighter-rouge">extreme programming</code>, tudo converge pro mesmo ponto: passos pequenos, validados, incrementais.</p>

<h3 id="monte-guardrails-pro-agente">Monte guardrails pro agente</h3>

<p>Agentes funcionam melhor quando têm restrições. Eu não esperava que isso importasse tanto, mas muda tudo.</p>

<p>Dê ao agente arquivos de contexto (<code class="language-plaintext highlighter-rouge">CLAUDE.md</code>, <code class="language-plaintext highlighter-rouge">AGENTS.md</code>, <code class="language-plaintext highlighter-rouge">skills files</code>) pra ele conhecer as convenções do seu codebase. Configure <code class="language-plaintext highlighter-rouge">linters</code> e <code class="language-plaintext highlighter-rouge">formatters</code> pra que o agente leia a saída e corrija as próprias violações. Escreva testes e rode depois de cada mudança pra que você e o agente saibam se algo quebrou. Adicione <code class="language-plaintext highlighter-rouge">git hooks</code> (<code class="language-plaintext highlighter-rouge">pre-commit</code>, <code class="language-plaintext highlighter-rouge">pre-push</code>) como portões automáticos. Mantenha seu <code class="language-plaintext highlighter-rouge">pipeline</code> de CI/CD como checkpoint final.</p>

<p>Nenhuma dessas ferramentas é nova. Elas existiam antes de IA e sempre foram boas práticas. Mas agora são a infraestrutura que impede o desenvolvimento assistido por IA de sair dos trilhos. <code class="language-plaintext highlighter-rouge">Linters</code> mais testes mais <code class="language-plaintext highlighter-rouge">hooks</code> é basicamente um parceiro automatizado de <code class="language-plaintext highlighter-rouge">code review</code> em cima do agente.</p>

<h2 id="o-que-falta-aprender">O que falta aprender?</h2>

<p>Muita coisa. Mais do que nunca, na verdade.</p>

<p>Eu tive uma PM na GoDaddy que estimava pontos de tarefas. Mas como você estima algo se não entende o processo de construir?</p>

<p>Você não consegue revisar o que não conhece.</p>

<p>Pra guiar um agente por uma feature, do <code class="language-plaintext highlighter-rouge">frontend</code> ao <code class="language-plaintext highlighter-rouge">backend</code>, você precisa entender UX, modelagem de dados, design de API, segurança, arquitetura. Precisa saber como sistemas se conectam, como quebram, como escalam.</p>

<p>Sistemas distribuídos, algoritmos, redes: são mais valiosos agora, não menos. O caminho de <code class="language-plaintext highlighter-rouge">bootcamp</code> de “aprenda React em 12 semanas” está se fechando. O caminho de fundamentos profundos está escancarado.</p>

<p>No lado de produto: aprenda sobre o produto do seu cliente. Entenda como os projetos se integram. Esse tipo de conhecimento contextual torna um desenvolvedor insubstituível, com ou sem agente.</p>

<h2 id="o-agente-te-amplifica">O agente te amplifica</h2>

<p>IA amplifica o que você já é. Se você é um bom dev que planeja antes de codar, escreve testes e entende o problema antes de resolver, o agente vai te tornar muito melhor.</p>

<p>Se você pula o pensamento, se não se importa em entender, se só faz <code class="language-plaintext highlighter-rouge">vibecoding</code>… vai acabar com uma bagunça. Uma bagunça rápida, mas uma bagunça.</p>

<p>Velocidade não é a mesma coisa que produtividade.</p>

<p>O agente é um multiplicador. E multiplicador só funciona se o que ele está multiplicando vale alguma coisa.</p>

<p>Por hoje é só.</p>

<hr />

<p><strong>Referências:</strong></p>

<ul>
  <li><a href="https://antirez.com/news/158">antirez - Don’t fall into the anti-AI hype</a></li>
  <li><a href="https://adventures.nodeland.dev/archive/yes-learning-to-code-is-still-valuable/">Matteo Collina - Yes, Learning to Code Is Still Valuable</a></li>
  <li><a href="https://www.humanlayer.dev/blog/writing-a-good-claude-md">Writing a Good CLAUDE.md</a></li>
  <li><a href="https://claude.com/blog/skills-explained">Claude Code Skills Explained</a></li>
  <li><a href="https://akitaonrails.com/2026/02/16/vibe-code-do-zero-a-producao-em-6-dias-the-m-akita-chronicles/">Akita on Rails - Vibe Code do Zero à Produção</a></li>
</ul>]]></content><author><name></name></author><category term="ia" /><category term="ia" /><category term="hard skills" /><category term="prompt engineering" /><category term="software development" /><summary type="html"><![CDATA[A maioria dos devs ou rejeita IA completamente ou faz vibecoding sem pensar. Existe um meio termo que funciona: Agentic Engineering.]]></summary></entry><entry xml:lang="pt-BR"><title type="html">Desenvolvimento com IA é 4x mais rápido, mas não 4x mais fácil</title><link href="https://codesilva.com/ia/2026/02/23/desenvolvimento-com-ia-e-4x-mais-rapido-mas-nao-4x-mais-facil.html" rel="alternate" type="text/html" title="Desenvolvimento com IA é 4x mais rápido, mas não 4x mais fácil" /><published>2026-02-23T00:00:00+00:00</published><updated>2026-02-23T00:00:00+00:00</updated><id>https://codesilva.com/ia/2026/02/23/desenvolvimento-com-ia-e-4x-mais-rapido-mas-nao-4x-mais-facil</id><content type="html" xml:base="https://codesilva.com/ia/2026/02/23/desenvolvimento-com-ia-e-4x-mais-rapido-mas-nao-4x-mais-facil.html"><![CDATA[<p>Você descreve o que quer, aperta enter, e sai um app funcionando, completo, de primeira. Não é assim que funciona. Pelo menos não pra nada real.</p>

<p>Eu <a href="https://codesilva.com/ia/2026/02/22/de-zero-codigo-a-thumbnails-gerados-por-ia-em-4-dias">construí o Kanario</a>, um gerador de thumbnails pra blog que puxa um rascunho do WordPress, passa por uma LLM pra gerar prompts de imagem, e gera capas. Tem CLI, bot no Discord, dois backends de LLM, dois backends de geração de imagem, criptografia de credenciais por usuário, deploy no Cloud Run e pipeline completo de CI/CD. Com desenvolvimento assistido por IA, levou quatro dias. Uma estimativa tradicional colocaria entre 7 e 11 semanas pra um dev senior sozinho.</p>

<p><strong>Isso é um ganho de 4-6x.</strong> Mas aconteceu num lugar bem específico.</p>

<h2 id="geração-de-código-ficou-rápida-o-resto-ficou-igual">Geração de código ficou rápida. O resto ficou igual.</h2>

<p>Todo <code class="language-plaintext highlighter-rouge">boilerplate</code>: hierarquias de classes de erro, mocks de teste, parsing de argumentos CLI, handling de interações do Discord, Dockerfile, YAML do GitHub Actions. Foi de horas pra minutos. São coisas onde você sabe exatamente o que quer, mas digitar é tedioso. A IA remove essa fricção.</p>

<p>APIs desconhecidas foram outro grande ganho. Ao invés de gastar uma hora lendo a doc de async polling do RunPod ou tentando entender a verificação de assinatura Ed25519 do Discord, eu descrevia a intenção e iterava no resultado. O ciclo de exploração que costumava ser “lê doc, tenta algo, lê mais doc, conserta” comprimiu pra “descreve o que preciso, ajusta a saída.”</p>

<p>Escrever testes ficou muito mais rápido também. Uma vez que os padrões estavam estabelecidos (mock de <code class="language-plaintext highlighter-rouge">HttpClient</code>, mocks a nível de módulo com variáveis substituíveis, Fastify inject pra rotas HTTP), eu descrevia um novo caso de teste e recebia um teste funcionando em segundos.</p>

<p>Mas <strong>arquitetura</strong>? Toda decisão sobre como o sistema deveria ser estruturado exigiu pensar. Injeção de dependência do <code class="language-plaintext highlighter-rouge">HttpClient</code>. O schema compartilhado do gerador de prompts entre Gemini e Claude. A camada de workflow que permite CLI e Discord usarem a mesma lógica. A hierarquia de erros com hints acionáveis por código de erro do WordPress.</p>

<p>A IA não acelerou essas decisões porque <strong>o gargalo nunca foi digitar o código. Era descobrir a abstração certa.</strong></p>

<h2 id="os-bugs-que-a-ia-não-enxerga-sozinha">Os bugs que a IA não enxerga sozinha</h2>

<p><code class="language-plaintext highlighter-rouge">Prompt engineering</code> foi todo manual. Quando o Qwen começou a renderizar a palavra “robot” como uma cópia do personagem mascote, nenhuma ferramenta de IA poderia ter me avisado. Eu tive que olhar as imagens geradas, notar o problema, levantar a hipótese de que a palavra “robot” estava conflitando com a imagem de referência, testar com “bot buddy” no lugar, e verificar a correção em múltiplas gerações.</p>

<p>Claro, IAs conseguem “ver” imagens hoje. Depois de identificar o problema, eu montei um <code class="language-plaintext highlighter-rouge">smoke test</code> onde o Claude analisava as imagens geradas e comparava com o que era esperado. Funcionou. Mas alguém precisou notar o problema primeiro, formular a hipótese, e montar a validação. A IA executou o teste. Eu defini o que testar.</p>

<p>Mesma coisa com o bug de balanceamento de quota da LLM. O gerador de prompts produzia exatamente 3 cenas com mascote e 1 diorama sem mascote, sempre. Estava balanceando internamente, tentando me dar uma “mistura legal.”</p>

<p>Corrigir exigiu entender que a formulação do <code class="language-plaintext highlighter-rouge">system prompt</code> estava incentivando isso sem eu perceber, e reescrever pra dizer “decida independentemente por cena.” A IA não pegou isso. Eu peguei porque a saída parecia errada.</p>

<h2 id="onde-isso-nos-deixa">Onde isso nos deixa</h2>

<p>O multiplicador é real. 4-6x nesse projeto. Mas vem de eliminar as partes mecânicas do desenvolvimento: traduzir decisões em sintaxe, escrever padrões repetitivos, conectar APIs que você entende conceitualmente mas não decorou.</p>

<p><strong>As decisões em si</strong>, o que construir, como estruturar, quando algo parece errado, continuam sendo suas. E se você tenta pular elas, se deixa a IA tomar decisões de arquitetura por você, acaba com uma codebase que funciona inicialmente mas briga com você toda vez que precisa mudar algo. A IA não conhece suas restrições nem seus usuários. Ela conhece sintaxe.</p>

<p>O pensamento continua sendo o trabalho. A digitação é que ficou mais barata.</p>

<p>Por hoje é só.</p>]]></content><author><name></name></author><category term="ia" /><category term="ia" /><category term="engenharia de software" /><category term="agentes" /><category term="carreira" /><summary type="html"><![CDATA[Construí um produto inteiro com assistência de IA em quatro dias ao invés de dois meses. O ganho é real, mas vem de eliminar digitação, não pensamento.]]></summary></entry></feed>