Compare commits
No commits in common. "c6ef56114f6082dd156dd5f1b642edc8b0f5b523" and "a77dce1c56d2ff3eb740ce6217c4a73edfaae80f" have entirely different histories.
c6ef56114f
...
a77dce1c56
@ -1,4 +1,4 @@
|
|||||||
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":45,"PLN":124,"LSN":61,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":44,"PLN":119,"LSN":60,"CLSN":1}}
|
||||||
{"type":"discussion","id":"DSC-0065","status":"open","ticket":"multi-frontend-avoid-premature-abstractions","title":"Evitar abstracoes prematuras na preparacao multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","multi-frontend","simplicity"],"agendas":[{"id":"AGD-0068","file":"AGD-0068-multi-frontend-avoid-premature-abstractions.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0065","status":"open","ticket":"multi-frontend-avoid-premature-abstractions","title":"Evitar abstracoes prematuras na preparacao multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","multi-frontend","simplicity"],"agendas":[{"id":"AGD-0068","file":"AGD-0068-multi-frontend-avoid-premature-abstractions.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0064","status":"open","ticket":"multi-frontend-architectural-tests","title":"Testes arquiteturais para fronteiras multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","tests","multi-frontend"],"agendas":[{"id":"AGD-0067","file":"AGD-0067-multi-frontend-architectural-tests.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0064","status":"open","ticket":"multi-frontend-architectural-tests","title":"Testes arquiteturais para fronteiras multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","tests","multi-frontend"],"agendas":[{"id":"AGD-0067","file":"AGD-0067-multi-frontend-architectural-tests.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0063","status":"open","ticket":"multi-frontend-synthetic-test-frontend","title":"Frontend sintetico de teste para provar neutralidade do pipeline","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","frontend","tests","backend","multi-frontend"],"agendas":[{"id":"AGD-0066","file":"AGD-0066-multi-frontend-synthetic-test-frontend.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0063","status":"open","ticket":"multi-frontend-synthetic-test-frontend","title":"Frontend sintetico de teste para provar neutralidade do pipeline","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","frontend","tests","backend","multi-frontend"],"agendas":[{"id":"AGD-0066","file":"AGD-0066-multi-frontend-synthetic-test-frontend.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
@ -6,7 +6,7 @@
|
|||||||
{"type":"discussion","id":"DSC-0061","status":"open","ticket":"multi-frontend-sdk-canonical-definition","title":"Auditoria e centralizacao gradual da definicao canonica do SDK","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","sdk","stdlib","hostcalls","intrinsics","multi-frontend"],"agendas":[{"id":"AGD-0064","file":"AGD-0064-multi-frontend-sdk-canonical-definition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0061","status":"open","ticket":"multi-frontend-sdk-canonical-definition","title":"Auditoria e centralizacao gradual da definicao canonica do SDK","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","sdk","stdlib","hostcalls","intrinsics","multi-frontend"],"agendas":[{"id":"AGD-0064","file":"AGD-0064-multi-frontend-sdk-canonical-definition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0060","status":"open","ticket":"multi-frontend-validation-boundaries","title":"Separar validacoes de linguagem e validacoes de plataforma","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","backend","validation","multi-frontend"],"agendas":[{"id":"AGD-0063","file":"AGD-0063-multi-frontend-validation-boundaries.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0060","status":"open","ticket":"multi-frontend-validation-boundaries","title":"Separar validacoes de linguagem e validacoes de plataforma","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","backend","validation","multi-frontend"],"agendas":[{"id":"AGD-0063","file":"AGD-0063-multi-frontend-validation-boundaries.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0059","status":"open","ticket":"multi-frontend-common-lifecycle","title":"Extrair lifecycle comum das responsabilidades do frontend PBS","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","lifecycle","backend","multi-frontend"],"agendas":[{"id":"AGD-0062","file":"AGD-0062-multi-frontend-common-lifecycle.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0059","status":"open","ticket":"multi-frontend-common-lifecycle","title":"Extrair lifecycle comum das responsabilidades do frontend PBS","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","lifecycle","backend","multi-frontend"],"agendas":[{"id":"AGD-0062","file":"AGD-0062-multi-frontend-common-lifecycle.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0058","status":"done","ticket":"multi-frontend-serializable-ir","title":"Manter a IR comum serializavel por design","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","ir","backend","serialization","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0060","file":"discussion/lessons/DSC-0058-multi-frontend-serializable-ir/LSN-0060-data-contract-first-for-serializable-irbackend.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
{"type":"discussion","id":"DSC-0058","status":"open","ticket":"multi-frontend-serializable-ir","title":"Manter a IR comum serializavel por design","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","ir","backend","serialization","multi-frontend"],"agendas":[{"id":"AGD-0061","file":"AGD-0061-multi-frontend-serializable-ir.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0057","status":"done","ticket":"multi-frontend-frontend-backend-contract","title":"Estabilizar contrato entre frontend e backend comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","ir","backend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0059","file":"discussion/lessons/DSC-0057-multi-frontend-frontend-backend-contract/LSN-0059-common-irbackend-handoff-and-backend-guardrails.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
{"type":"discussion","id":"DSC-0057","status":"done","ticket":"multi-frontend-frontend-backend-contract","title":"Estabilizar contrato entre frontend e backend comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","ir","backend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0059","file":"discussion/lessons/DSC-0057-multi-frontend-frontend-backend-contract/LSN-0059-common-irbackend-handoff-and-backend-guardrails.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
||||||
{"type":"discussion","id":"DSC-0056","status":"done","ticket":"multi-frontend-remove-pbs-branches","title":"Generalizar o contrato LSP/editorial para frontends","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","studio","frontend","coupling","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0058","file":"discussion/lessons/DSC-0056-multi-frontend-remove-pbs-branches/LSN-0058-generic-frontend-editorial-contract-for-lsp.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
{"type":"discussion","id":"DSC-0056","status":"done","ticket":"multi-frontend-remove-pbs-branches","title":"Generalizar o contrato LSP/editorial para frontends","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","studio","frontend","coupling","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0058","file":"discussion/lessons/DSC-0056-multi-frontend-remove-pbs-branches/LSN-0058-generic-frontend-editorial-contract-for-lsp.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
||||||
{"type":"discussion","id":"DSC-0055","status":"done","ticket":"multi-frontend-compiler-vs-language-services","title":"Separar compilacao de servicos editoriais de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","lsp","editor","frontend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0056","file":"discussion/lessons/DSC-0055-multi-frontend-compiler-vs-language-services/LSN-0056-compile-first-frontends-with-optional-editorial-capabilities.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
{"type":"discussion","id":"DSC-0055","status":"done","ticket":"multi-frontend-compiler-vs-language-services","title":"Separar compilacao de servicos editoriais de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","lsp","editor","frontend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0056","file":"discussion/lessons/DSC-0055-multi-frontend-compiler-vs-language-services/LSN-0056-compile-first-frontends-with-optional-editorial-capabilities.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
id: LSN-0060
|
|
||||||
ticket: multi-frontend-serializable-ir
|
|
||||||
title: Data contract first for serializable IRBackend
|
|
||||||
created: 2026-07-15
|
|
||||||
tags: [compiler, compiler-general, ir, backend, serialization, multi-frontend]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Data contract first for serializable IRBackend
|
|
||||||
|
|
||||||
## Original Problem
|
|
||||||
|
|
||||||
The compiler already had a common executable handoff: `IRBackend`. PBS emits it, and common backend stages consume it before lowering to `IRVM`.
|
|
||||||
|
|
||||||
The remaining risk was subtler than direct PBS coupling. A Java-only handoff can accidentally grow around process-local assumptions: callbacks, services, object identity, mutable global state, unordered collections, lazy values, or direct references between live objects. Those shapes can work inside one JVM and still make a future external frontend or out-of-process boundary expensive to add.
|
|
||||||
|
|
||||||
The work needed to keep the handoff serializable by design without choosing JSON, Protobuf, RPC, plugin loading, or a separate process too early.
|
|
||||||
|
|
||||||
## Consolidated Decision
|
|
||||||
|
|
||||||
`IRBackend` is a data contract first.
|
|
||||||
|
|
||||||
Public `IRBackend` contract types must remain modelable as an acyclic, deterministic data graph made from primitives, strings, enums, immutable values, ordered lists, explicit ids, stable symbolic keys, and source attribution values such as file ids and spans.
|
|
||||||
|
|
||||||
The public handoff must not expose callbacks, lambdas, service objects, registries, visitors, live compiler services, frontend AST/parser/token/semantic/editorial objects, mutable global state, object identity semantics, unordered output-affecting collections, cyclic public references, process-dependent lazy values, or a selected wire format.
|
|
||||||
|
|
||||||
The important boundary is the data shape, not a codec. A future serializer should be able to consume the handoff because the public model is already disciplined, but this decision does not require a serializer to exist now.
|
|
||||||
|
|
||||||
## Final Implementation
|
|
||||||
|
|
||||||
The implementation updated the compiler-general backend spec with a new `IRBackend` serializability-by-design subsection. The spec now states the allowed and forbidden public shapes and keeps wire format selection explicitly deferred.
|
|
||||||
|
|
||||||
The conformance matrix added `G20-4.2.x` rows for the new requirements:
|
|
||||||
|
|
||||||
- public `IRBackend` modelability as an acyclic deterministic data graph;
|
|
||||||
- rejection of callbacks, services, mutable or unordered collections, frontend-owned objects, process-dependent lazy values, cyclic public references, and wire-format commitments;
|
|
||||||
- use of explicit ids or stable symbolic keys for cross-object references.
|
|
||||||
|
|
||||||
The executable guardrails live in `IRBackendExecutableContractTest`:
|
|
||||||
|
|
||||||
- the public contract audit covers all current `p.studio.compiler.models` handoff types and nested value types;
|
|
||||||
- support types such as `FileId`, `ModuleId`, `CallableId`, `IntrinsicId`, `Span`, table reference records, and `ReadOnlyList` are explicitly classified;
|
|
||||||
- reflection checks inspect public fields, record components, public constructors, public methods, and generic type arguments;
|
|
||||||
- guardrails reject PBS/frontend exposure, callback shapes, service-like public shapes, mutable/unordered collection contracts, and other non-serializable public exposures;
|
|
||||||
- deterministic aggregation tests prove stable ordering for functions, synthetic functions, globals, executable functions, module pools, callable signatures, intrinsic pools, reserved metadata, and required capabilities;
|
|
||||||
- known references are locked to explicit ids or stable keys rather than direct object references.
|
|
||||||
|
|
||||||
The audit found no concrete public serialization leak that required changing the production model. That result is meaningful: the current `IRBackend` shape was already close to the target, so the correct implementation was spec clarification plus tests, not a redesign.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Good public handoff shapes:
|
|
||||||
|
|
||||||
- `ModuleId`, `CallableId`, `IntrinsicId`, and `FileId` for table-scoped references;
|
|
||||||
- `ReadOnlyList<T>` for ordered collections;
|
|
||||||
- `ModuleReference`, `CallableSignatureRef`, and `IntrinsicReference` as stable table values;
|
|
||||||
- `Span` for source attribution;
|
|
||||||
- enums and records for fixed value surfaces.
|
|
||||||
|
|
||||||
Bad public handoff shapes:
|
|
||||||
|
|
||||||
- `Map`, `Set`, or mutable collection interfaces where output order matters;
|
|
||||||
- `Runnable`, `Callable`, `Function`, visitors, or callbacks;
|
|
||||||
- compiler services or registries exposed as public fields or constructor parameters;
|
|
||||||
- frontend AST, parser, token, semantic, or editor types;
|
|
||||||
- direct references to another handoff object where an id or ordered table entry is the stable contract.
|
|
||||||
|
|
||||||
## Pitfalls
|
|
||||||
|
|
||||||
- Do not confuse "serializable by design" with "serialize it now." A codec is a future implementation detail.
|
|
||||||
- Do not add typed ids everywhere by taste. Add them when a real public field would otherwise depend on textual identity, object identity, or direct object references.
|
|
||||||
- Do not treat `ReadOnlyList` as cosmetic. Ordered collections are part of the deterministic contract.
|
|
||||||
- Do not let a private assembler or aggregator implementation shape leak into public handoff semantics.
|
|
||||||
- Do not move the rule into PBS specs. PBS owns how it populates the handoff; compiler-general owns the common handoff contract.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- `docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md`
|
|
||||||
- `docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md`
|
|
||||||
- `prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java`
|
|
||||||
- `prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendConformanceMatrixSpecTest.java`
|
|
||||||
- `prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java`
|
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
id: AGD-0061
|
||||||
|
ticket: multi-frontend-serializable-ir
|
||||||
|
title: Manter a IR comum serializavel por design
|
||||||
|
status: open
|
||||||
|
created: 2026-07-15
|
||||||
|
resolved:
|
||||||
|
decision:
|
||||||
|
tags: [compiler, compiler-general, ir, backend, serialization, multi-frontend]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agenda - Manter a IR serializavel por design
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Domain owner: `compiler/general`.
|
||||||
|
|
||||||
|
Avaliar se a IR comum pode atravessar futuramente uma fronteira de processo sem redesenho, evitando callbacks, estado global, ciclos e identity equality como parte do contrato.
|
||||||
|
|
||||||
|
## Contexto atual
|
||||||
|
|
||||||
|
O pipeline atual roda na JVM, mas frontends futuros podem ser externos. O alinhamento proibe escolher JSON, Protobuf, RPC ou processo externo agora; a discussao deve focar no formato dos dados.
|
||||||
|
|
||||||
|
## Escopo
|
||||||
|
|
||||||
|
- Auditar records/classes da IR comum.
|
||||||
|
- Identificar referencias a servicos, objetos mutaveis, ciclos e IDs implicitos.
|
||||||
|
- Definir principios de modelagem: IDs explicitos, listas ordenadas, enums e valores primitivos.
|
||||||
|
|
||||||
|
## Fora de escopo
|
||||||
|
|
||||||
|
- Implementar codec.
|
||||||
|
- Serializar a saida do PBS dentro do mesmo processo.
|
||||||
|
- Escolher wire format.
|
||||||
|
|
||||||
|
## Arquivos e componentes a inspecionar
|
||||||
|
|
||||||
|
- `prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/models/...`
|
||||||
|
- `prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/...`
|
||||||
|
- testes `IRBackendExecutableContractTest` e `LowerToIRVMServiceTest`
|
||||||
|
- docs de specs do compiler geral
|
||||||
|
|
||||||
|
## Alteracoes propostas
|
||||||
|
|
||||||
|
Opcao A: documentar invariantes de serializabilidade e corrigir apenas vazamentos claros.
|
||||||
|
|
||||||
|
Opcao B: criar tipos de ID e spans onde hoje houver identidade textual ou referencias diretas instaveis.
|
||||||
|
|
||||||
|
Recomendacao inicial: comecar por auditoria e testes de reflexao leves antes de qualquer migracao estrutural.
|
||||||
|
|
||||||
|
## Estrategia de implementacao
|
||||||
|
|
||||||
|
Gerar uma lista de tipos publicos da IR, revisar construtores e campos, e propor correcoes incrementais para pontos que impediriam um codec futuro.
|
||||||
|
|
||||||
|
## Testes necessarios
|
||||||
|
|
||||||
|
- Teste de reflexao sobre campos publicos/record components da IR.
|
||||||
|
- Teste de determinismo de ordenacao.
|
||||||
|
- Teste de ausencia de referencias a AST, servicos ou callbacks.
|
||||||
|
|
||||||
|
## Criterios de aceitacao
|
||||||
|
|
||||||
|
- A IR pode ser descrita como grafo de dados aciclico ou com referencias por ID.
|
||||||
|
- Nenhuma escolha prematura de wire format e introduzida.
|
||||||
|
|
||||||
|
## Riscos
|
||||||
|
|
||||||
|
- Overengineering de IDs onde o contrato ainda nao exige.
|
||||||
|
- Quebrar ergonomia de testes ao tornar todos os objetos verbosos.
|
||||||
|
|
||||||
|
## Decisoes que devem ser registradas
|
||||||
|
|
||||||
|
- Regras minimas de serializabilidade.
|
||||||
|
- Tipos de ID que devem existir agora.
|
||||||
|
- Itens explicitamente adiados para codec futuro.
|
||||||
|
|
||||||
@ -78,37 +78,6 @@ The common backend contract MUST NOT expose or require:
|
|||||||
4. PBS editorial or language-service objects,
|
4. PBS editorial or language-service objects,
|
||||||
5. or any type under a language frontend package as part of the public backend handoff.
|
5. or any type under a language frontend package as part of the public backend handoff.
|
||||||
|
|
||||||
## 4.2 Serializable-by-Design Contract Shape
|
|
||||||
|
|
||||||
The public `IRBackend` handoff MUST remain serializable by design. This requirement does not select a wire format, schema language, RPC mechanism, process boundary, plugin runtime, or external frontend transport. It only constrains the public data shape so those choices remain possible later without redesigning the handoff.
|
|
||||||
|
|
||||||
Public `IRBackend` contract types MUST be modelable as an acyclic, deterministic data graph made of:
|
|
||||||
|
|
||||||
1. primitive values and strings,
|
|
||||||
2. enums,
|
|
||||||
3. immutable value objects,
|
|
||||||
4. ordered lists,
|
|
||||||
5. nullable or optional scalar fields only where absence is part of the contract,
|
|
||||||
6. explicit identifier values for cross-references,
|
|
||||||
7. and source attribution values such as file ids and spans.
|
|
||||||
|
|
||||||
The public handoff MUST NOT expose, require, or rely on:
|
|
||||||
|
|
||||||
1. callbacks, lambdas, service objects, registries, visitors, or live compiler services,
|
|
||||||
2. frontend AST, parser, token, semantic, or editorial objects,
|
|
||||||
3. mutable global state as part of interpretation,
|
|
||||||
4. object identity equality as a semantic key,
|
|
||||||
5. unordered collections where iteration order affects output,
|
|
||||||
6. cyclic references between public handoff objects,
|
|
||||||
7. lazy values whose result depends on current process state,
|
|
||||||
8. or a selected wire format.
|
|
||||||
|
|
||||||
Cross-object references MUST use explicit ids or stable symbolic keys. Existing table-scoped identifiers such as `FileId`, `ModuleId`, `CallableId`, and `IntrinsicId` are valid handoff values when their table scope is explicit and deterministic. New typed ids SHOULD be introduced only when a concrete public handoff field would otherwise depend on textual identity, object identity, or a direct object reference.
|
|
||||||
|
|
||||||
Ordered collections MUST preserve deterministic order. Any map-like data that becomes part of the handoff MUST either be represented as an ordered list of entries or define deterministic key ordering before it reaches the public contract.
|
|
||||||
|
|
||||||
Initial enforcement for this contract SHOULD be audit and guardrail based: specs, reflection tests over public handoff surfaces, deterministic ordering tests, and targeted fixes for concrete leaks. A serializer or schema implementation is not required for conformance to this subsection.
|
|
||||||
|
|
||||||
## 5. Backend Entry Preconditions
|
## 5. Backend Entry Preconditions
|
||||||
|
|
||||||
Lowering from `IRBackend` to `IRVM` may start only when:
|
Lowering from `IRBackend` to `IRVM` may start only when:
|
||||||
|
|||||||
@ -43,9 +43,6 @@ to concrete positive/negative test evidence and current status.
|
|||||||
| G20-4.1.1 | `IRBackend` MUST be the common frontend-to-backend executable handoff. | `LowerToIRVMServiceTest#lowerMustAcceptManuallyConstructedCommonIRBackend` | N/A | pass | Direct backend lowering from manually constructed common IRBackend proves PBS parser/frontend services are not required. |
|
| G20-4.1.1 | `IRBackend` MUST be the common frontend-to-backend executable handoff. | `LowerToIRVMServiceTest#lowerMustAcceptManuallyConstructedCommonIRBackend` | N/A | pass | Direct backend lowering from manually constructed common IRBackend proves PBS parser/frontend services are not required. |
|
||||||
| G20-4.1.2 | Common backend code MUST NOT depend on `p.studio.compiler.pbs`. | `CommonBackendArchitectureTest#commonBackendMustNotImportPbsFrontendPackages` | N/A | pass | Source-level architectural guard rejects PBS frontend imports in common backend packages. |
|
| G20-4.1.2 | Common backend code MUST NOT depend on `p.studio.compiler.pbs`. | `CommonBackendArchitectureTest#commonBackendMustNotImportPbsFrontendPackages` | N/A | pass | Source-level architectural guard rejects PBS frontend imports in common backend packages. |
|
||||||
| G20-4.1.3 | Public `IRBackend` contract MUST NOT expose PBS AST, token, parser, semantic, or editorial types. | `IRBackendExecutableContractTest#publicIRBackendContractMustNotExposePbsTypes` | N/A | pass | Reflection guard covers public contract fields, constructors, methods, and record components. |
|
| G20-4.1.3 | Public `IRBackend` contract MUST NOT expose PBS AST, token, parser, semantic, or editorial types. | `IRBackendExecutableContractTest#publicIRBackendContractMustNotExposePbsTypes` | N/A | pass | Reflection guard covers public contract fields, constructors, methods, and record components. |
|
||||||
| G20-4.2.1 | Public `IRBackend` contract types MUST remain modelable as an acyclic, deterministic data graph. | `IRBackendExecutableContractTest#publicIRBackendContractMustExposeOnlySerializableShapes`; `IRBackendExecutableContractTest#aggregatorMustEmitDeterministicOrderedPublicContractShape` | N/A | pass | Covers public shape guardrails, generic type arguments, ordered aggregate emission, table remapping, and capability de-duplication order. |
|
|
||||||
| G20-4.2.2 | Public `IRBackend` contract types MUST NOT expose callbacks, services, mutable/unordered collection contracts, frontend-owned objects, process-dependent lazy values, cyclic public references, or wire-format commitments. | `IRBackendExecutableContractTest#publicIRBackendContractMustExposeOnlySerializableShapes` | N/A | pass | Reflection guard covers public contract fields, constructors, methods, record components, and generic type arguments. |
|
|
||||||
| G20-4.2.3 | Public `IRBackend` cross-object references MUST use explicit ids or stable symbolic keys. | `IRBackendExecutableContractTest#publicIRBackendContractAuditMustClassifyAllowedSupportTypes`; `IRBackendExecutableContractTest#publicIRBackendContractMustUseExplicitIdsOrStableKeysForKnownReferences` | N/A | pass | Current audit found no concrete public serialization leak requiring model correction. Existing `FileId`, `ModuleId`, `CallableId`, and `IntrinsicId` remain explicit table-scoped identifiers. |
|
|
||||||
| G20-6.2 | `IRVM_EXT` MUST declare structural metadata (`pops/pushes/is_branch/is_terminator`). | `IRVMValidatorTest#validateMustApplyStructuralMetadataForCustomInternalExtension`; `IRVMValidatorTest#validateMustRejectCustomInternalExtensionWhenStructuralMetadataUnderflowsStack`; `IRVMOp` record contract (`pops/pushes/branch/terminator/internal`) | N/A | pass | Dedicated extension fixtures now assert structural metadata is consumed by validation behavior. |
|
| G20-6.2 | `IRVM_EXT` MUST declare structural metadata (`pops/pushes/is_branch/is_terminator`). | `IRVMValidatorTest#validateMustApplyStructuralMetadataForCustomInternalExtension`; `IRVMValidatorTest#validateMustRejectCustomInternalExtensionWhenStructuralMetadataUnderflowsStack`; `IRVMOp` record contract (`pops/pushes/branch/terminator/internal`) | N/A | pass | Dedicated extension fixtures now assert structural metadata is consumed by validation behavior. |
|
||||||
| G20-6.3 | `IRVM_EXT` MUST be eliminable before bytecode emission. | `OptimizeIRVMServiceTest#optimizeDefaultPassesMustEliminateUnreachableInternalExtensionBeforeEmission` | `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemain`; `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemainEvenWithNonEmptyEmissionPlan`; `IRVMValidatorTest#validateMustRejectInternalOpcodeWhenConfigured` | pass | Optimizer elimination path and emit-stage hard rejection path are both covered. |
|
| G20-6.3 | `IRVM_EXT` MUST be eliminable before bytecode emission. | `OptimizeIRVMServiceTest#optimizeDefaultPassesMustEliminateUnreachableInternalExtensionBeforeEmission` | `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemain`; `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemainEvenWithNonEmptyEmissionPlan`; `IRVMValidatorTest#validateMustRejectInternalOpcodeWhenConfigured` | pass | Optimizer elimination path and emit-stage hard rejection path are both covered. |
|
||||||
| G20-6.4 | IRVM MUST preserve per-function slot and identity headers. | `IRVMProgramTest#constructorMustRejectModuleAndEmissionPlanMismatch` | `IRVMProgramTest#constructorMustRejectModuleAndEmissionPlanMismatch` | pass | Header mismatch is rejected deterministically. |
|
| G20-6.4 | IRVM MUST preserve per-function slot and identity headers. | `IRVMProgramTest#constructorMustRejectModuleAndEmissionPlanMismatch` | `IRVMProgramTest#constructorMustRejectModuleAndEmissionPlanMismatch` | pass | Header mismatch is rejected deterministically. |
|
||||||
|
|||||||
@ -39,9 +39,6 @@ class BackendConformanceMatrixSpecTest {
|
|||||||
"G20-4.1.1",
|
"G20-4.1.1",
|
||||||
"G20-4.1.2",
|
"G20-4.1.2",
|
||||||
"G20-4.1.3",
|
"G20-4.1.3",
|
||||||
"G20-4.2.1",
|
|
||||||
"G20-4.2.2",
|
|
||||||
"G20-4.2.3",
|
|
||||||
"G20-6.2",
|
"G20-6.2",
|
||||||
"G20-6.3",
|
"G20-6.3",
|
||||||
"G20-6.4",
|
"G20-6.4",
|
||||||
|
|||||||
@ -11,35 +11,15 @@ import p.studio.compiler.source.tables.IntrinsicReference;
|
|||||||
import p.studio.compiler.source.tables.ModuleReference;
|
import p.studio.compiler.source.tables.ModuleReference;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class IRBackendExecutableContractTest {
|
class IRBackendExecutableContractTest {
|
||||||
private static final String FORBIDDEN_PBS_PACKAGE = "p.studio.compiler.pbs";
|
private static final String FORBIDDEN_PBS_PACKAGE = "p.studio.compiler.pbs";
|
||||||
private static final Set<Class<?>> AUDITED_SUPPORT_TYPES = Set.of(
|
|
||||||
FileId.class,
|
|
||||||
ModuleId.class,
|
|
||||||
CallableId.class,
|
|
||||||
IntrinsicId.class,
|
|
||||||
Span.class,
|
|
||||||
ModuleReference.class,
|
|
||||||
CallableSignatureRef.class,
|
|
||||||
IntrinsicReference.class,
|
|
||||||
ReadOnlyList.class,
|
|
||||||
String.class,
|
|
||||||
Integer.class,
|
|
||||||
Long.class,
|
|
||||||
Boolean.class);
|
|
||||||
private static final List<Class<?>> PUBLIC_CONTRACT_TYPES = List.of(
|
private static final List<Class<?>> PUBLIC_CONTRACT_TYPES = List.of(
|
||||||
IRBackend.class,
|
IRBackend.class,
|
||||||
IRBackend.IRBackendAggregator.class,
|
IRBackend.IRBackendAggregator.class,
|
||||||
@ -65,83 +45,6 @@ class IRBackendExecutableContractTest {
|
|||||||
IRReservedMetadata.IntrinsicSurface.class,
|
IRReservedMetadata.IntrinsicSurface.class,
|
||||||
IRReservedMetadata.BuiltinConstSurface.class);
|
IRReservedMetadata.BuiltinConstSurface.class);
|
||||||
|
|
||||||
@Test
|
|
||||||
void publicIRBackendContractAuditMustCoverAllModelTypes() {
|
|
||||||
final var expectedTopLevelModelTypes = Set.of(
|
|
||||||
IRBackend.class,
|
|
||||||
IRBackendReader.class,
|
|
||||||
IRBackendFile.class,
|
|
||||||
IRBackendExecutableFunction.class,
|
|
||||||
IRFunction.class,
|
|
||||||
IRGlobal.class,
|
|
||||||
IRGlobalOrigin.class,
|
|
||||||
IRGlobalVisibility.class,
|
|
||||||
IRHiddenGlobalKind.class,
|
|
||||||
IRReservedMetadata.class,
|
|
||||||
IRSyntheticCallableKind.class,
|
|
||||||
IRSyntheticFunction.class,
|
|
||||||
IRSyntheticOrigin.class);
|
|
||||||
|
|
||||||
assertTrue(
|
|
||||||
PUBLIC_CONTRACT_TYPES.containsAll(expectedTopLevelModelTypes),
|
|
||||||
"public IRBackend contract audit must cover every top-level handoff model type");
|
|
||||||
assertTrue(
|
|
||||||
PUBLIC_CONTRACT_TYPES.contains(IRBackendExecutableFunction.Instruction.class)
|
|
||||||
&& PUBLIC_CONTRACT_TYPES.contains(IRBackendExecutableFunction.HostCallMetadata.class)
|
|
||||||
&& PUBLIC_CONTRACT_TYPES.contains(IRBackendExecutableFunction.IntrinsicCallMetadata.class)
|
|
||||||
&& PUBLIC_CONTRACT_TYPES.contains(IRReservedMetadata.HostMethodBinding.class)
|
|
||||||
&& PUBLIC_CONTRACT_TYPES.contains(IRReservedMetadata.BuiltinTypeSurface.class)
|
|
||||||
&& PUBLIC_CONTRACT_TYPES.contains(IRReservedMetadata.BuiltinFieldSurface.class)
|
|
||||||
&& PUBLIC_CONTRACT_TYPES.contains(IRReservedMetadata.IntrinsicSurface.class)
|
|
||||||
&& PUBLIC_CONTRACT_TYPES.contains(IRReservedMetadata.BuiltinConstSurface.class),
|
|
||||||
"public IRBackend contract audit must cover nested handoff value types");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void publicIRBackendContractAuditMustClassifyAllowedSupportTypes() {
|
|
||||||
assertTrue(
|
|
||||||
AUDITED_SUPPORT_TYPES.containsAll(Set.of(
|
|
||||||
FileId.class,
|
|
||||||
ModuleId.class,
|
|
||||||
CallableId.class,
|
|
||||||
IntrinsicId.class)),
|
|
||||||
"table-scoped ids are audited as explicit handoff identifiers");
|
|
||||||
assertTrue(
|
|
||||||
AUDITED_SUPPORT_TYPES.contains(Span.class),
|
|
||||||
"Span is audited as source attribution data");
|
|
||||||
assertTrue(
|
|
||||||
AUDITED_SUPPORT_TYPES.containsAll(Set.of(
|
|
||||||
ModuleReference.class,
|
|
||||||
CallableSignatureRef.class,
|
|
||||||
IntrinsicReference.class)),
|
|
||||||
"source table references are audited as ordered table values");
|
|
||||||
assertTrue(
|
|
||||||
AUDITED_SUPPORT_TYPES.contains(ReadOnlyList.class),
|
|
||||||
"ReadOnlyList is audited as the ordered collection contract");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void publicIRBackendContractMustUseExplicitIdsOrStableKeysForKnownReferences() {
|
|
||||||
assertEquals(ModuleId.class, recordComponentType(IRBackendExecutableFunction.class, "moduleId"));
|
|
||||||
assertEquals(CallableId.class, recordComponentType(IRBackendExecutableFunction.class, "callableId"));
|
|
||||||
assertEquals(ModuleId.class, recordComponentType(IRBackendExecutableFunction.Instruction.class, "calleeModuleId"));
|
|
||||||
assertEquals(CallableId.class, recordComponentType(IRBackendExecutableFunction.Instruction.class, "calleeCallableId"));
|
|
||||||
assertEquals(IntrinsicId.class, recordComponentType(IRBackendExecutableFunction.IntrinsicCallMetadata.class, "intrinsicId"));
|
|
||||||
|
|
||||||
assertEquals(FileId.class, recordComponentType(IRBackendFile.class, "fileId"));
|
|
||||||
assertEquals(FileId.class, recordComponentType(IRFunction.class, "fileId"));
|
|
||||||
assertEquals(FileId.class, recordComponentType(IRGlobal.class, "fileId"));
|
|
||||||
assertEquals(ModuleId.class, recordComponentType(IRGlobal.class, "moduleId"));
|
|
||||||
assertEquals(FileId.class, recordComponentType(IRGlobalOrigin.class, "derivedFromFile"));
|
|
||||||
assertEquals(ModuleId.class, recordComponentType(IRGlobalOrigin.class, "derivedFromModule"));
|
|
||||||
assertEquals(FileId.class, recordComponentType(IRSyntheticOrigin.class, "fileId"));
|
|
||||||
assertEquals(ModuleId.class, recordComponentType(IRSyntheticFunction.class, "moduleId"));
|
|
||||||
|
|
||||||
assertEquals(String.class, recordComponentType(ModuleReference.class, "project"));
|
|
||||||
assertEquals(String.class, recordComponentType(CallableSignatureRef.class, "callableName"));
|
|
||||||
assertEquals(String.class, recordComponentType(IntrinsicReference.class, "canonicalName"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void publicIRBackendContractMustNotExposePbsTypes() {
|
void publicIRBackendContractMustNotExposePbsTypes() {
|
||||||
final var violations = new ArrayList<String>();
|
final var violations = new ArrayList<String>();
|
||||||
@ -178,57 +81,6 @@ class IRBackendExecutableContractTest {
|
|||||||
"public IRBackend contract must not expose PBS frontend types: " + violations);
|
"public IRBackend contract must not expose PBS frontend types: " + violations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void publicIRBackendContractMustExposeOnlySerializableShapes() {
|
|
||||||
final var violations = new ArrayList<String>();
|
|
||||||
|
|
||||||
for (final var contractType : PUBLIC_CONTRACT_TYPES) {
|
|
||||||
inspectSerializableContractType(contractType.getName(), contractType, violations);
|
|
||||||
for (final var field : contractType.getFields()) {
|
|
||||||
inspectSerializableContractType(
|
|
||||||
contractType.getName() + "#" + field.getName(),
|
|
||||||
field.getGenericType(),
|
|
||||||
violations);
|
|
||||||
}
|
|
||||||
for (final var constructor : contractType.getConstructors()) {
|
|
||||||
for (final var parameterType : constructor.getGenericParameterTypes()) {
|
|
||||||
inspectSerializableContractType(
|
|
||||||
contractType.getName() + "#<init>",
|
|
||||||
parameterType,
|
|
||||||
violations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (final var method : contractType.getMethods()) {
|
|
||||||
if (method.getDeclaringClass().equals(Object.class)
|
|
||||||
|| !Modifier.isPublic(method.getModifiers())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
inspectSerializableContractType(
|
|
||||||
contractType.getName() + "#" + method.getName(),
|
|
||||||
method.getGenericReturnType(),
|
|
||||||
violations);
|
|
||||||
for (final var parameterType : method.getGenericParameterTypes()) {
|
|
||||||
inspectSerializableContractType(
|
|
||||||
contractType.getName() + "#" + method.getName(),
|
|
||||||
parameterType,
|
|
||||||
violations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (contractType.isRecord()) {
|
|
||||||
for (final var component : contractType.getRecordComponents()) {
|
|
||||||
inspectSerializableContractType(
|
|
||||||
contractType.getName() + "#" + component.getName(),
|
|
||||||
component.getGenericType(),
|
|
||||||
violations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(
|
|
||||||
violations.isEmpty(),
|
|
||||||
"public IRBackend contract must expose only serializable-by-design shapes: " + violations);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void callInstructionMustRequireCategorySpecificMetadata() {
|
void callInstructionMustRequireCategorySpecificMetadata() {
|
||||||
assertThrows(IllegalArgumentException.class, () -> new IRBackendExecutableFunction.Instruction(
|
assertThrows(IllegalArgumentException.class, () -> new IRBackendExecutableFunction.Instruction(
|
||||||
@ -581,208 +433,6 @@ class IRBackendExecutableContractTest {
|
|||||||
assertEquals(1, backend.getCallableSignatures().get(1).moduleId().getIndex());
|
assertEquals(1, backend.getCallableSignatures().get(1).moduleId().getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void aggregatorMustEmitDeterministicOrderedPublicContractShape() {
|
|
||||||
final var first = emitDeterministicAggregateFixture();
|
|
||||||
final var second = emitDeterministicAggregateFixture();
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
List.of("alpha", "beta"),
|
|
||||||
first.getFunctions().asList().stream().map(IRFunction::name).toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("frameWrapper", "initWrapper"),
|
|
||||||
first.getSyntheticFunctions().asList().stream().map(IRSyntheticFunction::callableName).toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("state", "timer"),
|
|
||||||
first.getGlobals().asList().stream().map(IRGlobal::name).toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("frame", "init"),
|
|
||||||
first.getExecutableFunctions().asList().stream().map(IRBackendExecutableFunction::callableName).toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("app:alpha", "app:beta"),
|
|
||||||
first.getModulePool().asList().stream()
|
|
||||||
.map(module -> module.project() + ":" + String.join(".", module.pathSegments().asList()))
|
|
||||||
.toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("frame", "helper", "init"),
|
|
||||||
first.getCallableSignatures().asList().stream().map(CallableSignatureRef::callableName).toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("core.draw", "core.color.pack"),
|
|
||||||
first.getIntrinsicPool().asList().stream().map(IntrinsicReference::canonicalName).toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("draw", "play"),
|
|
||||||
first.getReservedMetadata().hostMethodBindings().asList().stream()
|
|
||||||
.map(IRReservedMetadata.HostMethodBinding::abiMethod)
|
|
||||||
.toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("Sprite", "Sound"),
|
|
||||||
first.getReservedMetadata().builtinTypeSurfaces().asList().stream()
|
|
||||||
.map(IRReservedMetadata.BuiltinTypeSurface::sourceTypeName)
|
|
||||||
.toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("WHITE", "BLACK"),
|
|
||||||
first.getReservedMetadata().builtinConstSurfaces().asList().stream()
|
|
||||||
.map(IRReservedMetadata.BuiltinConstSurface::sourceConstName)
|
|
||||||
.toList());
|
|
||||||
assertEquals(
|
|
||||||
List.of("gfx.draw", "audio.play", "input.read"),
|
|
||||||
first.getReservedMetadata().requiredCapabilities().asList());
|
|
||||||
|
|
||||||
assertEquals(first.toString(), second.toString());
|
|
||||||
assertEquals(
|
|
||||||
first.getReservedMetadata().requiredCapabilities().asList(),
|
|
||||||
second.getReservedMetadata().requiredCapabilities().asList());
|
|
||||||
assertEquals(
|
|
||||||
first.getExecutableFunctions().get(0).moduleId(),
|
|
||||||
second.getExecutableFunctions().get(0).moduleId());
|
|
||||||
assertEquals(
|
|
||||||
first.getExecutableFunctions().get(1).callableId(),
|
|
||||||
second.getExecutableFunctions().get(1).callableId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IRBackend emitDeterministicAggregateFixture() {
|
|
||||||
final var aggregator = IRBackend.aggregator();
|
|
||||||
aggregator.merge(new IRBackendFile(
|
|
||||||
new FileId(10),
|
|
||||||
ReadOnlyList.from(new IRFunction(new FileId(10), "alpha", 0, false, new Span(new FileId(10), 0, 5))),
|
|
||||||
ReadOnlyList.from(new IRSyntheticFunction(
|
|
||||||
new ModuleId(0),
|
|
||||||
"frameWrapper",
|
|
||||||
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
|
||||||
new IRSyntheticOrigin(new FileId(10), "frame", new Span(new FileId(10), 0, 5)))),
|
|
||||||
ReadOnlyList.from(new IRGlobal(
|
|
||||||
new FileId(10),
|
|
||||||
new ModuleId(0),
|
|
||||||
"state",
|
|
||||||
"int",
|
|
||||||
new Span(new FileId(10), 1, 2))),
|
|
||||||
ReadOnlyList.from(new IRBackendExecutableFunction(
|
|
||||||
new FileId(10),
|
|
||||||
new ModuleId(0),
|
|
||||||
"frame",
|
|
||||||
new CallableId(0),
|
|
||||||
0,
|
|
||||||
10,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
|
|
||||||
new ModuleId(0),
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
new IRBackendExecutableFunction.IntrinsicCallMetadata("core.draw", 1, new IntrinsicId(0)),
|
|
||||||
Span.none())),
|
|
||||||
Span.none())),
|
|
||||||
new IRReservedMetadata(
|
|
||||||
ReadOnlyList.from(new IRReservedMetadata.HostMethodBinding(
|
|
||||||
"Graphics",
|
|
||||||
"draw",
|
|
||||||
"gfx",
|
|
||||||
"draw",
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
"gfx.draw",
|
|
||||||
Span.none())),
|
|
||||||
ReadOnlyList.from(new IRReservedMetadata.BuiltinTypeSurface(
|
|
||||||
"Sprite",
|
|
||||||
"core.Sprite",
|
|
||||||
1,
|
|
||||||
ReadOnlyList.from(new IRReservedMetadata.BuiltinFieldSurface(
|
|
||||||
"x",
|
|
||||||
"int",
|
|
||||||
0,
|
|
||||||
Span.none())),
|
|
||||||
ReadOnlyList.empty(),
|
|
||||||
Span.none())),
|
|
||||||
ReadOnlyList.from(new IRReservedMetadata.BuiltinConstSurface(
|
|
||||||
"WHITE",
|
|
||||||
"color",
|
|
||||||
"core.color.white",
|
|
||||||
1,
|
|
||||||
Span.none())),
|
|
||||||
ReadOnlyList.from("gfx.draw", "audio.play")),
|
|
||||||
ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("alpha"))),
|
|
||||||
ReadOnlyList.from(
|
|
||||||
new CallableSignatureRef(new ModuleId(0), "frame", 0, "() -> unit"),
|
|
||||||
new CallableSignatureRef(new ModuleId(0), "helper", 0, "() -> unit")),
|
|
||||||
ReadOnlyList.from(new IntrinsicReference("core.draw", 1))));
|
|
||||||
aggregator.merge(new IRBackendFile(
|
|
||||||
new FileId(20),
|
|
||||||
ReadOnlyList.from(new IRFunction(new FileId(20), "beta", 1, true, new Span(new FileId(20), 0, 7))),
|
|
||||||
ReadOnlyList.from(new IRSyntheticFunction(
|
|
||||||
new ModuleId(0),
|
|
||||||
"initWrapper",
|
|
||||||
IRSyntheticCallableKind.PROJECT_INIT,
|
|
||||||
new IRSyntheticOrigin(new FileId(20), "init", new Span(new FileId(20), 0, 7)))),
|
|
||||||
ReadOnlyList.from(new IRGlobal(
|
|
||||||
new FileId(20),
|
|
||||||
new ModuleId(0),
|
|
||||||
"timer",
|
|
||||||
"int",
|
|
||||||
new Span(new FileId(20), 1, 3))),
|
|
||||||
ReadOnlyList.from(new IRBackendExecutableFunction(
|
|
||||||
new FileId(20),
|
|
||||||
new ModuleId(0),
|
|
||||||
"init",
|
|
||||||
new CallableId(0),
|
|
||||||
0,
|
|
||||||
8,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
|
|
||||||
new ModuleId(0),
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, new IntrinsicId(0)),
|
|
||||||
Span.none())),
|
|
||||||
Span.none())),
|
|
||||||
new IRReservedMetadata(
|
|
||||||
ReadOnlyList.from(new IRReservedMetadata.HostMethodBinding(
|
|
||||||
"Audio",
|
|
||||||
"play",
|
|
||||||
"audio",
|
|
||||||
"play",
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
"audio.play",
|
|
||||||
Span.none())),
|
|
||||||
ReadOnlyList.from(new IRReservedMetadata.BuiltinTypeSurface(
|
|
||||||
"Sound",
|
|
||||||
"core.Sound",
|
|
||||||
1,
|
|
||||||
ReadOnlyList.empty(),
|
|
||||||
ReadOnlyList.from(new IRReservedMetadata.IntrinsicSurface(
|
|
||||||
"volume",
|
|
||||||
"core.sound.volume",
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
Span.none())),
|
|
||||||
Span.none())),
|
|
||||||
ReadOnlyList.from(new IRReservedMetadata.BuiltinConstSurface(
|
|
||||||
"BLACK",
|
|
||||||
"color",
|
|
||||||
"core.color.black",
|
|
||||||
1,
|
|
||||||
Span.none())),
|
|
||||||
ReadOnlyList.from("gfx.draw", "input.read")),
|
|
||||||
ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("beta"))),
|
|
||||||
ReadOnlyList.from(new CallableSignatureRef(new ModuleId(0), "init", 0, "() -> unit")),
|
|
||||||
ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1))));
|
|
||||||
return aggregator.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void inspectType(
|
private static void inspectType(
|
||||||
final String owner,
|
final String owner,
|
||||||
final Type type,
|
final Type type,
|
||||||
@ -795,81 +445,4 @@ class IRBackendExecutableContractTest {
|
|||||||
violations.add(owner + " -> " + typeName);
|
violations.add(owner + " -> " + typeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void inspectSerializableContractType(
|
|
||||||
final String owner,
|
|
||||||
final Type type,
|
|
||||||
final ArrayList<String> violations) {
|
|
||||||
if (type == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type instanceof ParameterizedType parameterizedType) {
|
|
||||||
inspectSerializableContractType(owner, parameterizedType.getRawType(), violations);
|
|
||||||
for (final var argument : parameterizedType.getActualTypeArguments()) {
|
|
||||||
inspectSerializableContractType(owner, argument, violations);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!(type instanceof Class<?> clazz)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (clazz.isPrimitive()
|
|
||||||
|| clazz.isEnum()
|
|
||||||
|| AUDITED_SUPPORT_TYPES.contains(clazz)
|
|
||||||
|| PUBLIC_CONTRACT_TYPES.contains(clazz)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isForbiddenFrontendOwnedType(clazz)) {
|
|
||||||
violations.add(owner + " exposes frontend-owned type " + clazz.getName());
|
|
||||||
}
|
|
||||||
if (isForbiddenCallbackShape(clazz)) {
|
|
||||||
violations.add(owner + " exposes callback shape " + clazz.getName());
|
|
||||||
}
|
|
||||||
if (isForbiddenMutableOrUnorderedCollection(clazz)) {
|
|
||||||
violations.add(owner + " exposes mutable or unordered collection " + clazz.getName());
|
|
||||||
}
|
|
||||||
if (isForbiddenServiceShape(clazz)) {
|
|
||||||
violations.add(owner + " exposes service-like shape " + clazz.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isForbiddenFrontendOwnedType(final Class<?> clazz) {
|
|
||||||
final var packageName = clazz.getPackageName();
|
|
||||||
return packageName.startsWith("p.studio.compiler.pbs")
|
|
||||||
|| packageName.contains(".parser")
|
|
||||||
|| packageName.contains(".semantic")
|
|
||||||
|| packageName.contains(".editor");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isForbiddenCallbackShape(final Class<?> clazz) {
|
|
||||||
return clazz.equals(Runnable.class)
|
|
||||||
|| Callable.class.isAssignableFrom(clazz)
|
|
||||||
|| Function.class.isAssignableFrom(clazz)
|
|
||||||
|| clazz.getPackageName().startsWith("java.util.function");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isForbiddenMutableOrUnorderedCollection(final Class<?> clazz) {
|
|
||||||
return !clazz.equals(ReadOnlyList.class)
|
|
||||||
&& (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isForbiddenServiceShape(final Class<?> clazz) {
|
|
||||||
final var simpleName = clazz.getSimpleName();
|
|
||||||
return simpleName.endsWith("Service")
|
|
||||||
|| simpleName.endsWith("Registry")
|
|
||||||
|| simpleName.endsWith("Visitor")
|
|
||||||
|| simpleName.endsWith("Provider");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?> recordComponentType(
|
|
||||||
final Class<?> recordType,
|
|
||||||
final String componentName) {
|
|
||||||
for (final var component : recordType.getRecordComponents()) {
|
|
||||||
if (component.getName().equals(componentName)) {
|
|
||||||
return component.getType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fail("missing record component " + recordType.getName() + "#" + componentName);
|
|
||||||
throw new IllegalStateException("unreachable");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user