Skip to content

Codegen: seção final de while top-level gigante no __rtsn_main não executa (corrupção sensível a layout) #1978

Description

@MarcosBrendonDePaula

Resumo

Num programa com um while top-level muito grande no __rtsn_main (~350 statements: dezenas de locais por iteração, chamadas a app.*/egui.*/input.*, métodos de singleton, picking aninhado, seção de UI enorme), a parte final do corpo do loop deixa de executar silenciosamente. No caso concreto (editor do rts-game, main.ts), a seção de render (setCam/drawGPU) nunca rodava → objetos 3D invisíveis, enquanto o resto do frame (ws poll, scene.update, animação) rodava normal.

Assinatura de corrupção de codegen sensível a layout: adicionar/remover statements inócuos (ex.: um S.markA = N;) muda se a seção de render roda ou não. Um contador escrito no fim do loop fica em 0 (nunca setado) mesmo com o loop iterando milhares de vezes.

Contorno (funciona 100%)

Envolver o corpo do frame numa função e chamá-la do loop:

// ANTES (bug): render não roda
while (app.running()) {
  const goOn = app.beginFrame();
  if (!goOn) break;
  /* ...350 statements... render... */   // <- seção final não executa
  app.endFrame();
}

// DEPOIS (ok): render roda sempre
function frame(): void { /* ...350 statements... render...*/ app.endFrame(); }
while (app.running()) {
  if (!app.beginFrame()) break;
  frame();
}

Em função o layout de codegen é diferente e o corpo roda inteiro, de forma estável. (rts-game commit 16d86b2.)

Impacto

Silencioso e perigoso: nenhum panic, nenhum erro de compilação, rts ir compila limpo. Statements simplesmente não rodam. Difícil de diagnosticar (só apareceu porque instrumentamos um contador lido por fora, via WebSocket).

O que já foi tentado / descartado

  • Teoria "método de singleton importado não despacha no top-level do while"FALSA. Repro mínimo (singleton importado com array, scene.update()/computeWorld() + iteração no top-level do while) roda correto. Métodos/campos de singleton importado despacham OK tanto em função quanto no top-level.
  • Repro pequeno denso (loop top-level de 200 iterações com ~30 locais + scene.update/computeWorld + iteração) → passa (200/200). Não reproduz.
  • Repro GUI (createAppAt + app.beginFrame + scene + ~15 locais, 40 frames) → passa. Não reproduz.
  • Fix de aliasing de gcell (PR/commit af57caae, block_depth no lower_let): consertou um bug REAL e separado (um let/const aninhado em loop no __rtsn_main colidindo de nome com um global promovido escrevia na mesma gcell em vez de shadowar). NÃO resolve este heisenbug — o main.ts continua com render=0 sem o function-wrap mesmo depois desse fix.

Ou seja: o bug só reproduz na escala/complexidade completa do __rtsn_main real — é um efeito de limiar (contagem de basic-blocks / Variables / slots numa única função). Cada chamada emite emit_post_call_error_check (2 blocks), então um loop com dezenas de chamadas gera centenas–milhares de blocks num só __rtsn_main.

Investigação de codegen (feita)

Ponteiros levantados (arquivos em crates/rts-codegen-new/src/front/run/):

  • lower.rslower_function (383+), flag is_main (func.name == "__rtsn_main"), lower_block (bump de block_depth).
  • lower.rs:311-321 — nota de que reusar resultado de call efetivo entre blocks já fez Cranelift panicar antes (caching de immutable-gcell como Variable def'd uma vez na entry).
  • trycatch.rs:94-118emit_post_call_error_check cria 2 blocks por chamada.
  • Reads de gcell (Tagged, sem shape/class) num __rtsn_main com milhares de blocks — vale confirmar se a construção de φ (SSA) do Cranelift permanece correta nessa escala.

Hipótese

Overflow de algum limite implícito (basic-blocks / Variables / stack-slots) numa única função __rtsn_main, OU um problema de SSA/φ-construção do Cranelift no __rtsn_main gigante — que a função-wrap evita por dividir o mesmo código em duas funções menores.

Repro

Executar o editor rts-game/main.ts sem a função-wrap (corpo inline no while): a seção de render não roda (validável via a porta de controle WebSocket embutida: drawnLast=0 mesmo com wouldDraw=8). Com a função-wrap: drawnLast=8 estável.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions