Escada de abstração (alvo completo):
- ARC simples (default)
- weak/unowned explícitos para quebrar ciclos
- Arenas em blocos
performant {}com análise de lifetime
Progresso desta fase (parcial / atualizado):
- Closures agora armazenam
Weak<RefCell<Environment>>evitando ciclosEnvironment -> Function -> Environment. - Funções normais:
closure = Weak; ambiente vive via cadeia de ambientes (stack léxico). - Métodos "bound": adicionamos
retained_env(strong) para manter o ambiente sintético vivo;closurecontinua Weak. - Chamada com ambiente já coletado emite diagnóstico
Dangling closure environmente executa em ambiente vazio (provisório para debug). - Açúcar sintático:
weak expr,unowned expr, postfix?(upgrade opcional de weak) e!(acesso unowned) mapeiam para builtins internos. - Validador sintático/linter:
art lintagora sinaliza uso semântico inválido de?/!quando a expressão não é referênciaweak/unowned, além de alertarweak/unownedaplicados a literais escalares. - Detector de ciclos protótipo: agora opera sobre ids reais de heap (
HeapComposite) construindo grafo de objetos vivos; algoritmo Tarjan SCC. Classificaisolated(sem incoming externo),reachable_from_roote marcaleak_candidate = isolated && !reachable_from_root. - Sugestões: arestas internas iniciais + ranking (score = out_deg(from)+in_deg(to)) top 3.
- Métricas em runtime atuais:
weak_created,weak_upgrades,weak_dangling,unowned_created,unowned_dangling,cycle_reports_run,cycle_leaks_detected,strong_increments,strong_decrements,objects_finalized,heap_alive,avg_out_degree,avg_in_degree.
O runtime agora expõe métricas por arena quando blocos performant {} são usados. Elas aparecem na saída do CLI (art metrics --json) e no relatório compacto. Principais chaves:
arena_alloc_count: { arena_id: alloc_count }— número de alocações registradas na arena identificada porarena_id.objects_finalized_per_arena: { arena_id: finalized_count }— quantos objetos daquela arena tiveram seus finalizers executados e foram contabilizados como finalizados.finalizer_promotions_per_arena: { arena_id: promotions }— quantos handles foram promovidos ao root porque um finalizer daquela arena criou referências sobreviventes.
Notas de interpretação e limites:
arena_idé um identificador interno (u32) estável durante a execução do programa. Não há garantia sobre densidade ou ordenação entre ids (pode haver gaps).- Valores são contadores não-negativos (usize) e podem ser zero quando a execução não usou arenas.
- Modelo principal: ARC (contagem automática de referências) com contadores separados para strong e weak.
- Tipos de referência adicionais:
- Weak: ponteiro fraco que pode ser atualizado (upgrade opcional para Option).
- Unowned: referência não-owning que assume validade enquanto o dono existir; em modo debug valida
alivee registra diagnóstico se alvo morto.
- Finalizers: funções associadas a objetos heap que são executadas quando o objeto perde o último strong.
- Arenas: blocos lexicais
performant {}que permitem alocações temporárias com contabilização e finalização por arena.
- Todo valor composto (arrays, structs, enums, closures, objetos wrapper) é heapificado e representado por um
ObjHandle(internamente u64/u32). - Estrutura mínima por objeto (
HeapObject):- value: os dados/variant
- strong: contador de referências fortes (usize)
- weak: contador de referências weak (usize)
- alive: booleano derivado (
strong > 0durante execução) - arena_id: Option — se o objeto foi alocado/atribuído a uma arena
Observação: a implementação atual usa um único mapa heap_objects contendo todos os objetos; o arena_id é uma etiqueta lógica usada para finalização e métricas.
- weak(x) cria um wrapper fraco ao redor de x (ou reusa o id se x já for composto).
- upgrade(weak) / postfix ? tenta retornar Some(handle) se o alvo estiver alive, senão None.
- Weak não afeta a vida do objeto (não incrementa strong).
- Em produção, unowned é mais permissivo (sem checagens) para reduzir overhead, mas é categorizado como comportamento inseguro se usado incorretamente.
-
Execução de finalizers
- Quando
strongchega a 0 um objeto é marcadoalive=falsee qualquer finalizer associado é executado imediatamente. - Finalizers são executados num frame filho temporário. Isso permite que o finalizer crie handles locais fortes; o runtime irá identificar e promover (se necessário) handles que devam sobreviver após finalização.
- Quando
-
Sweep / remoção
- Após execução dos finalizers e decréscimos recursivos de strong nos objetos referenciados, o runtime executa uma passagem de limpeza que remove objetos com
alive == false && weak == 0. - Objetos com
weak > 0permanecem até que os weak sejam liberados.
- Após execução dos finalizers e decréscimos recursivos de strong nos objetos referenciados, o runtime executa uma passagem de limpeza que remove objetos com
Observações de estabilidade e robustez:
- A ordem de execução dos finalizers é determinística por id (ordenamento estável). Implementações críticas podem precisar de uma ordem baseada em grafo — isso é roadmap futuro.
- Pode ser feita uma multi-pass sweep para estabilizar efeitos de finalizers que desencadeiam novas finalizações.
- Objetivo: permitir alocações temporárias de baixa latência controlando os custos de contagem global.
- Semântica:
- Alocações dentro de
performant {}são marcadas comarena_ide contabilizadas emarena_alloc_count. - Ao sair do bloco,
finalize_arena(arena_id)é invocado: decresce fortes, executa finalizers pertinentes e realiza sweep local/determinístico. - Finalizers de objetos de arena podem promover handles para o root; tais promoções são atribuídas à arena via
finalizer_promotions_per_arena.
- Alocações dentro de
O prelude agora expõe um fluxo explícito para reuso de arena sem depender apenas de performant {}:
arena_new() -> Int- Reserva um
arena_idreutilizável para o processo atual.
- Reserva um
arena_with(arena_id, callback)- Executa
callbackdentro da arena informada e finaliza essa arena ao final da chamada. - O mesmo
arena_idpode ser reutilizado em múltiplas chamadas.
- Executa
arena_release(arena_id) -> Bool- Força finalização imediata da arena (retorna
falsepara id desconhecido).
- Força finalização imediata da arena (retorna
Exemplo:
let aid = arena_new()
func phase() {
let _tmp = [1, 2, 3]
}
arena_with(aid, phase)
arena_with(aid, phase)
arena_release(aid)
- Regras estáticas (checagem conservadora):
O runtime expõe métricas voltadas para diagnóstico e telemetria. As chaves principais disponíveis via art metrics --json:
-
Globais:
handled_errors: número de erros manejados durante execuçãoexecuted_statements: número de statements executadoscrash_free: proporção em percentagem dos statements sem crashfinalizer_promotions: total de handles promovidos durante execução de finalizersweak_created,weak_upgrades,weak_danglingunowned_created,unowned_danglingcycle_reports_run— contagem de detecções de ciclos em execução de debug
-
Por-arena (quando arenas foram usadas):
arena_alloc_count: { arena_id: alloc_count }objects_finalized_per_arena: { arena_id: finalized_count }finalizer_promotions_per_arena: { arena_id: promotions }
Interpretação rápida:
arena_alloc_countajuda a localizar blocos com muitas alocações temporárias.objects_finalized_per_arenamostra quantos objetos de cada arena executaram finalizers (ajuda a diagnosticar trabalho pós-escopo).finalizer_promotions_per_arenaidentifica finalizers que promovem referências para o heap global (frequentemente um anti-pattern para temporários).
Boas práticas de telemetria:
- Em CI, alerte quando
finalizer_promotions_per_arenaexceder um limiar relativo (ex.: > 5% das alocações da arena). - Correlacione
arena_alloc_countcom latência/throughput para detectar hotspots.
APIs destinadas a testes e diagnósticos (visíveis nos helpers do Interpreter):
debug_heap_register(val) -> id— registra valor no heap e retorna id.debug_heap_register_in_arena(val, arena_id) -> id— registra valor comarena_id.debug_define_global(name, val)— define um global para fins de teste.debug_heap_remove(id)— simula remoção do último strong ref (invoca dec strong).debug_heap_inc_weak(id)/debug_heap_dec_weak(id)— manipulam contador weak para testes.debug_run_finalizer(id)— força execução do fluxo de finalização (dec_recursive + sweep).debug_sweep_dead()— varre e remove objetos mortos (alive == false && weak == 0).debug_finalize_arena(arena_id)— invoca explicitamente a finalização de uma arena.debug_heap_contains(id)— checa presença no heap.
Use estes helpers quando precisar tornar cenários determinísticos em testes de unidade/integrção.
Nota de implementação: para tornar a adaptação do Arc interno consistente e evitar duplicação
de escrita nos campos strong/weak, as mutações diretas de HeapObject foram centralizadas
em crates/interpreter/src/heap_utils.rs.
- As funções exportadas em
heap_utilsaceitam apenas&mut HeapObjecte realizam a mutação sobre o objeto:inc_strong_obj,dec_strong_obj -> bool,inc_weak_obj,dec_weak_obj -> bool,force_strong_to_one_obj. - Contrato: os helpers NÃO atualizam métricas do
Interpreterporque muitas chamadas ocorrem quando já há um borrow mutável ao mapa de heap; atualizar métricas dali causaria conflitos de borrowing. OInterpreterpermanece responsável por incrementarstrong_increments,strong_decrementse outras métricas quando apropriado, usando o valor retornado pelos helpers (bool) para decidir se um decrement foi efetivo.
Essa separação mantém os pontos de mutação auditáveis (um arquivo) e evita erros do borrow-checker do compilador ao mesmo tempo que preserva a telemetria no nível do runtime.
Exemplo: finalizer que cria um handle promovido (pseudo-Artcode)
let outside = null
{
let target = [1,2,3]
on_finalize(target, fn() { outside = copy(target) })
}
// após o bloco, target foi finalizado; `outside` foi promovido pelo runtime se o finalizer criou um handle forte
Exemplo: uso de arena e finalizer (pseudo-Artcode)
performant {
let a = [1,2,3] // alocado na arena 1
on_finalize(a, fn() { promoted = wrap(a) })
}
// `a` finalizado; if `promoted` foi criado, será promovido ao root e atribuído à arena na métrica de promoção
- Segurança vs. performance:
unownedoferece performance com risco de dangling; recomenda-se uso com precaução e testes em modo debug. - Finalizers poderosos: permitem cleanup, mas tráfego de promoção pode transformar temporários em permanentes; monitore
finalizer_promotions_per_arena. - Ordenação de finalizers: a estratégia atual é determinística por id; se houver requisitos semânticos mais fortes considere implementar ordenação por grafo ou topológica.
- Ordenação de finalizers baseada em dependências (em vez de id) — tópico para Fase seguinte.
- Otimizações de arenas: bump allocation para reduzir overhead, e integração com planos AOT/JIT para reduzir custos de wrapper.
- Validação mais forte em tempo de compilação para evitar padrões que escapem arenas.
Este documento será atualizado conforme a implementação evolui; abra RFCs para mudanças estruturais que alterem invariantes de runtime.