globals and synthetic inits

This commit is contained in:
bQUARKz 2026-03-22 04:52:19 +00:00
parent b00b4e3eac
commit f49dc64fe0
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 287 additions and 0 deletions

View File

@ -0,0 +1,286 @@
# 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:
```pbs
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:
```pbs
[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.

View File

@ -12,6 +12,7 @@ Closed agendas are moved to `docs/pbs/agendas/archive`.
4. `18.3. Backend Workshop 3 - Bytecode Marshaling and Runtime Conformance.md`
5. `18.4. Asset References in Game Code - Names vs Compile-Time Lowering Agenda.md`
6. `18.5. Ignored Call Results in Executable Lowering Agenda.md`
7. `19. Globals, Synthetic Module Init, and FRAME_RET Agenda.md`
## Purpose