prometeu-studio/docs/compiler/pbs/agendas/19. Globals, Synthetic Module Init, and FRAME_RET Agenda.md

15 KiB

Globals, Synthetic Module Init, and FRAME_RET Agenda

Status

Open

Domain Owner

docs/compiler/pbs

Este tema pertence ao domínio PBS do compiler porque afeta diretamente:

  • a surface language de declarações top-level;
  • a detecção de entrypoints pelo frontend PBS;
  • o modelo de inicialização de módulo e programa;
  • o lowering de entrypoint de frame;
  • a relação entre entrypoint lógico do usuário e entrypoint publicado no artefato;
  • o contrato interno entre frontend, IRBackend, IRVM lowering e bytecode final.

Nesta fase, o owner continua sendo compiler/pbs mesmo quando o resultado reutilizar capacidades já existentes da VM, porque a discussão é sobre como o compiler vai expor e orquestrar essas capacidades.

Problema

Hoje o PBS não expõe variáveis globais mutáveis de módulo.

O topo do arquivo aceita declare const, mas declare const é compile-time e não storage runtime. Isso impede formas como:

declare struct Vec2(x: int, y: int);

declare global origin: Vec2 = new Vec2(0, 0);

Ao mesmo tempo:

  1. a VM já tem suporte operacional para globals por slot;
  2. o compiler não modela globals no IR executável atual;
  3. o entrypoint de frame do usuário hoje coincide, na prática, com a função que delimita o frame lógico;
  4. o pipeline atual usa FRAME_RET no final desse fluxo de frame lógico.
  5. a seleção do entrypoint ainda depende de configuração explícita em FrontendSpec, em vez de ser derivada da própria source language do PBS.

Se introduzirmos declare global, o compiler precisará definir:

  1. como globals são inicializados;
  2. quando essa inicialização roda;
  3. como garantir execução uma única vez;
  4. como conviver com um init() custom do usuário;
  5. como detectar de forma canônica quais funções do usuário exercem os papéis de init e frame;
  6. como preservar o contrato de frame lógico quando a função publicada deixar de ser a função frame() original do usuário.

Contexto

O estado atual do sistema é:

  • PBS proíbe let top-level e não expõe storage mutável de módulo;
  • declare const é avaliado em compile time e pode ser inlinado/foldado;
  • inicializadores não constantes como new, call sugar, some, none, if, switch e outras formas executáveis não pertencem ao modelo de declare const;
  • a VM do runtime já possui GetGlobal e SetGlobal;
  • o IRBackend executável atual modela locals, calls, jumps e literais, mas não modela globals;
  • o entrypoint de frame hoje é tratado como raiz do frame lógico, inclusive para o ponto final onde FRAME_RET é emitido;
  • o frontend PBS ainda depende de configuração explícita em FrontendSpec para saber qual callable é o entrypoint publicado;
  • hoje isso aparece como acoplamento a nomes/configuração que deveriam ser deduzidos pela própria linguagem.

O cenário motivador é permitir globals de módulo no PBS sem exigir, nesta fase, mudança de runtime.

Também queremos aproveitar essa discussão para mover a detecção de entrypoint para a source language do PBS, usando atributos explícitos:

[INIT]
fn init() -> void {}

[FRAME]
fn frame() -> void {}

Essa direção permitiria ao frontend PBS:

  1. localizar explicitamente o init do usuário;
  2. localizar explicitamente o frame root do usuário;
  3. montar o wrapper sintético final sem depender de configuração paralela em FrontendSpec;
  4. eliminar, nesta linha de evolução, a configuração explícita atual de entrypoint no registry/spec do frontend.

Esta agenda também cobre a extensão desse modelo para discutir:

  1. se [INIT] deve existir apenas como hook final de programa ou também como surface por módulo;
  2. qual é exatamente o comportamento operacional de [FRAME] como root lógico de frame do usuário;
  3. como [INIT] por módulo, [INIT] final de programa, wrapper sintético e FRAME_RET se compõem sem ambiguidade.

Isso empurra a solução para o compiler:

  1. synth de init por módulo;
  2. synth de wrapper do frame published entrypoint;
  3. eventual símbolo privado boot_done;
  4. redefinição do ponto onde o frame lógico realmente termina;
  5. detecção de init/frame a partir de atributos de source;
  6. remoção progressiva das configs explícitas atuais em FrontendSpec.

Core Questions

  1. PBS deve introduzir declare global como declaração top-level distinta de declare const?
  2. declare global deve aceitar inicializadores executáveis como new Vec2(...), calls e outras expressões lowerables?
  3. O compiler deve sintetizar um module init por módulo que contenha declare global?
  4. A inicialização deve rodar eager no boot lógico do programa ou lazy no primeiro frame?
  5. Deve existir um init() custom do usuário que roda depois de todos os module inits e antes do primeiro frame()?
  6. O PBS deve introduzir atributos explícitos [INIT] e [FRAME] para identificar as funções corretas do usuário?
  7. A descoberta de entrypoint no frontend PBS deve deixar de depender de configuração explícita em FrontendSpec?
  8. [INIT] deve poder existir também em escopo/módulo de forma declarativa para orientar module init explícito, ou o compiler deve manter module init sempre totalmente sintético?
  9. Qual é o contrato preciso de comportamento de [FRAME] no PBS: callable obrigatório, assinatura fixa, frequência esperada e relação com o frame lógico publicado?
  10. O published frame entrypoint deve passar a ser um wrapper sintético, por exemplo __pbs_frame(), em vez do frame() do usuário?
  11. FRAME_RET deve continuar significando "fim do frame lógico" e apenas mudar de owner, saindo da função do usuário para o wrapper sintético?
  12. Como o compiler deve tratar ordem topológica, dependências e ciclos entre globals de módulos distintos?
  13. Qual política vale quando algum module init ou init() do usuário falha: retry no próximo frame, fail-fast, ou outra forma?
  14. Globals exportados por mod.barrel devem se comportar como storage compartilhado de módulo ou como snapshot/importação por valor?

Inputs Already Fixed Elsewhere

Os seguintes pontos já parecem fixos ou fortemente estabelecidos e não devem ser contraditos nesta agenda:

  • a VM já suporta globals em nível de bytecode/execução;
  • declare const não é storage mutável/runtime-initialized;
  • o runtime já sabe executar um entrypoint published por frame;
  • FRAME_RET já é usado como marcador do fim de um frame lógico;
  • o frontend PBS hoje ainda carrega configuração explícita de entrypoint fora da source language;
  • nesta fase queremos evitar mudanças novas no runtime e concentrar a evolução no compiler.

Options

Option A

Adicionar declare global, introduzir [INIT] e [FRAME] como superfícies explícitas para lifecycle/entrypoint, discutir [INIT] final vs [INIT] por módulo, gerar module init sintético por módulo, gerar wrapper sintético de frame published entrypoint e mover o FRAME_RET para esse wrapper.

Option B

Adicionar declare global, mas exigir init totalmente estático ou restrito, sem init() custom do usuário e sem wrapper published separado do frame() atual.

Option C

Não adicionar declare global agora; manter apenas declare const e postergar a discussão até existir um modelo maior de bootstrap/program lifecycle.

Option D

Adicionar declare global, mas exigir lazy materialization no primeiro acesso em vez de module init explícito por ordem topológica.

Tradeoffs

Option A

  • Prós:
    • modelo explícito e fácil de explicar;
    • acomoda declare global com inicializadores realmente executáveis;
    • faz o PBS declarar na própria linguagem quais são os callables de init e frame;
    • abre espaço para discutir de forma unificada init por módulo, init final e root de frame;
    • permite init() custom do usuário sem mudar o runtime;
    • preserva o significado de FRAME_RET como fim do frame lógico;
    • remove um acoplamento indesejado entre frontend PBS e configuração manual de entrypoint em FrontendSpec.
  • Contras:
    • exige nova modelagem no compiler para globals, init synthesis e published frame wrapper;
    • exige regras novas de atributo e validação de unicidade para [INIT] e [FRAME];
    • obriga decisão clara sobre ordem, ciclos e falhas;
    • muda a relação atual entre frame() do usuário e entrypoint publicado.

Option B

  • Prós:
    • escopo menor;
    • reduz a superfície de lifecycle;
    • pode evitar parte da complexidade de wrapper e boot state.
  • Contras:
    • resolve mal o caso motivador com new Vec2(...);
    • produz uma feature de globais com restrição semântica forte demais;
    • deixa o init() do usuário para outra rodada e pode forçar redesign posterior.

Option C

  • Prós:
    • nenhum risco imediato em pipeline/backend;
    • preserva o estado atual do compiler.
  • Contras:
    • não resolve o caso desejado;
    • mantém uma lacuna entre capacidade da VM e surface language do PBS;
    • posterga um problema arquitetural que já apareceu de forma concreta.

Option D

  • Prós:
    • evita bootstrap eager explícito;
    • pode reduzir custo de init em módulos não usados.
  • Contras:
    • complica determinismo e observabilidade;
    • torna mais difícil explicar dependências entre módulos;
    • aumenta risco semântico para reentrância, ciclos e ordem de efeitos;
    • parece desnecessariamente sofisticado para v1.

Recommendation

Seguir com a Option A.

Direção recomendada para discussão:

  1. introduzir declare global como nova declaração top-level distinta de declare const;
  2. permitir inicializadores executáveis lowerables, incluindo new e calls compatíveis com o modelo de lowering executável;
  3. sintetizar um module init por módulo owner de globals;
  4. ordenar os module inits de forma determinística e topológica;
  5. introduzir [INIT] e [FRAME] como superfícies explícitas de detecção do init/frame do usuário;
  6. remover da evolução do PBS a dependência de configuração explícita de entrypoint em FrontendSpec, migrando a seleção para a análise da source language;
  7. discutir explicitamente se [INIT] por módulo será uma surface do usuário ou se módulo continuará com init apenas sintético;
  8. permitir um init() custom opcional do usuário, executado depois de todos os module inits;
  9. publicar um wrapper sintético de frame, por exemplo __pbs_frame(), como verdadeiro frame root;
  10. tratar frame() do usuário como callable normal invocado por esse wrapper;
  11. manter FRAME_RET como marcador do fim do frame lógico, mas emitido no wrapper sintético publicado em vez de no frame() do usuário;
  12. manter toda a primeira fase restrita ao compiler e ao backend pipeline, sem novos requisitos para o runtime além dos contratos já existentes.

Open Questions

  1. O nome surface deve ser exatamente declare global, ou outra forma top-level?
  2. Globals devem exigir initializer obrigatório em v1, ou existirão shells reservados parecidos com builtin const?
  3. [INIT] e [FRAME] devem ser atributos reservados do PBS ou outra forma de marker surface?
  4. [INIT] deve exigir assinatura fixa fn init() -> void?
  5. [FRAME] deve exigir assinatura fixa fn frame() -> void?
  6. Deve existir no máximo um [INIT] e um [FRAME] por programa, por módulo owner, ou por outro escopo?
  7. [INIT] por módulo deve ser permitido como surface explícita do usuário ou ser non-goal da primeira fase?
  8. Se [INIT] por módulo existir, ele roda antes ou no lugar do module init sintético de globals?
  9. A ausência de [INIT] deve ser válida, mas a ausência de [FRAME] deve ser erro hard?
  10. O init() do usuário pertence apenas ao módulo owner do published frame entrypoint ou pode vir de qualquer módulo visível?
  11. O compiler deve sintetizar boot_done como global oculto de programa, local estático do wrapper, ou outro mecanismo interno?
  12. Em caso de trap/falha durante module init ou init(), o próximo frame deve tentar novamente ou o programa deve entrar em estado terminal?
  13. Módulos podem ler globals de outros módulos durante init, desde que a ordem topológica já os tenha materializado?
  14. Como diagnosticar ciclos entre globals intra-módulo e inter-módulo de forma didática?
  15. Qual é a semântica exata de [FRAME] para o compiler e para a documentação da linguagem: raiz de tick, raiz de frame lógico, ou apenas callable nomeado para publicação?
  16. O barrel/export de global deve seguir a mesma superfície de const, ou precisamos de distinção explícita na documentação/export metadata?
  17. O IRBackend deve ganhar instruções explícitas GET_GLOBAL/SET_GLOBAL, ou globals devem ser lowered por outra representação intermediária antes de virar bytecode?
  18. Em que etapa da migração a configuração explícita atual em FrontendSpec deixa de existir por completo?

Expected Decisions to Produce

  1. A surface language e a gramática de declare global.
  2. A surface language e a política de detecção de entrypoint via [INIT] e [FRAME].
  3. O papel de [INIT] por módulo versus module init totalmente sintético.
  4. O contrato semântico de globals de módulo em PBS.
  5. O modelo de inicialização sintética por módulo.
  6. O contrato de init() custom do usuário.
  7. A semântica comportamental de [FRAME] como root lógico do usuário.
  8. A política de ordering e ciclo para init/global dependencies.
  9. O novo owner do FRAME_RET no pipeline de frame.
  10. O plano de remoção das configs explícitas atuais em FrontendSpec.
  11. O recorte exato desta fase como mudança somente de compiler.

Expected Spec Material

  • atualização da Core Syntax specification para declare global;
  • atualização da Core Syntax specification para [INIT] e [FRAME];
  • atualização da Static Semantics specification para typing, visibility e rules de init/global;
  • atualização da Dynamic Semantics specification para lifecycle de globals e boot one-shot;
  • atualização da Lowering spec para globals, module init synthesis, entrypoint detection e published frame wrapper;
  • eventual atualização de AST/IR contracts se essas superfícies forem documentadas.

Non-Goals

  • redefinir a VM para adicionar uma nova capacidade de globals;
  • introduzir um novo hook explícito de runtime nesta fase;
  • redesenhar todo o lifecycle de cartridge/firmware fora do que o compiler já publica como entrypoint;
  • resolver lazy module loading ou hot reload;
  • manter indefinidamente configuração duplicada de entrypoint em FrontendSpec e na source language;
  • projetar persistência/snapshot/save-state de globals como parte desta discussão inicial.

Next Step Suggested

Converter esta agenda em uma decision do domínio compiler/pbs fechando:

  1. a forma de declare global;
  2. a superfície [INIT]/[FRAME] e a nova detecção de entrypoints;
  3. o modelo de module init sintético;
  4. o papel do init() do usuário;
  5. o published frame wrapper;
  6. o reposicionamento do FRAME_RET;
  7. a remoção da configuração explícita atual em FrontendSpec.

Depois disso, abrir um pull-request/plan que separe explicitamente:

  1. parser/AST;
  2. semântica de atributos [INIT]/[FRAME] e resolução de entrypoint;
  3. semântica e barrel/export/import;
  4. IR/backend model para globals;
  5. synthesis de init e frame wrapper;
  6. remoção das configs explícitas antigas no frontend registry/specs;
  7. testes de regressão do pipeline compilador -> IR -> bytecode.