implements PLN-0125 #19
@ -1,11 +1,11 @@
|
|||||||
{"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":47,"PLN":126,"LSN":63,"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":[]}
|
||||||
{"type":"discussion","id":"DSC-0062","status":"open","ticket":"multi-frontend-pvm-neutrality","title":"Neutralidade da PVM e identificacao PBX independente de PBS","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["vm-arch","runtime","pvm","pbx","compiler","multi-frontend"],"agendas":[{"id":"AGD-0065","file":"AGD-0065-multi-frontend-pvm-neutrality.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0062","status":"done","ticket":"multi-frontend-pvm-neutrality","title":"Neutralidade da PVM e identificacao PBX independente de PBS","created_at":"2026-07-15","updated_at":"2026-09-18","tags":["vm-arch","runtime","pvm","pbx","compiler","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0061","file":"discussion/lessons/DSC-0062-multi-frontend-pvm-neutrality/LSN-0061-pbx-runtime-boundary.md","status":"done","created_at":"2026-09-18","updated_at":"2026-09-18"}]}
|
||||||
{"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":"done","ticket":"multi-frontend-common-lifecycle","title":"Extrair lifecycle comum das responsabilidades do frontend PBS","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","compiler-pbs","lifecycle","backend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0062","file":"discussion/lessons/DSC-0059-multi-frontend-common-lifecycle/LSN-0062-common-lifecycle-assembly-stage.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}
|
||||||
{"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":"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-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"}]}
|
||||||
|
|||||||
@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
id: LSN-0062
|
||||||
|
ticket: multi-frontend-common-lifecycle
|
||||||
|
title: Common lifecycle assembly after frontend declaration
|
||||||
|
created: 2026-09-19
|
||||||
|
tags: [compiler, compiler-general, compiler-pbs, lifecycle, backend, multi-frontend]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Common lifecycle assembly after frontend declaration
|
||||||
|
|
||||||
|
## Original Problem
|
||||||
|
|
||||||
|
`IRBackend` was already the common executable handoff, but the PBS frontend still
|
||||||
|
owned two different jobs. It admitted language markers `[Init]`, `[Frame]`, and
|
||||||
|
`InitAllowed`, and it also assembled the platform lifecycle: module init,
|
||||||
|
project-init wrapper, published frame wrapper, physical entrypoint, and boot
|
||||||
|
guard.
|
||||||
|
|
||||||
|
That second job discovered roles through `__pbs.*` names and lived inside
|
||||||
|
`PBSFrontendPhaseService`. Another frontend could not reuse the boot sequence
|
||||||
|
without copying PBS. Common backend code also risked treating PBS naming as the
|
||||||
|
integration contract.
|
||||||
|
|
||||||
|
The old path had a concrete defect. The `[Init]` colocated with `[Frame]` is
|
||||||
|
project init, but it was also included in the file-init fragment. The published
|
||||||
|
wrapper then called module init and project init separately, so the same
|
||||||
|
callable could run twice. Specs required project init exactly once, after all
|
||||||
|
module initializers and before the frame root.
|
||||||
|
|
||||||
|
## Consolidated Decision
|
||||||
|
|
||||||
|
Declaration and assembly are separate.
|
||||||
|
|
||||||
|
An executable frontend emits an immutable, ordered, typed
|
||||||
|
`IRLifecycleDeclaration` on `IRBackend`. The declaration carries:
|
||||||
|
|
||||||
|
- modules in explicit dependency order;
|
||||||
|
- each module's ordered file-init fragment references;
|
||||||
|
- an optional project-init callable;
|
||||||
|
- exactly one frame-root callable.
|
||||||
|
|
||||||
|
References use `ModuleId`, `FileId`, `CallableId`, a typed role, and source
|
||||||
|
attribution. Absence of a declaration means a deliberately non-executable
|
||||||
|
handoff. It must not synthesize a default `main`.
|
||||||
|
|
||||||
|
`FrontendPhasePipelineStage` stops at that declarative backend.
|
||||||
|
`AssembleLifecyclePipelineStage` runs immediately after it and is the terminal
|
||||||
|
stage of `analyze()`. `LifecycleAssemblerService` is the only owner of derived
|
||||||
|
lifecycle artifacts. `compile()` and `build()` reuse that single assembly pass.
|
||||||
|
|
||||||
|
`ResolveDepsPipelineStage` remains the ordering authority. Frontends propagate
|
||||||
|
that order; the assembler validates and applies it. Neither side may rebuild
|
||||||
|
order from synthetic names, `FileId`, textual module names, or incidental map
|
||||||
|
order.
|
||||||
|
|
||||||
|
Project init is excluded from file-init fragments and runs exactly once. Common
|
||||||
|
derived identities are neutral and deterministic. Common code must not import
|
||||||
|
PBS packages or inspect `__pbs.*` prefixes. Typed roles, ids, and origins are
|
||||||
|
the stable inspection surface; synthetic names are internal.
|
||||||
|
|
||||||
|
## Final Implementation
|
||||||
|
|
||||||
|
PBS now emits per-file lifecycle contributions and a project-level declaration.
|
||||||
|
A colocated `[Init]` is the project-init reference only. File-init fragments
|
||||||
|
remain frontend-lowered executables, consumed by id and role rather than by
|
||||||
|
name prefix. `synthesizePublishedLifecycle` was removed.
|
||||||
|
|
||||||
|
The common assembler validates the declaration and materializes:
|
||||||
|
|
||||||
|
1. module init callables in declared module/file order;
|
||||||
|
2. an optional project-init wrapper, invoked once;
|
||||||
|
3. the published frame wrapper, which is the physical entrypoint;
|
||||||
|
4. a hidden boot guard that prevents a second bootstrap after success.
|
||||||
|
|
||||||
|
The declaration remains on the assembled `IRBackend` as provenance. IRVM
|
||||||
|
lowering keeps only assembled-graph preconditions such as wrapper identity,
|
||||||
|
synthetic origin, and boot-guard presence. It does not reconstruct lifecycle
|
||||||
|
from names.
|
||||||
|
|
||||||
|
`analyze()` still returns a fully assembled executable backend, without IRVM,
|
||||||
|
bytecode, or file output. Neutral names use the `__prometeu.lifecycle.*` prefix.
|
||||||
|
|
||||||
|
Diagnostics stay split: PBS reports marker, signature, placement, cardinality,
|
||||||
|
and colocation errors; the common layer reports unresolved ids, incompatible
|
||||||
|
roles, missing frame root, invalid origins, and frontend-preassembled derived
|
||||||
|
artifacts.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Good frontend output:
|
||||||
|
|
||||||
|
- a file with globals or a non-project `[Init]` contributes a
|
||||||
|
`FILE_INIT_FRAGMENT`;
|
||||||
|
- the `[Init]` next to `[Frame]` is `PROJECT_INIT` only;
|
||||||
|
- `[Frame]` is `FRAME_ROOT`;
|
||||||
|
- module order follows the resolved dependency graph even when names or file
|
||||||
|
ids would sort the other way.
|
||||||
|
|
||||||
|
Good assembled output:
|
||||||
|
|
||||||
|
- wrapper call order is module inits, then project init once, then frame root;
|
||||||
|
- boot guard is a hidden global, not a user global;
|
||||||
|
- `getLifecycleDeclaration()` still exposes the original typed references.
|
||||||
|
|
||||||
|
Bad output:
|
||||||
|
|
||||||
|
- PBS emitting `MODULE_INIT`, `PROJECT_INIT` wrappers, published wrappers, or
|
||||||
|
boot guards;
|
||||||
|
- common code matching `__pbs.frame_wrapper$m0` to discover the entrypoint;
|
||||||
|
- sorting modules by `FileId` or textual path because the declaration order was
|
||||||
|
inconvenient;
|
||||||
|
- leaving both the PBS assembler and the common stage active.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- Do not treat `analyze()` as "frontend only." It now includes common lifecycle
|
||||||
|
assembly so callers still receive an executable `IRBackend`.
|
||||||
|
- Do not keep a temporary dual path. Two assemblers duplicate wrappers or init.
|
||||||
|
- Do not infer missing frame roots or repair invalid declarations in the common
|
||||||
|
layer. Fail deterministically.
|
||||||
|
- Do not move PBS marker rules into the assembler. Language admission stays in
|
||||||
|
PBS; structural handoff checks stay common.
|
||||||
|
- Do not preserve `__pbs.*` names for common-derived artifacts as compatibility
|
||||||
|
behavior. They were never a public ABI.
|
||||||
|
- Do not confuse a frontend file-init fragment name with a common-derived
|
||||||
|
identity. PBS may still name its own fragments; common code must not parse
|
||||||
|
those names.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Decision: `DEC-0046`
|
||||||
|
- Plan: `PLN-0125`
|
||||||
|
- Related lessons: `LSN-0059`, `LSN-0060`
|
||||||
|
- `docs/specs/compiler/23. Compiler Pipeline Entry Points Specification.md`
|
||||||
|
- `docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md`
|
||||||
|
- `docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md`
|
||||||
|
- `docs/specs/compiler-languages/pbs/4. Static Semantics Specification.md`
|
||||||
|
- `docs/specs/compiler-languages/pbs/9. Dynamic Semantics Specification.md`
|
||||||
|
- `docs/specs/compiler-languages/pbs/12. Diagnostics Specification.md`
|
||||||
|
- `docs/specs/compiler-languages/pbs/13. Lowering IRBackend Specification.md`
|
||||||
|
- `prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRLifecycleDeclaration.java`
|
||||||
|
- `prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/lifecycle/LifecycleAssemblerService.java`
|
||||||
|
- `prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/AssembleLifecyclePipelineStage.java`
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
---
|
||||||
|
id: LSN-0061
|
||||||
|
ticket: multi-frontend-pvm-neutrality
|
||||||
|
title: PBX is the language-neutral runtime boundary
|
||||||
|
created: 2026-09-18
|
||||||
|
tags: [vm-arch, runtime, pvm, pbx, compiler, multi-frontend]
|
||||||
|
---
|
||||||
|
|
||||||
|
# PBX is the language-neutral runtime boundary
|
||||||
|
|
||||||
|
## Original Problem
|
||||||
|
|
||||||
|
PBS was the first implemented frontend, so its name still appeared in a few
|
||||||
|
common compiler specification titles. That editorial residue made it easier to
|
||||||
|
misread the architecture as if PBS owned bytecode, IRVM, or the PVM execution
|
||||||
|
model.
|
||||||
|
|
||||||
|
The implementation was already mostly neutral: the compiler emitted PBX, the
|
||||||
|
runtime loaded PBX, and the common backend did not import PBS frontend packages.
|
||||||
|
The real risk was future drift. In particular, IRVM could be mistaken for a
|
||||||
|
runtime input, `PBS\0` could be confused with the executable magic, and duplicated
|
||||||
|
VM-architecture documents could silently become competing authorities.
|
||||||
|
|
||||||
|
## Consolidated Decision
|
||||||
|
|
||||||
|
The executable pipeline is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
language frontend -> IRBackend -> IRVM -> PBX -> loader/verifier -> PVM
|
||||||
|
```
|
||||||
|
|
||||||
|
`IRBackend`, `IRVM`, optimization, and PBX emission are compiler-owned.
|
||||||
|
`IRVM` is an internal compiler representation and never crosses the runtime
|
||||||
|
boundary. PBX is the only executable artifact consumed by the runtime.
|
||||||
|
|
||||||
|
The runtime repository owns PBX binary layout, Core ISA, loader behavior,
|
||||||
|
verification, and PVM execution. Studio owns the common compiler transformations
|
||||||
|
that produce conforming PBX. Studio copies of VM-architecture documents are
|
||||||
|
mirrors, not independent authorities.
|
||||||
|
|
||||||
|
`PBS` names a language, frontend, stdlib, and explicitly language-specific
|
||||||
|
integration. It does not name the executable format, magic, ISA, ABI, program
|
||||||
|
image, or runtime type. The canonical persisted artifact is `program.pbx`, and
|
||||||
|
its binary magic is `PBX\0`.
|
||||||
|
|
||||||
|
## Final Implementation
|
||||||
|
|
||||||
|
The common compiler specifications now identify bytecode/PBX and IRVM
|
||||||
|
optimization as language-neutral surfaces. They explicitly state that IRVM is
|
||||||
|
internal to the compiler and that PBX is the compiler/runtime boundary. Existing
|
||||||
|
PBS references were retained where they describe legitimate frontend-specific
|
||||||
|
publication or metadata behavior.
|
||||||
|
|
||||||
|
Studio's copies of `ARCHITECTURE.md`, `ISA_CORE.md`, and `INTRINSICS.csv` were
|
||||||
|
synchronized with the runtime repository. A mirror policy now identifies the
|
||||||
|
runtime repository as canonical and provides byte-for-byte comparison commands.
|
||||||
|
|
||||||
|
Existing Studio tests already proved two important invariants:
|
||||||
|
|
||||||
|
- `BytecodeModule` serializes the exact `PBX\0` header;
|
||||||
|
- common backend contracts neither import nor publicly expose PBS frontend
|
||||||
|
types.
|
||||||
|
|
||||||
|
Runtime conformance was strengthened in three focused places:
|
||||||
|
|
||||||
|
- the bytecode invalid-magic regression now explicitly proves that legacy
|
||||||
|
`PBS\0` is rejected;
|
||||||
|
- the PVM initialization regression now creates a `BytecodeModule` directly,
|
||||||
|
serializes it, loads and verifies it, prepares the boot call, and executes it
|
||||||
|
through `HALT` without any frontend;
|
||||||
|
- a quality check inspects the `prometeu-bytecode` and `prometeu-vm` manifests
|
||||||
|
and rejects dependencies whose identities belong to compiler or frontend
|
||||||
|
layers.
|
||||||
|
|
||||||
|
No production VM behavior, ISA, ABI, or binary format changed.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Language-specific work stops before the common backend:
|
||||||
|
|
||||||
|
```text
|
||||||
|
PBS AST and semantic model
|
||||||
|
-> PBS lowering
|
||||||
|
-> neutral IRBackend data
|
||||||
|
-> common IRVM lowering and optimization
|
||||||
|
-> PBX\0
|
||||||
|
```
|
||||||
|
|
||||||
|
A future frontend follows the same boundary:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FutureLanguage AST and semantic model
|
||||||
|
-> FutureLanguage lowering
|
||||||
|
-> neutral IRBackend data
|
||||||
|
-> the same common IRVM and PBX stages
|
||||||
|
```
|
||||||
|
|
||||||
|
The runtime sees the same PBX contract in both cases. It does not receive a
|
||||||
|
`languageId`, source extension, AST object, IRVM object, or frontend service.
|
||||||
|
|
||||||
|
## Pitfalls and Anti-Patterns
|
||||||
|
|
||||||
|
- Do not expose IRVM as a convenient runtime protocol. It is optimized for
|
||||||
|
compiler transformations, not binary compatibility or runtime loading.
|
||||||
|
- Do not rename PBX concepts after the first frontend. `PBS\0` is invalid even
|
||||||
|
when PBS produced the program.
|
||||||
|
- Do not globally ban the word `PBS` from runtime documentation. PBS is a valid
|
||||||
|
guest-language example; guards should inspect dependencies, public types, and
|
||||||
|
format identities instead of raw text.
|
||||||
|
- Do not duplicate tests when an existing guard proves the entire invariant.
|
||||||
|
Extend coverage only for the missing distinction, such as explicit `PBS\0`
|
||||||
|
rejection.
|
||||||
|
- Do not let a documentation mirror become a second authority. Synchronize it
|
||||||
|
against runtime or replace it with an explicit reference.
|
||||||
|
- Loading a PBX image is not the same as beginning execution. The PVM boot test
|
||||||
|
must prepare the published boot call before running the instruction budget.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `docs/specs/compiler/15. Bytecode and PBX Mapping Specification.md`
|
||||||
|
- `docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md`
|
||||||
|
- `docs/specs/compiler/21. IRVM Optimization Pipeline Specification.md`
|
||||||
|
- `docs/vm-arch/README.md`
|
||||||
|
- `prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeModule.java`
|
||||||
|
- `prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/CommonBackendArchitectureTest.java`
|
||||||
|
- Runtime: `crates/console/prometeu-bytecode/src/model.rs`
|
||||||
|
- Runtime: `crates/console/prometeu-vm/src/virtual_machine.rs`
|
||||||
|
- Runtime: `crates/dev/prometeu-quality-checks/tests/pvm_frontend_neutrality.rs`
|
||||||
|
|
||||||
|
## Takeaways
|
||||||
|
|
||||||
|
- PBX is the stable, language-neutral executable boundary.
|
||||||
|
- IRVM stays private to the compiler pipeline.
|
||||||
|
- Runtime authority and compiler authority are complementary, not competing.
|
||||||
|
- Small dependency and artifact tests are enough to protect this boundary
|
||||||
|
without introducing a plugin platform or a second language.
|
||||||
@ -1,77 +0,0 @@
|
|||||||
---
|
|
||||||
id: AGD-0062
|
|
||||||
ticket: multi-frontend-common-lifecycle
|
|
||||||
title: Extrair lifecycle comum das responsabilidades do frontend PBS
|
|
||||||
status: open
|
|
||||||
created: 2026-07-15
|
|
||||||
resolved:
|
|
||||||
decision:
|
|
||||||
tags: [compiler, compiler-general, compiler-pbs, lifecycle, backend, multi-frontend]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Agenda - Extrair o lifecycle do frontend PBS
|
|
||||||
|
|
||||||
## Objetivo
|
|
||||||
|
|
||||||
Domain owner: `compiler/general`, com impacto em `compiler/pbs`.
|
|
||||||
|
|
||||||
Separar declaracao de lifecycle feita por uma linguagem da montagem comum de init, frame, module initializers e entrypoint publicado.
|
|
||||||
|
|
||||||
## Contexto atual
|
|
||||||
|
|
||||||
As lessons e specs antigas indicam que PBS ja trata lifecycle e wrapper/entrypoint. O alinhamento pede que `PBSFrontendPhaseService` deixe de conter regras universais da plataforma.
|
|
||||||
|
|
||||||
## Escopo
|
|
||||||
|
|
||||||
- Localizar onde PBS sintetiza ou valida lifecycle.
|
|
||||||
- Definir contrato comum de `LifecycleDeclaration` ou equivalente.
|
|
||||||
- Planejar servico comum para validar, ordenar e montar entrypoint.
|
|
||||||
|
|
||||||
## Fora de escopo
|
|
||||||
|
|
||||||
- Alterar sintaxe PBS.
|
|
||||||
- Mudar comportamento observavel de projetos existentes.
|
|
||||||
- Refatorar todo o backend junto da extracao.
|
|
||||||
|
|
||||||
## Arquivos e componentes a inspecionar
|
|
||||||
|
|
||||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/...PBSFrontendPhaseService...`
|
|
||||||
- `prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java`
|
|
||||||
- `docs/specs/compiler/16. Runtime Execution and Initialization Specification.md`
|
|
||||||
- `docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md`
|
|
||||||
- `docs/specs/compiler-languages/pbs/13. Lowering IRBackend Specification.md`
|
|
||||||
|
|
||||||
## Alteracoes propostas
|
|
||||||
|
|
||||||
Opcao A: frontend PBS emite declaracoes neutras de lifecycle e backend/common assembler monta o entrypoint.
|
|
||||||
|
|
||||||
Opcao B: manter montagem no PBS por enquanto, mas isolar regras comuns em um servico compartilhado chamado pelo PBS.
|
|
||||||
|
|
||||||
Recomendacao inicial: extrair primeiro o servico comum com testes de caracterizacao, depois migrar o PBS para produzir apenas declaracoes.
|
|
||||||
|
|
||||||
## Estrategia de implementacao
|
|
||||||
|
|
||||||
Identificar fixture positiva atual, proteger com teste, criar contrato comum minimo, mover validacoes universais e manter mensagens PBS onde forem sintaxe/semantica da linguagem.
|
|
||||||
|
|
||||||
## Testes necessarios
|
|
||||||
|
|
||||||
- Lifecycle valido PBS continua gerando mesmo PBX/IRVM.
|
|
||||||
- Lifecycle ausente ou frame invalido falha no nivel comum quando aplicavel.
|
|
||||||
- Teste de IR comum com lifecycle sem parser PBS.
|
|
||||||
|
|
||||||
## Criterios de aceitacao
|
|
||||||
|
|
||||||
- PBS traduz a forma da linguagem para declaracoes neutras.
|
|
||||||
- Servico comum valida e monta lifecycle da plataforma.
|
|
||||||
|
|
||||||
## Riscos
|
|
||||||
|
|
||||||
- Mover validacoes PBS-especificas para camada comum por engano.
|
|
||||||
- Alterar ordem de inicializadores sem perceber.
|
|
||||||
|
|
||||||
## Decisoes que devem ser registradas
|
|
||||||
|
|
||||||
- Nome e ownership do servico comum.
|
|
||||||
- Forma canonica das declaracoes de lifecycle.
|
|
||||||
- Separacao entre erro de linguagem e erro de plataforma.
|
|
||||||
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
---
|
|
||||||
id: AGD-0065
|
|
||||||
ticket: multi-frontend-pvm-neutrality
|
|
||||||
title: Neutralidade da PVM e identificacao PBX independente de PBS
|
|
||||||
status: open
|
|
||||||
created: 2026-07-15
|
|
||||||
resolved:
|
|
||||||
decision:
|
|
||||||
tags: [vm-arch, runtime, pvm, pbx, compiler, multi-frontend]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Agenda - Manter a PVM neutra
|
|
||||||
|
|
||||||
## Objetivo
|
|
||||||
|
|
||||||
Domain owner: `vm-arch`, com impacto em `compiler/general`.
|
|
||||||
|
|
||||||
Garantir que a PVM conheca apenas PBX/IRVM e conceitos runtime neutros, incluindo identificacao correta do formato como `PBX`/`PBX\0`, nao `PBS`.
|
|
||||||
|
|
||||||
## Contexto atual
|
|
||||||
|
|
||||||
O documento e focado no Studio, mas runtime neutrality e uma fronteira transversal. A PVM nao deve conhecer `PBS service`, `PBS struct`, `PBS optional` ou qualquer conceito de linguagem.
|
|
||||||
|
|
||||||
## Escopo
|
|
||||||
|
|
||||||
- Auditar nomenclatura e imports runtime/PVM disponiveis no repo.
|
|
||||||
- Verificar magic/header de PBX quando o codigo estiver no workspace.
|
|
||||||
- Confirmar que linguagem e convertida antes da execucao.
|
|
||||||
|
|
||||||
## Fora de escopo
|
|
||||||
|
|
||||||
- Modificar a PVM se o acoplamento estiver apenas no Studio ou compiler.
|
|
||||||
- Alterar ISA sem necessidade.
|
|
||||||
- Adicionar suporte a qualquer linguagem nova.
|
|
||||||
|
|
||||||
## Arquivos e componentes a inspecionar
|
|
||||||
|
|
||||||
- `docs/vm-arch/ARCHITECTURE.md`
|
|
||||||
- `docs/vm-arch/ISA_CORE.md`
|
|
||||||
- `docs/specs/compiler/15. Bytecode and PBX Mapping Specification.md`
|
|
||||||
- `prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/...`
|
|
||||||
- runtime/PVM em repositorio ou modulo relacionado, quando presente
|
|
||||||
|
|
||||||
## Alteracoes propostas
|
|
||||||
|
|
||||||
Opcao A: auditoria documental e de codigo para nomes PBS indevidos em runtime/bytecode.
|
|
||||||
|
|
||||||
Opcao B: corrigir imediatamente apenas identificacao `PBS` vs `PBX` se houver bug confirmado e isolado.
|
|
||||||
|
|
||||||
Recomendacao inicial: auditar primeiro; corrigir somente caso a fronteira esteja claramente violada e a mudanca seja pequena.
|
|
||||||
|
|
||||||
## Estrategia de implementacao
|
|
||||||
|
|
||||||
Buscar termos PBS no runtime, bytecode e specs VM; classificar ocorrencias validas de docs historicas vs erros de contrato.
|
|
||||||
|
|
||||||
## Testes necessarios
|
|
||||||
|
|
||||||
- Teste de magic/header PBX.
|
|
||||||
- Teste arquitetural impedindo imports PBS no runtime/PVM.
|
|
||||||
- Teste de carregamento/verificacao de programa sem dependencia de linguagem.
|
|
||||||
|
|
||||||
## Criterios de aceitacao
|
|
||||||
|
|
||||||
- PVM nao depende de conceitos PBS.
|
|
||||||
- Formato executavel e tratado como PBX.
|
|
||||||
- Qualquer correcao runtime necessaria fica separada de refactors Studio/compiler.
|
|
||||||
|
|
||||||
## Riscos
|
|
||||||
|
|
||||||
- Confundir nomenclatura documental antiga com acoplamento real.
|
|
||||||
- Expandir escopo para VM quando o problema pertence ao compiler.
|
|
||||||
|
|
||||||
## Decisoes que devem ser registradas
|
|
||||||
|
|
||||||
- Lista de conceitos permitidos na PVM.
|
|
||||||
- Politica de nomenclatura PBX/PBS.
|
|
||||||
- Onde ficam testes de neutralidade runtime.
|
|
||||||
|
|
||||||
@ -194,16 +194,20 @@ At minimum, the PBS diagnostics baseline must cover:
|
|||||||
- `project init must be colocated with frame`,
|
- `project init must be colocated with frame`,
|
||||||
- `Init attribute target invalid`,
|
- `Init attribute target invalid`,
|
||||||
- `InitAllowed is valid only on SDK host methods`,
|
- `InitAllowed is valid only on SDK host methods`,
|
||||||
- `host call not allowed during init`,
|
- `host call not allowed during init`.
|
||||||
|
9. common lifecycle structural diagnostics owned by lifecycle assembly and backend preconditions, including:
|
||||||
|
- invalid or unresolved lifecycle declaration ids and roles,
|
||||||
|
- missing frame root on an executable declaration,
|
||||||
|
- frontend-preassembled derived lifecycle artifacts,
|
||||||
- `synthetic wrapper entrypoint missing`,
|
- `synthetic wrapper entrypoint missing`,
|
||||||
- `published entrypoint is not function index 0`,
|
- `published entrypoint is not function index 0`,
|
||||||
- `hidden boot guard is missing`,
|
- `hidden boot guard is missing`,
|
||||||
- `synthetic callable origin missing`.
|
- `synthetic callable origin missing`.
|
||||||
9. symbolic asset-reference diagnostics required by backend-owned `Addressable` resolution, including:
|
10. symbolic asset-reference diagnostics required by backend-owned `Addressable` resolution, including:
|
||||||
- unresolved terminal asset reference,
|
- unresolved terminal asset reference,
|
||||||
- namespace-versus-terminal asset misuse,
|
- namespace-versus-terminal asset misuse,
|
||||||
- structurally invalid symbolic asset path admitted by source syntax but rejected by semantic surface rules.
|
- structurally invalid symbolic asset path admitted by source syntax but rejected by semantic surface rules.
|
||||||
10. ignored-value diagnostics for expression statements, including:
|
11. ignored-value diagnostics for expression statements, including:
|
||||||
- a stable warning when a materialized value is produced and then ignored,
|
- a stable warning when a materialized value is produced and then ignored,
|
||||||
- and no warning for unit-like expression statements.
|
- and no warning for unit-like expression statements.
|
||||||
|
|
||||||
|
|||||||
@ -86,10 +86,12 @@ For each admitted PBS source unit and callable in the current lowering slice, PB
|
|||||||
4. source attribution anchor (`file + span`) for diagnostics and traceability,
|
4. source attribution anchor (`file + span`) for diagnostics and traceability,
|
||||||
5. source-observable parse intent for statement/expression structure (including precedence/associativity outcome already fixed by AST shape).
|
5. source-observable parse intent for statement/expression structure (including precedence/associativity outcome already fixed by AST shape).
|
||||||
6. deterministic `requiredCapabilities` derived from admitted host-binding metadata for packer/runtime-manifest assistance.
|
6. deterministic `requiredCapabilities` derived from admitted host-binding metadata for packer/runtime-manifest assistance.
|
||||||
7. compiler-selected published-wrapper entrypoint identity for backend handoff,
|
7. a typed `IRLifecycleDeclaration` for executable programs, with file-init fragment, optional project-init, and required frame-root references,
|
||||||
8. explicit global and synthetic lifecycle structure required by executable lowering.
|
8. explicit user globals and frontend-lowered file-init fragments required by later common lifecycle assembly,
|
||||||
9. backend-owned symbolic identities that must survive to later executable lowering, including symbolic asset references when admitted by the source slice.
|
9. backend-owned symbolic identities that must survive to later executable lowering, including symbolic asset references when admitted by the source slice.
|
||||||
|
|
||||||
|
PBS lowering MUST NOT emit module init, project-init wrapper, published frame wrapper, physical entrypoint, or boot guard. Those artifacts are produced exclusively by common lifecycle assembly.
|
||||||
|
|
||||||
Lowering must not collapse source categories in a way that erases required declaration/callable identity needed by downstream diagnostics or conformance assertions.
|
Lowering must not collapse source categories in a way that erases required declaration/callable identity needed by downstream diagnostics or conformance assertions.
|
||||||
|
|
||||||
The normative contract is obligation-based, not tied to one mandatory in-memory class graph.
|
The normative contract is obligation-based, not tied to one mandatory in-memory class graph.
|
||||||
@ -221,20 +223,18 @@ It does not replace:
|
|||||||
2. `docs/specs/compiler/21. IRVM Optimization Pipeline Specification.md`,
|
2. `docs/specs/compiler/21. IRVM Optimization Pipeline Specification.md`,
|
||||||
3. or `docs/specs/compiler/15. Bytecode and PBX Mapping Specification.md`.
|
3. or `docs/specs/compiler/15. Bytecode and PBX Mapping Specification.md`.
|
||||||
|
|
||||||
### 12.7 Executable Lifecycle and Published Wrapper Obligation
|
### 12.7 Executable Lifecycle Declaration Obligation
|
||||||
|
|
||||||
For executable PBS frontends, PBS lowering must preserve the compiler-selected published wrapper rather than a frontend-declared nominal entrypoint.
|
For executable PBS frontends, PBS lowering MUST emit a typed `IRLifecycleDeclaration` rather than assembling the published lifecycle.
|
||||||
|
|
||||||
At `IRBackend` emission time:
|
At frontend `IRBackend` emission time:
|
||||||
|
|
||||||
1. the lowered graph must contain explicit lifecycle structure for:
|
1. PBS MUST retain marker admission for `[Init]`, `[Frame]`, and `InitAllowed`,
|
||||||
- user-authored globals,
|
2. PBS MUST emit typed references for:
|
||||||
- file init fragments,
|
- file-init fragments that include global materialization and non-project `[Init]`,
|
||||||
- module init,
|
- the optional project-init callable, which is the `[Init]` colocated with `[Frame]`,
|
||||||
- project init when present,
|
- and the required frame-root callable marked with `[Frame]`,
|
||||||
- and the published frame wrapper,
|
3. the colocated project init MUST be excluded from file-init fragments,
|
||||||
2. the published frame wrapper must be the effective entrypoint identity handed to common backend stages,
|
4. module order in the declaration MUST propagate the explicit dependency order already derived from resolved project/module data, without sorting by `FileId`, textual module name, or synthetic callable name,
|
||||||
3. the userland callable marked with `[Frame]` must remain distinguishable as the logical frame root,
|
5. PBS MUST NOT emit module init, project-init wrapper, published frame wrapper, physical entrypoint, or boot guard,
|
||||||
4. the wrapper must own final `FRAME_RET`,
|
6. and common lifecycle assembly remains the owner of derived artifacts, including the published wrapper that becomes the physical entrypoint and owns the final frame return.
|
||||||
5. common backend stages must not reintroduce manifest-owned or `FrontendSpec`-owned nominal entrypoint authority,
|
|
||||||
6. and hidden compiler-owned lifecycle state such as the boot guard must remain structurally distinguishable from user globals.
|
|
||||||
|
|||||||
@ -250,6 +250,8 @@ Rules:
|
|||||||
- A file may declare at most one `[Init]`.
|
- A file may declare at most one `[Init]`.
|
||||||
- The `[Init]` colocated with `[Frame]` is the project init.
|
- The `[Init]` colocated with `[Frame]` is the project init.
|
||||||
- Project init must be colocated with `[Frame]`.
|
- Project init must be colocated with `[Frame]`.
|
||||||
|
- PBS static semantics and lowering admit those markers and translate them into a typed `IRLifecycleDeclaration`.
|
||||||
|
- PBS MUST NOT materialize module init, project-init wrapper, published frame wrapper, physical entrypoint, or boot guard; those artifacts belong to common lifecycle assembly.
|
||||||
- `InitAllowed` is valid only on SDK host methods and is invalid on userland `fn`, `global`, `service`, `const`, `struct`, `contract`, and callback surfaces.
|
- `InitAllowed` is valid only on SDK host methods and is invalid on userland `fn`, `global`, `service`, `const`, `struct`, `contract`, and callback surfaces.
|
||||||
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Host` attribute.
|
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Host` attribute.
|
||||||
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Capability` attribute.
|
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Capability` attribute.
|
||||||
|
|||||||
@ -102,17 +102,21 @@ Executable PBS programs have a logical bootstrap sequence before ordinary frame
|
|||||||
Normative order:
|
Normative order:
|
||||||
|
|
||||||
1. materialize globals for each file according to dependency order,
|
1. materialize globals for each file according to dependency order,
|
||||||
2. execute that file's `[Init]`, if present,
|
2. execute that file's non-project `[Init]`, if present,
|
||||||
3. compose those steps into one synthetic module init per module,
|
3. compose those steps into one synthetic module init per module,
|
||||||
4. execute project init, if present,
|
4. execute project init exactly once, if present, after all module initializers,
|
||||||
5. then invoke the userland callable marked with `[Frame]`.
|
5. then invoke the userland callable marked with `[Frame]`.
|
||||||
|
|
||||||
|
The `[Init]` colocated with `[Frame]` is project init. It MUST NOT also run as a file-init fragment.
|
||||||
|
|
||||||
Lifecycle bootstrap is one-shot:
|
Lifecycle bootstrap is one-shot:
|
||||||
|
|
||||||
- no automatic retry occurs after boot failure,
|
- no automatic retry occurs after boot failure,
|
||||||
- failure during module init aborts boot,
|
- failure during module init aborts boot,
|
||||||
- failure during project init aborts boot.
|
- failure during project init aborts boot.
|
||||||
|
|
||||||
|
Common lifecycle assembly materializes this order from the typed declaration. PBS remains the language source of marker meaning; it is not the owner of the published wrapper.
|
||||||
|
|
||||||
## 6.2 Logical frame root and physical wrapper
|
## 6.2 Logical frame root and physical wrapper
|
||||||
|
|
||||||
PBS distinguishes the logical frame root from the physical published entrypoint:
|
PBS distinguishes the logical frame root from the physical published entrypoint:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# PBS Bytecode and PBX Mapping Specification
|
# Bytecode and PBX Mapping Specification
|
||||||
|
|
||||||
Status: Draft v1 (Backend Baseline)
|
Status: Draft v1 (Backend Baseline)
|
||||||
Applies to: mapping from lowered/optimized backend programs into PBX sections, bytecode-facing artifacts, and source-to-artifact invariants required by loader/verifier/runtime
|
Applies to: mapping from lowered/optimized backend programs into PBX sections, bytecode-facing artifacts, and source-to-artifact invariants required by loader/verifier/runtime
|
||||||
@ -9,6 +9,10 @@ This document defines the normative mapping between backend-lowered semantics an
|
|||||||
|
|
||||||
Its purpose is to keep artifact emission deterministic and compatible with runtime loader/verifier contracts.
|
Its purpose is to keep artifact emission deterministic and compatible with runtime loader/verifier contracts.
|
||||||
|
|
||||||
|
PBX is the only executable boundary between the compiler and runtime. `IRVM`
|
||||||
|
is a compiler-internal representation consumed by bytecode emission; it MUST
|
||||||
|
NOT be exposed to or consumed by the runtime loader, verifier, or PVM.
|
||||||
|
|
||||||
## 2. Scope
|
## 2. Scope
|
||||||
|
|
||||||
This document defines:
|
This document defines:
|
||||||
|
|||||||
@ -11,6 +11,12 @@ Its purpose is to make backend lowering deterministic, reviewable, and compatibl
|
|||||||
|
|
||||||
`IRBackend` is the common frontend-to-backend executable handoff. It is emitted by frontends and consumed by common backend stages. Backend lowering MUST NOT require PBS AST nodes, PBS tokens, PBS parser structures, PBS semantic objects, PBS editorial objects, or equivalent language-owned objects from future frontends.
|
`IRBackend` is the common frontend-to-backend executable handoff. It is emitted by frontends and consumed by common backend stages. Backend lowering MUST NOT require PBS AST nodes, PBS tokens, PBS parser structures, PBS semantic objects, PBS editorial objects, or equivalent language-owned objects from future frontends.
|
||||||
|
|
||||||
|
`IRVM` is an internal compiler representation. It MUST be consumed by common
|
||||||
|
compiler optimization and PBX emission stages, and it MUST NOT cross the
|
||||||
|
compiler/runtime boundary or become an input, dependency, public type, or
|
||||||
|
operational concept of the runtime loader, verifier, or PVM. PBX is the only
|
||||||
|
executable artifact boundary between compiler and runtime.
|
||||||
|
|
||||||
## 2. Scope
|
## 2. Scope
|
||||||
|
|
||||||
This document defines:
|
This document defines:
|
||||||
@ -66,10 +72,32 @@ At this boundary, `IRBackend` may contain only language-neutral compiler concept
|
|||||||
8. host-call metadata,
|
8. host-call metadata,
|
||||||
9. intrinsic metadata,
|
9. intrinsic metadata,
|
||||||
10. reserved metadata required for backend lowering,
|
10. reserved metadata required for backend lowering,
|
||||||
11. and required runtime or platform capabilities.
|
11. required runtime or platform capabilities,
|
||||||
|
12. and an optional typed `IRLifecycleDeclaration` used as lifecycle provenance after common assembly.
|
||||||
|
|
||||||
The common backend contract MUST express these concepts as backend obligations, not as PBS syntax or PBS semantic rules. When a concept currently originates from PBS, the backend contract still owns only the language-neutral obligation carried by `IRBackend`.
|
The common backend contract MUST express these concepts as backend obligations, not as PBS syntax or PBS semantic rules. When a concept currently originates from PBS, the backend contract still owns only the language-neutral obligation carried by `IRBackend`.
|
||||||
|
|
||||||
|
### 4.3 IRLifecycleDeclaration
|
||||||
|
|
||||||
|
`IRLifecycleDeclaration` is the typed, immutable, serializable-by-design lifecycle provenance carried by `IRBackend`.
|
||||||
|
|
||||||
|
An executable handoff MUST contain exactly one declaration before common assembly. A deliberately non-executable handoff MAY omit it; absence MUST NOT synthesize a default `main` or published wrapper.
|
||||||
|
|
||||||
|
The declaration MUST represent, using ordered collections and typed ids:
|
||||||
|
|
||||||
|
1. modules in explicit dependency order,
|
||||||
|
2. each module's ordered file-init fragment references,
|
||||||
|
3. an optional project-init callable reference,
|
||||||
|
4. and exactly one frame-root callable reference.
|
||||||
|
|
||||||
|
References MUST use `ModuleId`, `FileId`, `CallableId`, a typed role, and source attribution. The public shape MUST NOT expose maps, sets, callbacks, compiler services, or frontend-owned types.
|
||||||
|
|
||||||
|
`AssembleLifecyclePipelineStage` MUST run after `FrontendPhasePipelineStage` and before `LowerToIRVMPipelineStage`. `LifecycleAssemblerService` is the only owner of derived lifecycle artifacts: module init, project-init wrapper, published frame wrapper, physical entrypoint, and hidden boot guard. Those artifacts MUST use deterministic neutral internal identities. Common code MUST NOT discover roles from `__pbs.*` prefixes or depend on PBS packages.
|
||||||
|
|
||||||
|
The declaration MUST remain available on the assembled `IRBackend` as typed provenance. Downstream inspection MUST use roles, ids, and origins rather than synthetic names.
|
||||||
|
|
||||||
|
Project init MUST be absent from file-init fragments and MUST execute exactly once, after all module initializers and before the frame root. Bootstrap failure MUST abort without retry. The boot guard MUST prevent a second bootstrap after successful completion. The published wrapper owns the final frame return.
|
||||||
|
|
||||||
The common backend contract MUST NOT expose or require:
|
The common backend contract MUST NOT expose or require:
|
||||||
|
|
||||||
1. `PbsExpression`, `PbsStatement`, `PbsToken`, or equivalent PBS-owned syntax objects,
|
1. `PbsExpression`, `PbsStatement`, `PbsToken`, or equivalent PBS-owned syntax objects,
|
||||||
@ -114,11 +142,14 @@ Initial enforcement for this contract SHOULD be audit and guardrail based: specs
|
|||||||
Lowering from `IRBackend` to `IRVM` may start only when:
|
Lowering from `IRBackend` to `IRVM` may start only when:
|
||||||
|
|
||||||
1. frontend admission to `IRBackend` is complete for the active slice,
|
1. frontend admission to `IRBackend` is complete for the active slice,
|
||||||
2. required callsite categories (`CALL_FUNC`/`CALL_HOST`/`CALL_INTRINSIC`) are available,
|
2. common lifecycle assembly has already materialized derived artifacts for an executable handoff,
|
||||||
3. required canonical host/intrinsic metadata is available for admitted callsites,
|
3. required callsite categories (`CALL_FUNC`/`CALL_HOST`/`CALL_INTRINSIC`) are available,
|
||||||
4. dependency-scoped fail-fast exclusions have already been applied at the `IRBackend` boundary,
|
4. required canonical host/intrinsic metadata is available for admitted callsites,
|
||||||
5. compiler-selected published-wrapper entrypoint identity is present and unambiguous,
|
5. dependency-scoped fail-fast exclusions have already been applied at the `IRBackend` boundary,
|
||||||
6. and target `vm_profile` is selected deterministically.
|
6. compiler-selected published-wrapper entrypoint identity is present and unambiguous,
|
||||||
|
7. and target `vm_profile` is selected deterministically.
|
||||||
|
|
||||||
|
Declaration validation, role uniqueness, explicit order, and rejection of frontend-preassembled derived artifacts belong to `LifecycleAssemblerService`. `IRBackend -> IRVM` MUST keep only backend preconditions on the assembled graph, such as published-wrapper identity, synthetic origin presence, and hidden boot-guard presence. It MUST NOT reconstruct lifecycle order from callable names.
|
||||||
|
|
||||||
## 6. IRVM Model Obligations
|
## 6. IRVM Model Obligations
|
||||||
|
|
||||||
@ -159,13 +190,14 @@ Function indexing must be deterministic:
|
|||||||
4. manifest-owned or name-only entrypoint fallback is forbidden,
|
4. manifest-owned or name-only entrypoint fallback is forbidden,
|
||||||
5. and identical admitted input graphs must produce identical function-id assignments.
|
5. and identical admitted input graphs must produce identical function-id assignments.
|
||||||
|
|
||||||
For PBS executable lowering:
|
For executable lowering:
|
||||||
|
|
||||||
- the userland callable marked with `[Frame]` remains the logical frame root,
|
- the declared frame-root callable remains the logical frame root,
|
||||||
- the wrapper path must contain final `FRAME_RET`,
|
- the assembled published wrapper is the physical entrypoint,
|
||||||
|
- the wrapper path must contain the final frame return,
|
||||||
- and lowering must preserve that distinction through `IRVM`.
|
- and lowering must preserve that distinction through `IRVM`.
|
||||||
|
|
||||||
These PBS-facing names describe the current frontend source of the lifecycle structure. The backend-owned obligation is the neutral requirement that the compiler-selected published wrapper be the physical entrypoint and that lifecycle state remain explicit in `IRBackend`.
|
PBS `[Init]`/`[Frame]` markers are a frontend source of the declaration. The backend-owned obligation is the neutral requirement that the compiler-selected published wrapper be the physical entrypoint and that lifecycle provenance remain explicit in `IRBackend` through typed roles and ids. Neutral synthetic names are internal and are not a public ABI.
|
||||||
|
|
||||||
## 9. Callsite Lowering Obligations
|
## 9. Callsite Lowering Obligations
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# PBS IRVM Optimization Pipeline Specification
|
# IRVM Optimization Pipeline Specification
|
||||||
|
|
||||||
Status: Draft v1 (Pipeline Baseline)
|
Status: Draft v1 (Pipeline Baseline)
|
||||||
Applies to: placement and invariants of the `OptimizeIRVM` stage between lowering and bytecode emission
|
Applies to: placement and invariants of the `OptimizeIRVM` stage between lowering and bytecode emission
|
||||||
@ -9,6 +9,10 @@ This document defines the normative placement and safety invariants of the `Opti
|
|||||||
|
|
||||||
Its purpose is to allow optimization in the backend pipeline without transferring semantic optimization responsibility to runtime.
|
Its purpose is to allow optimization in the backend pipeline without transferring semantic optimization responsibility to runtime.
|
||||||
|
|
||||||
|
`IRVM` is internal to the compiler. Optimization output MUST flow to PBX
|
||||||
|
emission and MUST NOT be exposed as a runtime input or protocol. PBX remains the
|
||||||
|
only executable boundary consumed by the runtime loader, verifier, and PVM.
|
||||||
|
|
||||||
## 2. Scope
|
## 2. Scope
|
||||||
|
|
||||||
This document defines:
|
This document defines:
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
Status: Draft v1 (Traceability Baseline)
|
Status: Draft v1 (Traceability Baseline)
|
||||||
Applies to: compiler/backend conformance traceability for canonical stage order and entrypoint-specific contracts
|
Applies to: compiler/backend conformance traceability for canonical stage order and entrypoint-specific contracts
|
||||||
|
|
||||||
Last Updated: 2026-07-15
|
Last Updated: 2026-09-18
|
||||||
|
|
||||||
## 1. Purpose
|
## 1. Purpose
|
||||||
|
|
||||||
@ -46,6 +46,9 @@ to concrete positive/negative test evidence and current status.
|
|||||||
| 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.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.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-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-4.3.1 | Executable `IRBackend` MUST carry a typed, ordered, id-based `IRLifecycleDeclaration` as provenance. | `IRBackendExecutableContractTest#aggregatorMustRemapTypedLifecycleReferencesAndPreserveDeclaredModuleOrder`; `LifecycleAssemblerServiceTest#assembleMustMaterializeNeutralLifecycleArtifactsInDeclaredOrder` | `IRBackendExecutableContractTest#aggregatorMustOmitLifecycleDeclarationWhenNoFileContributesOne` | pass | Declaration uses `ReadOnlyList`, typed ids, roles, and source attribution; aggregator remaps ids with explicit module order. |
|
||||||
|
| G20-4.3.2 | Missing lifecycle declaration MUST mean a deliberately non-executable handoff and MUST NOT synthesize entrypoints. | `LifecycleAssemblerServiceTest#assembleMustNoOpWhenLifecycleDeclarationIsAbsent`; `AssembleLifecyclePipelineStageTest#runMustNoOpForDeliberatelyNonExecutableHandoff` | N/A | pass | Assembler and stage leave the backend unchanged when the declaration is absent. |
|
||||||
|
| G20-4.3.3 | Common lifecycle assembly MUST be the exclusive owner of derived artifacts and MUST use neutral identities. | `LifecycleAssemblerServiceTest#assembleMustMaterializeNeutralLifecycleArtifactsInDeclaredOrder`; `CommonBackendArchitectureTest#commonLifecycleAndBackendMustNotDiscoverRolesFromPbsNamePrefixes` | `LifecycleAssemblerServiceTest#assembleMustRejectPreMaterializedDerivedArtifacts` | pass | Derived wrappers and boot guard are common-owned; architecture guard rejects PBS imports and `__pbs.` prefix matching. |
|
||||||
| 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. |
|
||||||
@ -73,14 +76,18 @@ to concrete positive/negative test evidence and current status.
|
|||||||
| G21-9.1 | Validation MUST include optimized-vs-non-optimized equivalence fixtures. | `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLoweredHostIntrinsicFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForConditionalJoinFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForSimpleLoopFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLinearCallFixture` | N/A | pass | Dedicated opt on/off harness with reusable interpreter and deterministic trace assertions is in place. |
|
| G21-9.1 | Validation MUST include optimized-vs-non-optimized equivalence fixtures. | `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLoweredHostIntrinsicFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForConditionalJoinFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForSimpleLoopFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLinearCallFixture` | N/A | pass | Dedicated opt on/off harness with reusable interpreter and deterministic trace assertions is in place. |
|
||||||
| G21-9.2 | Validation MUST preserve known negative loader/verifier behavior. | `BackendSafetyGateSUTest#emitStageMustExposeMarshalingLinkageFailureDeterministically`; `BackendGateIIntegrationTest` rejection suite | N/A | pass | |
|
| G21-9.2 | Validation MUST preserve known negative loader/verifier behavior. | `BackendSafetyGateSUTest#emitStageMustExposeMarshalingLinkageFailureDeterministically`; `BackendGateIIntegrationTest` rejection suite | N/A | pass | |
|
||||||
| G21-9.3 | Validation MUST preserve deterministic artifact-level invariants. | `BackendSafetyGateSUTest#fullPipelineMustProduceDeterministicBytecodeForSameInput`; `BytecodeEmitterTest#emitMustRemainDeterministicAfterInterning` | N/A | pass | |
|
| G21-9.3 | Validation MUST preserve deterministic artifact-level invariants. | `BackendSafetyGateSUTest#fullPipelineMustProduceDeterministicBytecodeForSameInput`; `BytecodeEmitterTest#emitMustRemainDeterministicAfterInterning` | N/A | pass | |
|
||||||
| G23-5.1 | Canonical compiler pipeline MUST preserve the shared stage order through `WriteBytecodeArtifact`. | `MainProjectPipelineIntegrationTest#analyzeShouldNotWriteProgramBytecode`; `MainProjectPipelineIntegrationTest#compileShouldProduceInMemoryBytecodeWithoutWritingProgramBytecode`; `MainProjectPipelineIntegrationTest#buildShouldWriteProgramBytecode` | N/A | partial | Entrypoint execution now covers the full analyze -> compile -> build path, but a dedicated order-only assertion for every terminal boundary is still missing. |
|
| G23-5.1 | Canonical compiler pipeline MUST preserve the shared stage order through `WriteBytecodeArtifact`. | `BuilderPipelineServiceOrderTest#canonicalOrderMustContainOptimizeBetweenLowerAndEmit`; `MainProjectPipelineIntegrationTest#analyzeShouldNotWriteProgramBytecode`; `MainProjectPipelineIntegrationTest#compileShouldProduceInMemoryBytecodeWithoutWritingProgramBytecode`; `MainProjectPipelineIntegrationTest#buildShouldWriteProgramBytecode` | N/A | pass | Dedicated order assertion now covers analyses, compile, and build stage lists, including `AssembleLifecyclePipelineStage`. |
|
||||||
| G23-6.1 | `analyze` MUST terminate after `FrontendPhase` and MUST NOT execute backend artifact stages. | `MainProjectPipelineIntegrationTest#analyzeShouldNotWriteProgramBytecode` | N/A | pass | The integration fixture asserts the analysis snapshot contract and proves `program.pbx` is not written on the analysis path. |
|
| G23-5.2 | `AssembleLifecyclePipelineStage` MUST run immediately after `FrontendPhasePipelineStage` and before IRVM lowering. | `BuilderPipelineServiceOrderTest#canonicalOrderMustContainOptimizeBetweenLowerAndEmit`; `BuilderPipelineServiceOrderTest#analyzeMustTerminateAfterLifecycleAssemblyWithoutBackendArtifacts` | N/A | pass | Analyses terminate at lifecycle assembly; compile stages begin at `LowerToIRVMPipelineStage`. |
|
||||||
|
| G23-6.1 | `analyze` MUST terminate after `AssembleLifecycle` and MUST NOT execute backend artifact stages. | `BuilderPipelineServiceOrderTest#analyzeMustTerminateAfterLifecycleAssemblyWithoutBackendArtifacts`; `MainProjectPipelineIntegrationTest#analyzeShouldNotWriteProgramBytecode` | N/A | pass | Analyze returns an assembled executable `IRBackend` with no IRVM, bytecode, or file output. |
|
||||||
| G23-6.2 | `compile` MUST terminate after `VerifyBytecode` and MUST return a validated in-memory executable result without disk write. | `MainProjectPipelineIntegrationTest#compileShouldProduceInMemoryBytecodeWithoutWritingProgramBytecode`; `BuilderPipelinePublicSurfaceTest#compileResultMustExposeValidatedInMemoryArtifacts` | N/A | pass | Coverage asserts validated in-memory bytecode artifacts and the no-write boundary for `compile`. |
|
| G23-6.2 | `compile` MUST terminate after `VerifyBytecode` and MUST return a validated in-memory executable result without disk write. | `MainProjectPipelineIntegrationTest#compileShouldProduceInMemoryBytecodeWithoutWritingProgramBytecode`; `BuilderPipelinePublicSurfaceTest#compileResultMustExposeValidatedInMemoryArtifacts` | N/A | pass | Coverage asserts validated in-memory bytecode artifacts and the no-write boundary for `compile`. |
|
||||||
| G23-6.3 | `build` MUST extend `compile` only with terminal artifact persistence. | `MainProjectPipelineIntegrationTest#buildShouldWriteProgramBytecode`; `BuilderPipelinePublicSurfaceTest#buildResultMustExposeArtifactPathAndCompilePayload` | N/A | pass | Coverage asserts that `build` retains the compile payload and writes the filesystem artifact path expected by callers. |
|
| G23-6.3 | `build` MUST extend `compile` only with terminal artifact persistence. | `MainProjectPipelineIntegrationTest#buildShouldWriteProgramBytecode`; `BuilderPipelinePublicSurfaceTest#buildResultMustExposeArtifactPathAndCompilePayload` | N/A | pass | Coverage asserts that `build` retains the compile payload and writes the filesystem artifact path expected by callers. |
|
||||||
| G23-7.1 | `AnalysisSnapshot` MUST expose the minimum shared analysis contract. | `MainProjectPipelineIntegrationTest#analyzeShouldNotWriteProgramBytecode`; `BuilderPipelinePublicSurfaceTest#analysisSnapshotMustExposeMinimumSharedContract` | N/A | pass | Both executable and structural contract checks now cover the shared minimum analysis payload. |
|
| G23-7.1 | `AnalysisSnapshot` MUST expose the minimum shared analysis contract. | `MainProjectPipelineIntegrationTest#analyzeShouldNotWriteProgramBytecode`; `BuilderPipelinePublicSurfaceTest#analysisSnapshotMustExposeMinimumSharedContract` | N/A | pass | Both executable and structural contract checks now cover the shared minimum analysis payload. |
|
||||||
| G23-8.1 | Caller-specific configs/contexts MUST NOT redefine canonical stage semantics. | N/A | N/A | missing | Requires explicit multi-entrypoint composition tests once non-filesystem contexts are implemented. |
|
| G23-8.1 | Caller-specific configs/contexts MUST NOT redefine canonical stage semantics. | N/A | N/A | missing | Requires explicit multi-entrypoint composition tests once non-filesystem contexts are implemented. |
|
||||||
| G23-9.1 | Legacy public `run` MUST be removed as the normative entrypoint and filesystem-default behavior MUST be expressed through `build`. | `BuilderPipelinePublicSurfaceTest#publicServiceSurfaceMustExposeExplicitEntrypointsAndNoPublicRun`; `MainProjectPipelineIntegrationTest#buildShouldWriteProgramBytecode` | N/A | partial | Public service coverage now proves `run` is no longer public and `build` is the filesystem artifact path, but the CLI composition path is not yet exercised by a dedicated automated test. |
|
| G23-9.1 | Legacy public `run` MUST be removed as the normative entrypoint and filesystem-default behavior MUST be expressed through `build`. | `BuilderPipelinePublicSurfaceTest#publicServiceSurfaceMustExposeExplicitEntrypointsAndNoPublicRun`; `MainProjectPipelineIntegrationTest#buildShouldWriteProgramBytecode` | N/A | partial | Public service coverage now proves `run` is no longer public and `build` is the filesystem artifact path, but the CLI composition path is not yet exercised by a dedicated automated test. |
|
||||||
| PBS13-12.0 | PBS executable lowering MUST emit `IRBackend` satisfying frontend handoff obligations before common backend lowering. | `IRBackendExecutableContractTest` suite; `PBSFrontendPhaseServiceTest#shouldSynthesizePositiveTopic19FixtureAcrossFileInitProjectInitAndFrame` | `LowerToIRVMServiceTest#lowerMustRejectWhenSyntheticWrapperEntrypointIsMissing`; `LowerToIRVMServiceTest#lowerMustRejectWhenHiddenBootGuardIsMissing`; `LowerToIRVMServiceTest#lowerMustRejectWhenSyntheticCallableOriginIsMissing` | pass | Row group `PBS13-12.x` verifies PBS emission into the common handoff; common backend ownership is covered by G20 rows. |
|
| PBS13-12.0 | PBS executable lowering MUST emit `IRBackend` satisfying frontend handoff obligations before common backend lowering. | `IRBackendExecutableContractTest` suite; `PBSFrontendPhaseServiceTest#shouldSynthesizePositiveTopic19FixtureAcrossFileInitProjectInitAndFrame` | `LowerToIRVMServiceTest#lowerMustRejectWhenSyntheticWrapperEntrypointIsMissing`; `LowerToIRVMServiceTest#lowerMustRejectWhenHiddenBootGuardIsMissing`; `LowerToIRVMServiceTest#lowerMustRejectWhenSyntheticCallableOriginIsMissing` | pass | Row group `PBS13-12.x` verifies PBS emission into the common handoff; common backend ownership is covered by G20 rows. |
|
||||||
|
| PBS13-12.7 | PBS executable lowering MUST emit a typed lifecycle declaration and MUST NOT assemble derived wrappers. | `PBSFrontendPhaseServiceTest#shouldSynthesizePositiveTopic19FixtureAcrossFileInitProjectInitAndFrame`; `PbsFrontendCompilerTest#shouldExposeGlobalsAndTypedLifecycleDeclarationInBackendFile` | N/A | pass | Frontend tests assert declaration roles and the absence of module init, published wrapper, and boot guard. |
|
||||||
|
| PBS13-12.8 | Project init MUST be excluded from file-init fragments and MUST execute exactly once after module initializers. | `PbsFrontendCompilerTest#shouldExposeGlobalsAndTypedLifecycleDeclarationInBackendFile`; `LifecycleAssemblerServiceTest#assembleMustInvokeProjectInitExactlyOnceAndExcludeItFromFileInit` | N/A | pass | Colocated `[Init]` is declared as project init and invoked once by the assembled wrapper. |
|
||||||
|
| PBS13-12.9 | Lifecycle module order MUST propagate resolved dependency order without name or `FileId` inference. | `PBSFrontendPhaseServiceTest#shouldPublishDependencyOrderedLifecycleModulesInsteadOfNameOrFileIdOrder`; `IRBackendExecutableContractTest#aggregatorMustRemapTypedLifecycleReferencesAndPreserveDeclaredModuleOrder` | N/A | pass | Adversarial module names and file registration order do not change declared dependency order. |
|
||||||
| PBS13-12.1.1 | Callable identity MUST be preserved at handoff. | `IRBackendExecutableContractTest#aggregatorMustPreserveExecutableFunctionOrderDeterministically` | N/A | pass | |
|
| PBS13-12.1.1 | Callable identity MUST be preserved at handoff. | `IRBackendExecutableContractTest#aggregatorMustPreserveExecutableFunctionOrderDeterministically` | N/A | pass | |
|
||||||
| PBS13-12.1.2 | Observable callable signature MUST be preserved at handoff. | `IRBackendExecutableContractTest#functionContractMustRejectInvalidSlotAndSpanBounds` | N/A | pass | |
|
| PBS13-12.1.2 | Observable callable signature MUST be preserved at handoff. | `IRBackendExecutableContractTest#functionContractMustRejectInvalidSlotAndSpanBounds` | N/A | pass | |
|
||||||
| PBS13-12.1.3 | Callable category MUST be preserved at handoff. | `PbsFrontendCompilerTest#shouldLowerExecutableFunctionsWithCallsiteCategories` | N/A | pass | |
|
| PBS13-12.1.3 | Callable category MUST be preserved at handoff. | `PbsFrontendCompilerTest#shouldLowerExecutableFunctionsWithCallsiteCategories` | N/A | pass | |
|
||||||
|
|||||||
@ -65,12 +65,13 @@ The canonical stage order is:
|
|||||||
1. `ResolveDepsPipelineStage`
|
1. `ResolveDepsPipelineStage`
|
||||||
2. `LoadSourcesPipelineStage`
|
2. `LoadSourcesPipelineStage`
|
||||||
3. `FrontendPhasePipelineStage`
|
3. `FrontendPhasePipelineStage`
|
||||||
4. `LowerToIRVMPipelineStage`
|
4. `AssembleLifecyclePipelineStage`
|
||||||
5. `OptimizeIRVMPipelineStage`
|
5. `LowerToIRVMPipelineStage`
|
||||||
6. `EmitBytecodePipelineStage`
|
6. `OptimizeIRVMPipelineStage`
|
||||||
7. `LinkBytecodePipelineStage`
|
7. `EmitBytecodePipelineStage`
|
||||||
8. `VerifyBytecodePipelineStage`
|
8. `LinkBytecodePipelineStage`
|
||||||
9. `WriteBytecodeArtifactPipelineStage`
|
9. `VerifyBytecodePipelineStage`
|
||||||
|
10. `WriteBytecodeArtifactPipelineStage`
|
||||||
|
|
||||||
Public entrypoints MAY terminate early according to this document, but they MUST NOT:
|
Public entrypoints MAY terminate early according to this document, but they MUST NOT:
|
||||||
|
|
||||||
@ -82,21 +83,23 @@ Public entrypoints MAY terminate early according to this document, but they MUST
|
|||||||
|
|
||||||
### 6.1 `analyze`
|
### 6.1 `analyze`
|
||||||
|
|
||||||
`analyze` MUST terminate at `FrontendPhasePipelineStage`.
|
`analyze` MUST terminate at `AssembleLifecyclePipelineStage`.
|
||||||
|
|
||||||
`analyze` is defined as:
|
`analyze` is defined as:
|
||||||
|
|
||||||
1. `ResolveDeps`
|
1. `ResolveDeps`
|
||||||
2. `LoadSources`
|
2. `LoadSources`
|
||||||
3. `FrontendPhase`
|
3. `FrontendPhase`
|
||||||
|
4. `AssembleLifecycle`
|
||||||
|
|
||||||
`analyze` MUST:
|
`analyze` MUST:
|
||||||
|
|
||||||
1. resolve workspace/dependency inputs needed by the frontend,
|
1. resolve workspace/dependency inputs needed by the frontend,
|
||||||
2. load source surfaces admitted by the selected compiler configuration,
|
2. load source surfaces admitted by the selected compiler configuration,
|
||||||
3. run frontend semantic analysis,
|
3. run frontend semantic analysis and emit a declarative `IRBackend`,
|
||||||
4. return an `AnalysisSnapshot` or equivalent result contract,
|
4. assemble the common lifecycle from `IRLifecycleDeclaration` when present,
|
||||||
5. and remain free of backend artifact side effects.
|
5. return an `AnalysisSnapshot` or equivalent result contract whose `IRBackend` is fully assembled when the handoff is executable,
|
||||||
|
6. and remain free of backend artifact side effects.
|
||||||
|
|
||||||
`analyze` MUST NOT:
|
`analyze` MUST NOT:
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ Scope boundary:
|
|||||||
- PROMETEU itself is a fantasy handheld / fantasy console with a broader machine model, firmware model, cartridge model, and virtual hardware surface.
|
- PROMETEU itself is a fantasy handheld / fantasy console with a broader machine model, firmware model, cartridge model, and virtual hardware surface.
|
||||||
- This document does not define the whole PROMETEU machine.
|
- This document does not define the whole PROMETEU machine.
|
||||||
- This document defines the VM/runtime subsystem that executes bytecode inside that machine.
|
- This document defines the VM/runtime subsystem that executes bytecode inside that machine.
|
||||||
- For broader machine-level framing, see [`../specs/README.md`](../specs/README.md).
|
- For broader machine-level framing, see [`../specs/README.md`](../specs/runtime/README.md).
|
||||||
|
|
||||||
Document roles:
|
Document roles:
|
||||||
|
|
||||||
@ -149,6 +149,7 @@ The verifier statically checks bytecode for structural safety and stack‑shape
|
|||||||
- `SyscallMeta` defines expected arity and return slot counts. The loader resolves `HOSTCALL` against this metadata and rejects raw `SYSCALL` in PBX pre-load artifacts; the verifier checks final IDs/arity/return‑slot counts against the same metadata.
|
- `SyscallMeta` defines expected arity and return slot counts. The loader resolves `HOSTCALL` against this metadata and rejects raw `SYSCALL` in PBX pre-load artifacts; the verifier checks final IDs/arity/return‑slot counts against the same metadata.
|
||||||
- Arguments and returns
|
- Arguments and returns
|
||||||
- Arguments are taken from the operand stack in the order defined by the ABI. Returns use multi‑slot results via a host‑side return buffer (`HostReturn`) which the VM copies back onto the stack, or zero slots for “void”. A mismatch in result counts is a fault/panic per current hardening logic.
|
- Arguments are taken from the operand stack in the order defined by the ABI. Returns use multi‑slot results via a host‑side return buffer (`HostReturn`) which the VM copies back onto the stack, or zero slots for “void”. A mismatch in result counts is a fault/panic per current hardening logic.
|
||||||
|
- Example: the canonical asset runtime load surface is `asset.load(asset_id, slot) -> (status, handle)`. The caller does not supply `asset_name` or `asset_type`; bank kind is derived from `asset_table` using `asset_id`.
|
||||||
- Capabilities
|
- Capabilities
|
||||||
- Cartridge capability flags are applied before load-time host resolution. Missing required capability aborts load; invoking a syscall without the required capability also traps defensively at runtime.
|
- Cartridge capability flags are applied before load-time host resolution. Missing required capability aborts load; invoking a syscall without the required capability also traps defensively at runtime.
|
||||||
|
|
||||||
|
|||||||
@ -78,6 +78,17 @@ Authority rule:
|
|||||||
- `INTRINSIC u32` — final numeric VM-owned intrinsic call.
|
- `INTRINSIC u32` — final numeric VM-owned intrinsic call.
|
||||||
- `FRAME_SYNC` — yield until the next frame boundary (e.g., vblank); explicit safepoint.
|
- `FRAME_SYNC` — yield until the next frame boundary (e.g., vblank); explicit safepoint.
|
||||||
|
|
||||||
|
Host service arity is not encoded in the opcode itself. It is defined by resolved syscall metadata.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- `asset.load` currently resolves with `arg_slots = 2` and `ret_slots = 2`.
|
||||||
|
- The canonical stack contract is `asset_id, slot -> status, handle`.
|
||||||
|
- Callers do not provide an explicit asset kind; the runtime derives it from `asset_table`.
|
||||||
|
- `composer.bind_scene` resolves with `arg_slots = 1` and `ret_slots = 1`.
|
||||||
|
- The canonical stack contract is `bank_id -> status`.
|
||||||
|
- `composer.emit_sprite` resolves with `arg_slots = 9` and `ret_slots = 1`.
|
||||||
|
|
||||||
#### Canonical Intrinsic Registry Artifact
|
#### Canonical Intrinsic Registry Artifact
|
||||||
|
|
||||||
- Final intrinsic IDs and intrinsic stack metadata are published in [`INTRINSICS.csv`](INTRINSICS.csv).
|
- Final intrinsic IDs and intrinsic stack metadata are published in [`INTRINSICS.csv`](INTRINSICS.csv).
|
||||||
|
|||||||
32
docs/vm-arch/README.md
Normal file
32
docs/vm-arch/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# VM Architecture Mirror Policy
|
||||||
|
|
||||||
|
The Prometeu runtime repository is the normative authority for PBX binary
|
||||||
|
layout, Core ISA, loader behavior, verification, and PVM execution semantics.
|
||||||
|
The files in this directory are convenience mirrors for Studio-side compiler
|
||||||
|
and integration work; they are not an independent architectural authority.
|
||||||
|
|
||||||
|
Canonical runtime sources:
|
||||||
|
|
||||||
|
- `../runtime/docs/vm-arch/ARCHITECTURE.md`
|
||||||
|
- `../runtime/docs/vm-arch/ISA_CORE.md`
|
||||||
|
- `../runtime/docs/vm-arch/INTRINSICS.csv`
|
||||||
|
|
||||||
|
When these mirrors are retained, an integration change that updates a canonical
|
||||||
|
runtime source MUST update the corresponding Studio mirror in the same logical
|
||||||
|
change. Verify synchronization from the Studio repository root with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
diff -u docs/vm-arch/ARCHITECTURE.md ../runtime/docs/vm-arch/ARCHITECTURE.md
|
||||||
|
diff -u docs/vm-arch/ISA_CORE.md ../runtime/docs/vm-arch/ISA_CORE.md
|
||||||
|
diff -u docs/vm-arch/INTRINSICS.csv ../runtime/docs/vm-arch/INTRINSICS.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
All three commands MUST exit successfully with no output before a change claims
|
||||||
|
that the mirrors are synchronized. If the runtime repository is not available
|
||||||
|
in the same checkout, synchronization evidence must be produced against the
|
||||||
|
canonical runtime revision before merging.
|
||||||
|
|
||||||
|
Compiler-owned transformation contracts remain under `docs/specs/compiler`.
|
||||||
|
Those specifications define `IRBackend -> IRVM -> PBX` emission, but they MUST
|
||||||
|
conform to the runtime-owned PBX and ISA contracts and MUST NOT redefine runtime
|
||||||
|
execution semantics.
|
||||||
@ -8,6 +8,9 @@ import p.studio.compiler.models.IRFunction;
|
|||||||
import p.studio.compiler.models.IRGlobal;
|
import p.studio.compiler.models.IRGlobal;
|
||||||
import p.studio.compiler.models.IRGlobalOrigin;
|
import p.studio.compiler.models.IRGlobalOrigin;
|
||||||
import p.studio.compiler.models.IRGlobalVisibility;
|
import p.studio.compiler.models.IRGlobalVisibility;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableReference;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableRole;
|
||||||
|
import p.studio.compiler.models.IRLifecycleFileDeclaration;
|
||||||
import p.studio.compiler.models.IRReservedMetadata;
|
import p.studio.compiler.models.IRReservedMetadata;
|
||||||
import p.studio.compiler.models.IRSyntheticCallableKind;
|
import p.studio.compiler.models.IRSyntheticCallableKind;
|
||||||
import p.studio.compiler.models.IRSyntheticFunction;
|
import p.studio.compiler.models.IRSyntheticFunction;
|
||||||
@ -215,6 +218,12 @@ public final class PbsFrontendCompiler {
|
|||||||
if (fileInitExecutable != null) {
|
if (fileInitExecutable != null) {
|
||||||
executableFunctions.add(fileInitExecutable);
|
executableFunctions.add(fileInitExecutable);
|
||||||
}
|
}
|
||||||
|
final var lifecycleDeclaration = lowerLifecycleDeclaration(
|
||||||
|
fileId,
|
||||||
|
effectiveModuleId,
|
||||||
|
lifecycleArtifacts,
|
||||||
|
fileInitExecutable,
|
||||||
|
executableFunctions);
|
||||||
return new IRBackendFile(
|
return new IRBackendFile(
|
||||||
fileId,
|
fileId,
|
||||||
functions,
|
functions,
|
||||||
@ -224,7 +233,8 @@ public final class PbsFrontendCompiler {
|
|||||||
reservedMetadata,
|
reservedMetadata,
|
||||||
effectiveModulePool,
|
effectiveModulePool,
|
||||||
ReadOnlyList.wrap(callableSignatures),
|
ReadOnlyList.wrap(callableSignatures),
|
||||||
executableLowering.intrinsicPool());
|
executableLowering.intrinsicPool(),
|
||||||
|
lifecycleDeclaration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String callableShapeSurfaceOf(final PbsAst.FunctionDecl functionDecl) {
|
public String callableShapeSurfaceOf(final PbsAst.FunctionDecl functionDecl) {
|
||||||
@ -306,33 +316,19 @@ public final class PbsFrontendCompiler {
|
|||||||
final var frameDecl = lifecycleArtifacts.frameDecl();
|
final var frameDecl = lifecycleArtifacts.frameDecl();
|
||||||
final var firstGlobalDecl = lifecycleArtifacts.firstGlobalDecl();
|
final var firstGlobalDecl = lifecycleArtifacts.firstGlobalDecl();
|
||||||
|
|
||||||
final var fileInitAnchorSpan = initDecl != null ? initDecl.span() : firstGlobalDecl == null ? null : firstGlobalDecl.span();
|
final var ordinaryFileInit = frameDecl == null ? initDecl : null;
|
||||||
final var fileInitAnchorName = initDecl != null ? initDecl.name() : firstGlobalDecl == null ? "" : firstGlobalDecl.name();
|
final var fileInitAnchorSpan = ordinaryFileInit != null
|
||||||
|
? ordinaryFileInit.span()
|
||||||
|
: firstGlobalDecl == null ? null : firstGlobalDecl.span();
|
||||||
|
final var fileInitAnchorName = ordinaryFileInit != null
|
||||||
|
? ordinaryFileInit.name()
|
||||||
|
: firstGlobalDecl == null ? "" : firstGlobalDecl.name();
|
||||||
if (fileInitAnchorSpan != null) {
|
if (fileInitAnchorSpan != null) {
|
||||||
syntheticFunctions.add(new IRSyntheticFunction(
|
syntheticFunctions.add(new IRSyntheticFunction(
|
||||||
moduleId,
|
moduleId,
|
||||||
fileInitCallableName(fileId),
|
fileInitCallableName(fileId),
|
||||||
IRSyntheticCallableKind.FILE_INIT_FRAGMENT,
|
IRSyntheticCallableKind.FILE_INIT_FRAGMENT,
|
||||||
new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan)));
|
new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan)));
|
||||||
syntheticFunctions.add(new IRSyntheticFunction(
|
|
||||||
moduleId,
|
|
||||||
moduleInitCallableName(moduleId),
|
|
||||||
IRSyntheticCallableKind.MODULE_INIT,
|
|
||||||
new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan)));
|
|
||||||
}
|
|
||||||
if (initDecl != null && frameDecl != null) {
|
|
||||||
syntheticFunctions.add(new IRSyntheticFunction(
|
|
||||||
moduleId,
|
|
||||||
projectInitCallableName(moduleId),
|
|
||||||
IRSyntheticCallableKind.PROJECT_INIT,
|
|
||||||
new IRSyntheticOrigin(fileId, initDecl.name(), initDecl.span())));
|
|
||||||
}
|
|
||||||
if (frameDecl != null) {
|
|
||||||
syntheticFunctions.add(new IRSyntheticFunction(
|
|
||||||
moduleId,
|
|
||||||
frameWrapperCallableName(moduleId),
|
|
||||||
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
|
||||||
new IRSyntheticOrigin(fileId, frameDecl.name(), frameDecl.span())));
|
|
||||||
}
|
}
|
||||||
return ReadOnlyList.wrap(syntheticFunctions);
|
return ReadOnlyList.wrap(syntheticFunctions);
|
||||||
}
|
}
|
||||||
@ -343,8 +339,10 @@ public final class PbsFrontendCompiler {
|
|||||||
final PbsLifecycleArtifacts lifecycleArtifacts,
|
final PbsLifecycleArtifacts lifecycleArtifacts,
|
||||||
final ArrayList<IRBackendExecutableFunction> executableFunctions,
|
final ArrayList<IRBackendExecutableFunction> executableFunctions,
|
||||||
final ArrayList<CallableSignatureRef> callableSignatures) {
|
final ArrayList<CallableSignatureRef> callableSignatures) {
|
||||||
if (lifecycleArtifacts.firstGlobalDecl() == null
|
final var ordinaryFileInit = lifecycleArtifacts.frameDecl() == null
|
||||||
&& lifecycleArtifacts.initDecl() == null) {
|
? lifecycleArtifacts.initDecl()
|
||||||
|
: null;
|
||||||
|
if (lifecycleArtifacts.firstGlobalDecl() == null && ordinaryFileInit == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final var syntheticCallableName = fileInitCallableName(fileId);
|
final var syntheticCallableName = fileInitCallableName(fileId);
|
||||||
@ -352,8 +350,8 @@ public final class PbsFrontendCompiler {
|
|||||||
callableSignatures.add(new CallableSignatureRef(moduleId, syntheticCallableName, 0, "() -> unit"));
|
callableSignatures.add(new CallableSignatureRef(moduleId, syntheticCallableName, 0, "() -> unit"));
|
||||||
|
|
||||||
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
|
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
|
||||||
final var moduleInitSource = lifecycleArtifacts.initDecl();
|
final var moduleInitSource = ordinaryFileInit;
|
||||||
if (moduleInitSource != null && moduleInitSource != lifecycleArtifacts.frameDecl()) {
|
if (moduleInitSource != null) {
|
||||||
final var initExecutable = resolveLifecycleExecutable(executableFunctions, moduleId, moduleInitSource.name());
|
final var initExecutable = resolveLifecycleExecutable(executableFunctions, moduleId, moduleInitSource.name());
|
||||||
if (initExecutable != null) {
|
if (initExecutable != null) {
|
||||||
instructions.add(new IRBackendExecutableFunction.Instruction(
|
instructions.add(new IRBackendExecutableFunction.Instruction(
|
||||||
@ -390,6 +388,58 @@ public final class PbsFrontendCompiler {
|
|||||||
span);
|
span);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IRLifecycleFileDeclaration lowerLifecycleDeclaration(
|
||||||
|
final FileId fileId,
|
||||||
|
final ModuleId moduleId,
|
||||||
|
final PbsLifecycleArtifacts lifecycleArtifacts,
|
||||||
|
final IRBackendExecutableFunction fileInitExecutable,
|
||||||
|
final ArrayList<IRBackendExecutableFunction> executableFunctions) {
|
||||||
|
final var fileInitReference = fileInitExecutable == null
|
||||||
|
? null
|
||||||
|
: new IRLifecycleCallableReference(
|
||||||
|
moduleId,
|
||||||
|
fileId,
|
||||||
|
fileInitExecutable.callableId(),
|
||||||
|
IRLifecycleCallableRole.FILE_INIT_FRAGMENT,
|
||||||
|
fileInitExecutable.span());
|
||||||
|
|
||||||
|
final var projectInitSource = lifecycleArtifacts.frameDecl() == null
|
||||||
|
? null
|
||||||
|
: lifecycleArtifacts.initDecl();
|
||||||
|
final var projectInitExecutable = projectInitSource == null
|
||||||
|
? null
|
||||||
|
: resolveLifecycleExecutable(executableFunctions, moduleId, projectInitSource.name());
|
||||||
|
final var projectInitReference = projectInitExecutable == null
|
||||||
|
? null
|
||||||
|
: new IRLifecycleCallableReference(
|
||||||
|
moduleId,
|
||||||
|
fileId,
|
||||||
|
projectInitExecutable.callableId(),
|
||||||
|
IRLifecycleCallableRole.PROJECT_INIT,
|
||||||
|
projectInitSource.span());
|
||||||
|
|
||||||
|
final var frameSource = lifecycleArtifacts.frameDecl();
|
||||||
|
final var frameExecutable = frameSource == null
|
||||||
|
? null
|
||||||
|
: resolveLifecycleExecutable(executableFunctions, moduleId, frameSource.name());
|
||||||
|
final var frameReference = frameExecutable == null
|
||||||
|
? null
|
||||||
|
: new IRLifecycleCallableReference(
|
||||||
|
moduleId,
|
||||||
|
fileId,
|
||||||
|
frameExecutable.callableId(),
|
||||||
|
IRLifecycleCallableRole.FRAME_ROOT,
|
||||||
|
frameSource.span());
|
||||||
|
|
||||||
|
final var declaration = new IRLifecycleFileDeclaration(
|
||||||
|
moduleId,
|
||||||
|
fileId,
|
||||||
|
fileInitReference,
|
||||||
|
projectInitReference,
|
||||||
|
frameReference);
|
||||||
|
return declaration.isEmpty() ? null : declaration;
|
||||||
|
}
|
||||||
|
|
||||||
private IRBackendExecutableFunction resolveLifecycleExecutable(
|
private IRBackendExecutableFunction resolveLifecycleExecutable(
|
||||||
final ArrayList<IRBackendExecutableFunction> executableFunctions,
|
final ArrayList<IRBackendExecutableFunction> executableFunctions,
|
||||||
final ModuleId moduleId,
|
final ModuleId moduleId,
|
||||||
@ -443,27 +493,6 @@ public final class PbsFrontendCompiler {
|
|||||||
return "__pbs.file_init$f" + fileId.getId();
|
return "__pbs.file_init$f" + fileId.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String moduleInitCallableName(final ModuleId moduleId) {
|
|
||||||
return "__pbs.module_init$m" + normalizedModuleIndex(moduleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String projectInitCallableName(final ModuleId moduleId) {
|
|
||||||
return "__pbs.project_init$m" + normalizedModuleIndex(moduleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String frameWrapperCallableName(final ModuleId moduleId) {
|
|
||||||
return "__pbs.frame_wrapper$m" + normalizedModuleIndex(moduleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String bootGuardGlobalName(final ModuleId moduleId) {
|
|
||||||
return "__pbs.boot_guard$m" + normalizedModuleIndex(moduleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int normalizedModuleIndex(final ModuleId moduleId) {
|
|
||||||
final var normalized = moduleId == null ? ModuleId.none() : moduleId;
|
|
||||||
return normalized.isNone() ? -1 : normalized.getIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int safeToInt(final long value) {
|
private int safeToInt(final long value) {
|
||||||
if (value > Integer.MAX_VALUE) {
|
if (value > Integer.MAX_VALUE) {
|
||||||
return Integer.MAX_VALUE;
|
return Integer.MAX_VALUE;
|
||||||
|
|||||||
@ -5,24 +5,15 @@ import p.studio.compiler.messages.BuildingIssueSink;
|
|||||||
import p.studio.compiler.messages.FESurfaceContext;
|
import p.studio.compiler.messages.FESurfaceContext;
|
||||||
import p.studio.compiler.messages.FrontendPhaseContext;
|
import p.studio.compiler.messages.FrontendPhaseContext;
|
||||||
import p.studio.compiler.models.IRBackend;
|
import p.studio.compiler.models.IRBackend;
|
||||||
import p.studio.compiler.models.IRBackendExecutableFunction;
|
|
||||||
import p.studio.compiler.models.IRGlobal;
|
|
||||||
import p.studio.compiler.models.IRGlobalOrigin;
|
|
||||||
import p.studio.compiler.models.IRGlobalVisibility;
|
|
||||||
import p.studio.compiler.models.IRHiddenGlobalKind;
|
|
||||||
import p.studio.compiler.models.IRSyntheticCallableKind;
|
|
||||||
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
import p.studio.compiler.pbs.PbsReservedMetadataExtractor;
|
import p.studio.compiler.pbs.PbsReservedMetadataExtractor;
|
||||||
import p.studio.compiler.pbs.semantics.PbsFlowSemanticsValidator;
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticsValidator;
|
||||||
import p.studio.compiler.pbs.stdlib.InterfaceModuleLoader;
|
import p.studio.compiler.pbs.stdlib.InterfaceModuleLoader;
|
||||||
import p.studio.compiler.pbs.stdlib.ResourceStdlibEnvironmentResolver;
|
import p.studio.compiler.pbs.stdlib.ResourceStdlibEnvironmentResolver;
|
||||||
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
|
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
|
||||||
import p.studio.compiler.source.Span;
|
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.identifiers.CallableId;
|
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
import p.studio.compiler.source.identifiers.ModuleId;
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
import p.studio.compiler.source.tables.CallableSignatureRef;
|
|
||||||
import p.studio.utilities.logs.LogAggregator;
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
@ -168,7 +159,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
private IRBackend mergeCompiledSources(
|
private IRBackend mergeCompiledSources(
|
||||||
final ArrayList<CompiledSourceFile> compiledSourceFiles,
|
final ArrayList<CompiledSourceFile> compiledSourceFiles,
|
||||||
final Set<ModuleId> failedModuleIds,
|
final Set<ModuleId> failedModuleIds,
|
||||||
final Map<ModuleId, Set<ModuleId>> moduleDependencyGraph) {
|
final Map<ModuleId, ReadOnlyList<ModuleId>> moduleDependencyGraph) {
|
||||||
final var irBackendAggregator = IRBackend.aggregator();
|
final var irBackendAggregator = IRBackend.aggregator();
|
||||||
final var blockedModuleIds = blockedModulesByDependency(failedModuleIds, moduleDependencyGraph);
|
final var blockedModuleIds = blockedModulesByDependency(failedModuleIds, moduleDependencyGraph);
|
||||||
|
|
||||||
@ -178,438 +169,70 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
}
|
}
|
||||||
irBackendAggregator.merge(compiledSource.irBackendFile());
|
irBackendAggregator.merge(compiledSource.irBackendFile());
|
||||||
}
|
}
|
||||||
return synthesizePublishedLifecycle(irBackendAggregator.emit());
|
irBackendAggregator.lifecycleModuleOrder(lifecycleModuleOrder(
|
||||||
|
compiledSourceFiles,
|
||||||
|
blockedModuleIds,
|
||||||
|
moduleDependencyGraph));
|
||||||
|
return irBackendAggregator.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRBackend synthesizePublishedLifecycle(final IRBackend baseBackend) {
|
private ReadOnlyList<ModuleId> lifecycleModuleOrder(
|
||||||
final var callableSignatures = new ArrayList<>(baseBackend.getCallableSignatures().asList());
|
final ArrayList<CompiledSourceFile> compiledSourceFiles,
|
||||||
final var executableFunctions = new ArrayList<>(baseBackend.getExecutableFunctions().asList());
|
final Set<ModuleId> blockedModuleIds,
|
||||||
final var globals = new ArrayList<>(baseBackend.getGlobals().asList());
|
final Map<ModuleId, ReadOnlyList<ModuleId>> dependenciesByModule) {
|
||||||
final var fileInitFunctionsByModule = new LinkedHashMap<ModuleId, List<IRBackendExecutableFunction>>();
|
final var availableModules = new LinkedHashSet<ModuleId>();
|
||||||
final var projectInitAnchorsByModule = new LinkedHashMap<ModuleId, String>();
|
for (final var compiledSource : compiledSourceFiles) {
|
||||||
final var wrapperNamesByModule = new LinkedHashMap<ModuleId, String>();
|
if (!blockedModuleIds.contains(compiledSource.moduleId())) {
|
||||||
final var frameAnchorsByModule = new LinkedHashMap<ModuleId, String>();
|
availableModules.add(compiledSource.moduleId());
|
||||||
|
|
||||||
for (final var executable : executableFunctions) {
|
|
||||||
if (!executable.callableName().startsWith("__pbs.file_init$")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
fileInitFunctionsByModule
|
|
||||||
.computeIfAbsent(normalizeModuleId(executable.moduleId()), ignored -> new ArrayList<>())
|
|
||||||
.add(executable);
|
|
||||||
}
|
|
||||||
for (final var syntheticFunction : baseBackend.getSyntheticFunctions()) {
|
|
||||||
final var moduleId = normalizeModuleId(syntheticFunction.moduleId());
|
|
||||||
switch (syntheticFunction.kind()) {
|
|
||||||
case PROJECT_INIT -> projectInitAnchorsByModule.put(moduleId, syntheticFunction.origin().anchorCallableName());
|
|
||||||
case PUBLISHED_FRAME_WRAPPER -> {
|
|
||||||
wrapperNamesByModule.put(moduleId, syntheticFunction.callableName());
|
|
||||||
frameAnchorsByModule.put(moduleId, syntheticFunction.origin().anchorCallableName());
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ModuleId entryPointModuleId;
|
final var ordered = new ArrayList<ModuleId>(availableModules.size());
|
||||||
final String entryPointCallableName;
|
final var visiting = new HashSet<ModuleId>();
|
||||||
if (wrapperNamesByModule.size() == 1) {
|
final var visited = new HashSet<ModuleId>();
|
||||||
entryPointModuleId = wrapperNamesByModule.keySet().iterator().next();
|
for (final var moduleId : availableModules) {
|
||||||
entryPointCallableName = wrapperNamesByModule.get(entryPointModuleId);
|
appendDependenciesFirst(
|
||||||
} else {
|
|
||||||
entryPointModuleId = baseBackend.getEntryPointModuleId();
|
|
||||||
entryPointCallableName = baseBackend.getEntryPointCallableName();
|
|
||||||
}
|
|
||||||
|
|
||||||
final var sortedModuleIds = new ArrayList<ModuleId>();
|
|
||||||
sortedModuleIds.addAll(fileInitFunctionsByModule.keySet());
|
|
||||||
for (final var moduleId : wrapperNamesByModule.keySet()) {
|
|
||||||
if (!sortedModuleIds.contains(moduleId)) {
|
|
||||||
sortedModuleIds.add(moduleId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortedModuleIds.sort(Comparator.comparing(moduleId -> moduleSortKey(baseBackend, moduleId)));
|
|
||||||
|
|
||||||
final var moduleInitCallableIds = new LinkedHashMap<ModuleId, CallableId>();
|
|
||||||
for (final var moduleId : sortedModuleIds) {
|
|
||||||
final var moduleInitName = PbsFrontendCompiler.moduleInitCallableName(moduleId);
|
|
||||||
final var fileInitFunctions = new ArrayList<>(fileInitFunctionsByModule.getOrDefault(moduleId, List.of()));
|
|
||||||
if (fileInitFunctions.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
fileInitFunctions.sort(Comparator.comparingInt(fn -> fn.fileId().getId()));
|
|
||||||
|
|
||||||
final var callableId = new CallableId(callableSignatures.size());
|
|
||||||
callableSignatures.add(new CallableSignatureRef(moduleId, moduleInitName, 0, "() -> unit"));
|
|
||||||
moduleInitCallableIds.put(moduleId, callableId);
|
|
||||||
|
|
||||||
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
|
|
||||||
var anchorSpan = fileInitFunctions.getFirst().span();
|
|
||||||
var anchorFileId = fileInitFunctions.getFirst().fileId();
|
|
||||||
for (final var fileInitFunction : fileInitFunctions) {
|
|
||||||
instructions.add(callInstruction(fileInitFunction, fileInitFunction.span()));
|
|
||||||
anchorSpan = fileInitFunction.span();
|
|
||||||
anchorFileId = fileInitFunction.fileId();
|
|
||||||
}
|
|
||||||
instructions.add(retInstruction(anchorSpan));
|
|
||||||
executableFunctions.add(new IRBackendExecutableFunction(
|
|
||||||
anchorFileId,
|
|
||||||
moduleId,
|
moduleId,
|
||||||
moduleInitName,
|
availableModules,
|
||||||
callableId,
|
dependenciesByModule,
|
||||||
safeToInt(anchorSpan.getStart()),
|
visiting,
|
||||||
safeToInt(anchorSpan.getEnd()),
|
visited,
|
||||||
0,
|
ordered);
|
||||||
0,
|
}
|
||||||
0,
|
return ReadOnlyList.wrap(ordered);
|
||||||
1,
|
|
||||||
ReadOnlyList.wrap(instructions),
|
|
||||||
anchorSpan));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CallableId projectInitCallableId = null;
|
private void appendDependenciesFirst(
|
||||||
if (!entryPointModuleId.isNone()) {
|
|
||||||
final var projectInitAnchor = projectInitAnchorsByModule.get(entryPointModuleId);
|
|
||||||
final var projectInitTarget = resolveExecutable(executableFunctions, entryPointModuleId, projectInitAnchor);
|
|
||||||
if (projectInitTarget != null) {
|
|
||||||
final var projectInitName = PbsFrontendCompiler.projectInitCallableName(entryPointModuleId);
|
|
||||||
projectInitCallableId = new CallableId(callableSignatures.size());
|
|
||||||
callableSignatures.add(new CallableSignatureRef(entryPointModuleId, projectInitName, 0, "() -> unit"));
|
|
||||||
executableFunctions.add(new IRBackendExecutableFunction(
|
|
||||||
projectInitTarget.fileId(),
|
|
||||||
entryPointModuleId,
|
|
||||||
projectInitName,
|
|
||||||
projectInitCallableId,
|
|
||||||
projectInitTarget.sourceStart(),
|
|
||||||
projectInitTarget.sourceEnd(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
ReadOnlyList.from(
|
|
||||||
callInstruction(projectInitTarget, projectInitTarget.span()),
|
|
||||||
retInstruction(projectInitTarget.span())),
|
|
||||||
projectInitTarget.span()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final var frameExecutable = resolveExecutable(
|
|
||||||
executableFunctions,
|
|
||||||
entryPointModuleId,
|
|
||||||
frameAnchorsByModule.get(entryPointModuleId));
|
|
||||||
if (frameExecutable != null
|
|
||||||
&& entryPointCallableName != null
|
|
||||||
&& !entryPointCallableName.isBlank()
|
|
||||||
&& !entryPointModuleId.isNone()) {
|
|
||||||
final var existingBootGuard = globals.stream()
|
|
||||||
.filter(global -> moduleIdsMatch(entryPointModuleId, global.moduleId()))
|
|
||||||
.filter(global -> global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
final var bootGuardSlot = existingBootGuard != null
|
|
||||||
? existingBootGuard.slot()
|
|
||||||
: nextGlobalSlot(globals, entryPointModuleId);
|
|
||||||
final var wrapperCallableId = new CallableId(callableSignatures.size());
|
|
||||||
callableSignatures.add(new CallableSignatureRef(entryPointModuleId, entryPointCallableName, 0, "() -> unit"));
|
|
||||||
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
|
|
||||||
instructions.add(getGlobalInstruction(bootGuardSlot, frameExecutable.span()));
|
|
||||||
instructions.add(pushBoolInstruction(true, frameExecutable.span()));
|
|
||||||
instructions.add(eqInstruction(frameExecutable.span()));
|
|
||||||
instructions.add(jumpIfTrueInstruction("bootstrap_done", frameExecutable.span()));
|
|
||||||
for (final var moduleId : sortedModuleIds) {
|
|
||||||
final var moduleInitCallableId = moduleInitCallableIds.get(moduleId);
|
|
||||||
if (moduleInitCallableId == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
instructions.add(new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
|
||||||
moduleId,
|
|
||||||
PbsFrontendCompiler.moduleInitCallableName(moduleId),
|
|
||||||
moduleInitCallableId,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
frameExecutable.span()));
|
|
||||||
}
|
|
||||||
if (projectInitCallableId != null) {
|
|
||||||
instructions.add(new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
|
||||||
entryPointModuleId,
|
|
||||||
PbsFrontendCompiler.projectInitCallableName(entryPointModuleId),
|
|
||||||
projectInitCallableId,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
frameExecutable.span()));
|
|
||||||
}
|
|
||||||
instructions.add(pushBoolInstruction(true, frameExecutable.span()));
|
|
||||||
instructions.add(setGlobalInstruction(bootGuardSlot, frameExecutable.span()));
|
|
||||||
instructions.add(labelInstruction("bootstrap_done", frameExecutable.span()));
|
|
||||||
instructions.add(callInstruction(frameExecutable, frameExecutable.span()));
|
|
||||||
instructions.add(retInstruction(frameExecutable.span()));
|
|
||||||
executableFunctions.add(new IRBackendExecutableFunction(
|
|
||||||
frameExecutable.fileId(),
|
|
||||||
entryPointModuleId,
|
|
||||||
entryPointCallableName,
|
|
||||||
wrapperCallableId,
|
|
||||||
frameExecutable.sourceStart(),
|
|
||||||
frameExecutable.sourceEnd(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
ReadOnlyList.wrap(instructions),
|
|
||||||
frameExecutable.span()));
|
|
||||||
|
|
||||||
if (existingBootGuard == null) {
|
|
||||||
globals.add(new IRGlobal(
|
|
||||||
frameExecutable.fileId(),
|
|
||||||
entryPointModuleId,
|
|
||||||
PbsFrontendCompiler.bootGuardGlobalName(entryPointModuleId),
|
|
||||||
"bool",
|
|
||||||
bootGuardSlot,
|
|
||||||
IRGlobalVisibility.MODULE,
|
|
||||||
true,
|
|
||||||
IRHiddenGlobalKind.BOOT_GUARD,
|
|
||||||
new IRGlobalOrigin(
|
|
||||||
frameExecutable.fileId(),
|
|
||||||
entryPointModuleId,
|
|
||||||
frameAnchorsByModule.getOrDefault(entryPointModuleId, frameExecutable.callableName()),
|
|
||||||
frameExecutable.span(),
|
|
||||||
"BOOT_GUARD"),
|
|
||||||
frameExecutable.span()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final var normalizedGlobals = normalizeGlobalSlots(globals);
|
|
||||||
|
|
||||||
return IRBackend.builder()
|
|
||||||
.entryPointCallableName(entryPointCallableName)
|
|
||||||
.entryPointModuleId(entryPointModuleId)
|
|
||||||
.functions(baseBackend.getFunctions())
|
|
||||||
.syntheticFunctions(baseBackend.getSyntheticFunctions())
|
|
||||||
.globals(ReadOnlyList.wrap(normalizedGlobals))
|
|
||||||
.executableFunctions(ReadOnlyList.wrap(executableFunctions))
|
|
||||||
.modulePool(baseBackend.getModulePool())
|
|
||||||
.callableSignatures(ReadOnlyList.wrap(callableSignatures))
|
|
||||||
.intrinsicPool(baseBackend.getIntrinsicPool())
|
|
||||||
.reservedMetadata(baseBackend.getReservedMetadata())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int nextGlobalSlot(
|
|
||||||
final ArrayList<IRGlobal> globals,
|
|
||||||
final ModuleId moduleId) {
|
|
||||||
var nextSlot = 0;
|
|
||||||
for (final var global : globals) {
|
|
||||||
if (!moduleIdsMatch(moduleId, global.moduleId())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
nextSlot = Math.max(nextSlot, global.slot() + 1);
|
|
||||||
}
|
|
||||||
return nextSlot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayList<IRGlobal> normalizeGlobalSlots(final ArrayList<IRGlobal> globals) {
|
|
||||||
final var nextSlotByModule = new LinkedHashMap<ModuleId, Integer>();
|
|
||||||
final var normalized = new ArrayList<IRGlobal>(globals.size());
|
|
||||||
for (final var global : globals) {
|
|
||||||
final var moduleId = normalizeModuleId(global.moduleId());
|
|
||||||
final var slot = nextSlotByModule.getOrDefault(moduleId, 0);
|
|
||||||
nextSlotByModule.put(moduleId, slot + 1);
|
|
||||||
normalized.add(new IRGlobal(
|
|
||||||
global.fileId(),
|
|
||||||
moduleId,
|
|
||||||
global.name(),
|
|
||||||
global.declaredTypeSurface(),
|
|
||||||
slot,
|
|
||||||
global.visibility(),
|
|
||||||
global.isHidden(),
|
|
||||||
global.hiddenKind(),
|
|
||||||
global.origin(),
|
|
||||||
global.span()));
|
|
||||||
}
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction resolveExecutable(
|
|
||||||
final ArrayList<IRBackendExecutableFunction> executableFunctions,
|
|
||||||
final ModuleId moduleId,
|
final ModuleId moduleId,
|
||||||
final String callableName) {
|
final Set<ModuleId> availableModules,
|
||||||
if (callableName == null || callableName.isBlank()) {
|
final Map<ModuleId, ReadOnlyList<ModuleId>> dependenciesByModule,
|
||||||
return null;
|
final Set<ModuleId> visiting,
|
||||||
|
final Set<ModuleId> visited,
|
||||||
|
final ArrayList<ModuleId> ordered) {
|
||||||
|
if (visited.contains(moduleId) || !availableModules.contains(moduleId)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
for (final var executable : executableFunctions) {
|
if (!visiting.add(moduleId)) {
|
||||||
if (!callableName.equals(executable.callableName())) {
|
return;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (!moduleIdsMatch(moduleId, executable.moduleId())) {
|
for (final var dependency : dependenciesByModule.getOrDefault(moduleId, ReadOnlyList.empty())) {
|
||||||
continue;
|
appendDependenciesFirst(
|
||||||
|
dependency,
|
||||||
|
availableModules,
|
||||||
|
dependenciesByModule,
|
||||||
|
visiting,
|
||||||
|
visited,
|
||||||
|
ordered);
|
||||||
}
|
}
|
||||||
return executable;
|
visiting.remove(moduleId);
|
||||||
|
if (visited.add(moduleId)) {
|
||||||
|
ordered.add(moduleId);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction.Instruction callInstruction(
|
|
||||||
final IRBackendExecutableFunction callee,
|
|
||||||
final Span span) {
|
|
||||||
return new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
|
||||||
callee.moduleId(),
|
|
||||||
callee.callableName(),
|
|
||||||
callee.callableId(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
callee.paramSlots(),
|
|
||||||
callee.returnSlots(),
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction.Instruction retInstruction(final Span span) {
|
|
||||||
return new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.RET,
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction.Instruction getGlobalInstruction(
|
|
||||||
final int slot,
|
|
||||||
final Span span) {
|
|
||||||
return new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.GET_GLOBAL,
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
slot,
|
|
||||||
null,
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction.Instruction setGlobalInstruction(
|
|
||||||
final int slot,
|
|
||||||
final Span span) {
|
|
||||||
return new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.SET_GLOBAL,
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
slot,
|
|
||||||
null,
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction.Instruction pushBoolInstruction(
|
|
||||||
final boolean value,
|
|
||||||
final Span span) {
|
|
||||||
return new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.PUSH_BOOL,
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
value ? "true" : "false",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction.Instruction eqInstruction(final Span span) {
|
|
||||||
return new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.EQ,
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction.Instruction labelInstruction(
|
|
||||||
final String label,
|
|
||||||
final Span span) {
|
|
||||||
return new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.LABEL,
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
label,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRBackendExecutableFunction.Instruction jumpIfTrueInstruction(
|
|
||||||
final String targetLabel,
|
|
||||||
final Span span) {
|
|
||||||
return new IRBackendExecutableFunction.Instruction(
|
|
||||||
IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE,
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
targetLabel,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ModuleId normalizeModuleId(final ModuleId moduleId) {
|
|
||||||
return moduleId == null ? ModuleId.none() : moduleId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean moduleIdsMatch(
|
|
||||||
final ModuleId left,
|
|
||||||
final ModuleId right) {
|
|
||||||
final var normalizedLeft = normalizeModuleId(left);
|
|
||||||
final var normalizedRight = normalizeModuleId(right);
|
|
||||||
if (normalizedLeft.isNone() && normalizedRight.isNone()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !normalizedLeft.isNone()
|
|
||||||
&& !normalizedRight.isNone()
|
|
||||||
&& normalizedLeft.getIndex() == normalizedRight.getIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String moduleSortKey(
|
|
||||||
final IRBackend backend,
|
|
||||||
final ModuleId moduleId) {
|
|
||||||
if (moduleId != null && !moduleId.isNone()) {
|
|
||||||
final var index = moduleId.getIndex();
|
|
||||||
if (index >= 0 && index < backend.getModulePool().size()) {
|
|
||||||
final var moduleReference = backend.getModulePool().get(index);
|
|
||||||
final var joiner = new StringJoiner("/", moduleReference.project() + ":", "");
|
|
||||||
for (final var segment : moduleReference.pathSegments()) {
|
|
||||||
joiner.add(segment);
|
|
||||||
}
|
|
||||||
return joiner.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private int safeToInt(final long value) {
|
|
||||||
if (value > Integer.MAX_VALUE) {
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
if (value < Integer.MIN_VALUE) {
|
|
||||||
return Integer.MIN_VALUE;
|
|
||||||
}
|
|
||||||
return (int) value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<ModuleId> blockedModulesByDependency(
|
private Set<ModuleId> blockedModulesByDependency(
|
||||||
final Set<ModuleId> failedModuleIds,
|
final Set<ModuleId> failedModuleIds,
|
||||||
final Map<ModuleId, Set<ModuleId>> dependenciesByModule) {
|
final Map<ModuleId, ReadOnlyList<ModuleId>> dependenciesByModule) {
|
||||||
final Map<ModuleId, Set<ModuleId>> dependentsByModule = new HashMap<>();
|
final Map<ModuleId, Set<ModuleId>> dependentsByModule = new HashMap<>();
|
||||||
for (final var entry : dependenciesByModule.entrySet()) {
|
for (final var entry : dependenciesByModule.entrySet()) {
|
||||||
final var importer = entry.getKey();
|
final var importer = entry.getKey();
|
||||||
|
|||||||
@ -7,13 +7,14 @@ import p.studio.compiler.source.tables.ModuleTable;
|
|||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
record PbsModuleAssembly(
|
record PbsModuleAssembly(
|
||||||
ReadOnlyList<PbsParsedSourceFile> parsedSourceFiles,
|
ReadOnlyList<PbsParsedSourceFile> parsedSourceFiles,
|
||||||
Map<ModuleId, Set<ModuleId>> moduleDependencyGraph,
|
Map<ModuleId, ReadOnlyList<ModuleId>> moduleDependencyGraph,
|
||||||
ReadOnlyList<ModuleReference> canonicalModulePool,
|
ReadOnlyList<ModuleReference> canonicalModulePool,
|
||||||
Set<ModuleId> failedModuleIds,
|
Set<ModuleId> failedModuleIds,
|
||||||
ModuleTable moduleTable,
|
ModuleTable moduleTable,
|
||||||
@ -26,11 +27,11 @@ record PbsModuleAssembly(
|
|||||||
if (moduleDependencyGraph == null || moduleDependencyGraph.isEmpty()) {
|
if (moduleDependencyGraph == null || moduleDependencyGraph.isEmpty()) {
|
||||||
moduleDependencyGraph = Map.of();
|
moduleDependencyGraph = Map.of();
|
||||||
} else {
|
} else {
|
||||||
final var copiedGraph = new HashMap<ModuleId, Set<ModuleId>>();
|
final var copiedGraph = new LinkedHashMap<ModuleId, ReadOnlyList<ModuleId>>();
|
||||||
for (final var entry : moduleDependencyGraph.entrySet()) {
|
for (final var entry : moduleDependencyGraph.entrySet()) {
|
||||||
copiedGraph.put(entry.getKey(), Set.copyOf(entry.getValue() == null ? Set.of() : entry.getValue()));
|
copiedGraph.put(entry.getKey(), entry.getValue() == null ? ReadOnlyList.empty() : entry.getValue());
|
||||||
}
|
}
|
||||||
moduleDependencyGraph = Map.copyOf(copiedGraph);
|
moduleDependencyGraph = java.util.Collections.unmodifiableMap(copiedGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moduleTable == null) {
|
if (moduleTable == null) {
|
||||||
|
|||||||
@ -353,20 +353,24 @@ final class PbsModuleAssemblyService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<ModuleId, Set<ModuleId>> buildModuleDependencyGraph(
|
private Map<ModuleId, ReadOnlyList<ModuleId>> buildModuleDependencyGraph(
|
||||||
final ArrayList<PbsParsedSourceFile> parsedSourceFiles,
|
final ArrayList<PbsParsedSourceFile> parsedSourceFiles,
|
||||||
final ModuleTable moduleTable) {
|
final ModuleTable moduleTable) {
|
||||||
final Map<ModuleId, Set<ModuleId>> dependenciesByModule = new HashMap<>();
|
final Map<ModuleId, LinkedHashSet<ModuleId>> dependenciesByModule = new LinkedHashMap<>();
|
||||||
for (final var parsedSource : parsedSourceFiles) {
|
for (final var parsedSource : parsedSourceFiles) {
|
||||||
final var moduleDependencies = dependenciesByModule.computeIfAbsent(
|
final var moduleDependencies = dependenciesByModule.computeIfAbsent(
|
||||||
parsedSource.moduleId(),
|
parsedSource.moduleId(),
|
||||||
ignored -> new HashSet<>());
|
ignored -> new LinkedHashSet<>());
|
||||||
for (final var importDecl : parsedSource.ast().imports()) {
|
for (final var importDecl : parsedSource.ast().imports()) {
|
||||||
final var moduleRef = importDecl.moduleRef();
|
final var moduleRef = importDecl.moduleRef();
|
||||||
moduleDependencies.add(moduleId(moduleTable, moduleRef.project(), moduleRef.pathSegments()));
|
moduleDependencies.add(moduleId(moduleTable, moduleRef.project(), moduleRef.pathSegments()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dependenciesByModule;
|
final Map<ModuleId, ReadOnlyList<ModuleId>> ordered = new LinkedHashMap<>();
|
||||||
|
for (final var entry : dependenciesByModule.entrySet()) {
|
||||||
|
ordered.put(entry.getKey(), ReadOnlyList.wrap(new ArrayList<>(entry.getValue())));
|
||||||
|
}
|
||||||
|
return java.util.Collections.unmodifiableMap(ordered);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModuleId moduleId(
|
private ModuleId moduleId(
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import p.studio.compiler.models.IRBackendExecutableFunction;
|
|||||||
import p.studio.compiler.models.IRBackendFile;
|
import p.studio.compiler.models.IRBackendFile;
|
||||||
import p.studio.compiler.models.IRHiddenGlobalKind;
|
import p.studio.compiler.models.IRHiddenGlobalKind;
|
||||||
import p.studio.compiler.models.IRGlobalVisibility;
|
import p.studio.compiler.models.IRGlobalVisibility;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableRole;
|
||||||
import p.studio.compiler.models.IRSyntheticCallableKind;
|
import p.studio.compiler.models.IRSyntheticCallableKind;
|
||||||
import p.studio.compiler.models.SourceKind;
|
import p.studio.compiler.models.SourceKind;
|
||||||
import p.studio.compiler.pbs.lexer.LexErrors;
|
import p.studio.compiler.pbs.lexer.LexErrors;
|
||||||
@ -106,7 +107,7 @@ class PbsFrontendCompilerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldExposeGlobalsAndSyntheticLifecycleArtifactsInBackendFile() {
|
void shouldExposeGlobalsAndTypedLifecycleDeclarationInBackendFile() {
|
||||||
final var source = """
|
final var source = """
|
||||||
declare global SCORE: int = 0;
|
declare global SCORE: int = 0;
|
||||||
|
|
||||||
@ -129,12 +130,20 @@ class PbsFrontendCompilerTest {
|
|||||||
assertFalse(fileBackend.globals().getFirst().isHidden());
|
assertFalse(fileBackend.globals().getFirst().isHidden());
|
||||||
assertEquals(IRHiddenGlobalKind.NONE, fileBackend.globals().getFirst().hiddenKind());
|
assertEquals(IRHiddenGlobalKind.NONE, fileBackend.globals().getFirst().hiddenKind());
|
||||||
assertEquals("SCORE", fileBackend.globals().getFirst().origin().derivedFromUserSymbol());
|
assertEquals("SCORE", fileBackend.globals().getFirst().origin().derivedFromUserSymbol());
|
||||||
assertEquals(4, fileBackend.syntheticFunctions().size());
|
assertEquals(1, fileBackend.syntheticFunctions().size());
|
||||||
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.FILE_INIT_FRAGMENT));
|
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.FILE_INIT_FRAGMENT));
|
||||||
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.MODULE_INIT));
|
|
||||||
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.PROJECT_INIT));
|
|
||||||
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER));
|
|
||||||
assertTrue(fileBackend.syntheticFunctions().stream().allMatch(fn -> !fn.origin().anchorCallableName().isBlank()));
|
assertTrue(fileBackend.syntheticFunctions().stream().allMatch(fn -> !fn.origin().anchorCallableName().isBlank()));
|
||||||
|
assertEquals(IRLifecycleCallableRole.FILE_INIT_FRAGMENT,
|
||||||
|
fileBackend.lifecycleDeclaration().fileInitFragment().role());
|
||||||
|
assertEquals(IRLifecycleCallableRole.PROJECT_INIT,
|
||||||
|
fileBackend.lifecycleDeclaration().projectInit().role());
|
||||||
|
assertEquals(IRLifecycleCallableRole.FRAME_ROOT,
|
||||||
|
fileBackend.lifecycleDeclaration().frameRoot().role());
|
||||||
|
final var fileInit = fileBackend.executableFunctions().stream()
|
||||||
|
.filter(fn -> fn.callableId().equals(fileBackend.lifecycleDeclaration().fileInitFragment().callableId()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
assertTrue(fileInit.instructions().stream().noneMatch(instruction -> "boot".equals(instruction.calleeCallableName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -386,8 +386,10 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
assertTrue(diagnostics.isEmpty());
|
assertTrue(diagnostics.isEmpty());
|
||||||
assertEquals(2, irBackend.getFunctions().size());
|
assertEquals(2, irBackend.getFunctions().size());
|
||||||
assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName())));
|
assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName())));
|
||||||
assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
|
assertNotNull(irBackend.getLifecycleDeclaration());
|
||||||
function.callableName().startsWith("__pbs.frame_wrapper$m")));
|
assertEquals(IRLifecycleCallableRole.FRAME_ROOT, irBackend.getLifecycleDeclaration().frameRoot().role());
|
||||||
|
assertTrue(irBackend.getSyntheticFunctions().stream().noneMatch(function ->
|
||||||
|
function.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -483,36 +485,19 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
BuildingIssueSink.empty());
|
BuildingIssueSink.empty());
|
||||||
|
|
||||||
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||||
assertTrue(irBackend.getEntryPointCallableName().startsWith("__pbs.frame_wrapper$m"));
|
assertNotNull(irBackend.getLifecycleDeclaration());
|
||||||
|
assertEquals(IRLifecycleCallableRole.PROJECT_INIT, irBackend.getLifecycleDeclaration().projectInit().role());
|
||||||
|
assertEquals(IRLifecycleCallableRole.FRAME_ROOT, irBackend.getLifecycleDeclaration().frameRoot().role());
|
||||||
final var scoreGlobal = irBackend.getGlobals().stream()
|
final var scoreGlobal = irBackend.getGlobals().stream()
|
||||||
.filter(global -> "SCORE".equals(global.name()))
|
.filter(global -> "SCORE".equals(global.name()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
assertEquals(IRGlobalVisibility.PUBLIC, scoreGlobal.visibility());
|
assertEquals(IRGlobalVisibility.PUBLIC, scoreGlobal.visibility());
|
||||||
final var bootGuard = irBackend.getGlobals().stream()
|
assertTrue(irBackend.getGlobals().stream().noneMatch(global -> global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD));
|
||||||
.filter(global -> global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD)
|
assertTrue(irBackend.getSyntheticFunctions().stream().noneMatch(function ->
|
||||||
.findFirst()
|
function.kind() == IRSyntheticCallableKind.MODULE_INIT
|
||||||
.orElseThrow();
|
|| function.kind() == IRSyntheticCallableKind.PROJECT_INIT
|
||||||
assertTrue(bootGuard.isHidden());
|
|| function.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER));
|
||||||
assertEquals(IRGlobalVisibility.MODULE, bootGuard.visibility());
|
|
||||||
assertTrue(bootGuard.name().startsWith("__pbs.boot_guard$m"));
|
|
||||||
assertEquals(1, bootGuard.slot());
|
|
||||||
final var wrapper = irBackend.getExecutableFunctions().stream()
|
|
||||||
.filter(function -> irBackend.getEntryPointCallableName().equals(function.callableName()))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow();
|
|
||||||
assertEquals(11, wrapper.instructions().size());
|
|
||||||
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.GET_GLOBAL, wrapper.instructions().get(0).kind());
|
|
||||||
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_BOOL, wrapper.instructions().get(1).kind());
|
|
||||||
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.EQ, wrapper.instructions().get(2).kind());
|
|
||||||
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE, wrapper.instructions().get(3).kind());
|
|
||||||
assertTrue(wrapper.instructions().get(4).calleeCallableName().startsWith("__pbs.module_init$m"));
|
|
||||||
assertTrue(wrapper.instructions().get(5).calleeCallableName().startsWith("__pbs.project_init$m"));
|
|
||||||
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_BOOL, wrapper.instructions().get(6).kind());
|
|
||||||
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.SET_GLOBAL, wrapper.instructions().get(7).kind());
|
|
||||||
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.LABEL, wrapper.instructions().get(8).kind());
|
|
||||||
assertEquals("frame", wrapper.instructions().get(9).calleeCallableName());
|
|
||||||
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.RET, wrapper.instructions().get(10).kind());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -572,16 +557,100 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
BuildingIssueSink.empty());
|
BuildingIssueSink.empty());
|
||||||
|
|
||||||
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||||
assertTrue(irBackend.getEntryPointCallableName().startsWith("__pbs.frame_wrapper$m"));
|
assertNotNull(irBackend.getLifecycleDeclaration());
|
||||||
assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
|
assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
|
||||||
function.callableName().startsWith("__pbs.file_init$f")));
|
function.callableName().startsWith("__pbs.file_init$f")));
|
||||||
assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
|
assertEquals(1, irBackend.getLifecycleDeclaration().modules().getFirst().fileInitFragments().size());
|
||||||
function.callableName().startsWith("__pbs.module_init$m")));
|
assertEquals(IRLifecycleCallableRole.PROJECT_INIT, irBackend.getLifecycleDeclaration().projectInit().role());
|
||||||
assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
|
assertEquals(IRLifecycleCallableRole.FRAME_ROOT, irBackend.getLifecycleDeclaration().frameRoot().role());
|
||||||
function.callableName().startsWith("__pbs.project_init$m")));
|
|
||||||
assertTrue(irBackend.getGlobals().stream().anyMatch(global ->
|
assertTrue(irBackend.getGlobals().stream().anyMatch(global ->
|
||||||
"SCORE".equals(global.name()) && global.visibility() == IRGlobalVisibility.PUBLIC));
|
"SCORE".equals(global.name()) && global.visibility() == IRGlobalVisibility.PUBLIC));
|
||||||
assertTrue(irBackend.getGlobals().stream().anyMatch(global -> global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD));
|
assertTrue(irBackend.getGlobals().stream().noneMatch(global -> global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPublishDependencyOrderedLifecycleModulesInsteadOfNameOrFileIdOrder() throws IOException {
|
||||||
|
final var projectRoot = tempDir.resolve("project-lifecycle-dep-order");
|
||||||
|
final var sourceRoot = projectRoot.resolve("src");
|
||||||
|
final var zzzPath = sourceRoot.resolve("zzz");
|
||||||
|
final var aaaPath = sourceRoot.resolve("aaa");
|
||||||
|
Files.createDirectories(zzzPath);
|
||||||
|
Files.createDirectories(aaaPath);
|
||||||
|
|
||||||
|
final var zzzSource = zzzPath.resolve("source.pbs");
|
||||||
|
final var zzzBarrel = zzzPath.resolve("mod.barrel");
|
||||||
|
Files.writeString(zzzSource, """
|
||||||
|
declare global WARM: int = 1;
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
fn warmup() -> void { return; }
|
||||||
|
""");
|
||||||
|
Files.writeString(zzzBarrel, """
|
||||||
|
pub fn warmup() -> void;
|
||||||
|
pub global WARM;
|
||||||
|
""");
|
||||||
|
|
||||||
|
final var aaaSource = aaaPath.resolve("source.pbs");
|
||||||
|
final var aaaBarrel = aaaPath.resolve("mod.barrel");
|
||||||
|
Files.writeString(aaaSource, """
|
||||||
|
import { warmup } from @app:zzz;
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
fn boot() -> void {
|
||||||
|
warmup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Frame]
|
||||||
|
fn frame() -> void { return; }
|
||||||
|
""");
|
||||||
|
Files.writeString(aaaBarrel, """
|
||||||
|
pub fn boot() -> void;
|
||||||
|
pub fn frame() -> void;
|
||||||
|
""");
|
||||||
|
|
||||||
|
final var projectTable = new ProjectTable();
|
||||||
|
final var fileTable = new FileTable(1);
|
||||||
|
final var projectId = projectTable.register(ProjectDescriptor.builder()
|
||||||
|
.rootPath(projectRoot)
|
||||||
|
.name("app")
|
||||||
|
.version("1.0.0")
|
||||||
|
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registerFile(projectId, projectRoot, aaaSource, fileTable);
|
||||||
|
registerFile(projectId, projectRoot, aaaBarrel, fileTable);
|
||||||
|
registerFile(projectId, projectRoot, zzzSource, fileTable);
|
||||||
|
registerFile(projectId, projectRoot, zzzBarrel, fileTable);
|
||||||
|
|
||||||
|
final var ctx = new FrontendPhaseContext(
|
||||||
|
projectTable,
|
||||||
|
fileTable,
|
||||||
|
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
|
||||||
|
AppMode.Game);
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
final var irBackend = new PBSFrontendPhaseService().compile(
|
||||||
|
ctx,
|
||||||
|
diagnostics,
|
||||||
|
LogAggregator.empty(),
|
||||||
|
BuildingIssueSink.empty());
|
||||||
|
|
||||||
|
assertEquals(0, diagnostics.errorCount(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
|
||||||
|
final var declaration = irBackend.getLifecycleDeclaration();
|
||||||
|
assertNotNull(declaration);
|
||||||
|
assertEquals(2, declaration.modules().size());
|
||||||
|
final var firstModule = irBackend.getModulePool().get(declaration.modules().get(0).moduleId().getIndex());
|
||||||
|
final var secondModule = irBackend.getModulePool().get(declaration.modules().get(1).moduleId().getIndex());
|
||||||
|
assertEquals("zzz", firstModule.pathSegments().getFirst());
|
||||||
|
assertEquals("aaa", secondModule.pathSegments().getFirst());
|
||||||
|
assertFalse(declaration.modules().get(0).fileInitFragments().isEmpty());
|
||||||
|
assertEquals(IRLifecycleCallableRole.PROJECT_INIT, declaration.projectInit().role());
|
||||||
|
assertEquals(IRLifecycleCallableRole.FRAME_ROOT, declaration.frameRoot().role());
|
||||||
|
assertTrue(irBackend.getSyntheticFunctions().stream().noneMatch(function ->
|
||||||
|
function.kind() == IRSyntheticCallableKind.MODULE_INIT
|
||||||
|
|| function.kind() == IRSyntheticCallableKind.PROJECT_INIT
|
||||||
|
|| function.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -500,6 +500,8 @@ public class LowerToIRVMService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateCommonLifecycleStructure(final IRBackend backend) {
|
private void validateCommonLifecycleStructure(final IRBackend backend) {
|
||||||
|
// Backend precondition on an already-assembled handoff. Declaration
|
||||||
|
// validation and materialization belong to LifecycleAssemblerService.
|
||||||
if (backend.getSyntheticFunctions().isEmpty()) {
|
if (backend.getSyntheticFunctions().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,532 @@
|
|||||||
|
package p.studio.compiler.lifecycle;
|
||||||
|
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.models.*;
|
||||||
|
import p.studio.compiler.source.Span;
|
||||||
|
import p.studio.compiler.source.identifiers.CallableId;
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
import p.studio.compiler.source.tables.CallableSignatureRef;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class LifecycleAssemblerService {
|
||||||
|
public static final String PHASE = "lifecycle-assembly";
|
||||||
|
public static final String INVALID_DECLARATION = "COMMON_LIFECYCLE_INVALID_DECLARATION";
|
||||||
|
|
||||||
|
public IRBackend assemble(
|
||||||
|
final IRBackend backend,
|
||||||
|
final BuildingIssueSink issues) {
|
||||||
|
if (backend == null) {
|
||||||
|
report(issues, null, "IRBackend is required for lifecycle assembly");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final var declaration = backend.getLifecycleDeclaration();
|
||||||
|
if (declaration == null) {
|
||||||
|
return backend;
|
||||||
|
}
|
||||||
|
if (!validate(backend, declaration, issues)) {
|
||||||
|
return backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var callableSignatures = new ArrayList<>(backend.getCallableSignatures().asList());
|
||||||
|
final var executableFunctions = new ArrayList<>(backend.getExecutableFunctions().asList());
|
||||||
|
final var syntheticFunctions = new ArrayList<>(backend.getSyntheticFunctions().asList());
|
||||||
|
final var globals = new ArrayList<>(backend.getGlobals().asList());
|
||||||
|
final var moduleInitCallableIds = new LinkedHashMap<ModuleId, CallableId>();
|
||||||
|
|
||||||
|
for (final var module : declaration.modules()) {
|
||||||
|
if (module.fileInitFragments().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var moduleId = module.moduleId();
|
||||||
|
final var moduleInitName = moduleInitCallableName(moduleId);
|
||||||
|
final var moduleInitCallableId = new CallableId(callableSignatures.size());
|
||||||
|
callableSignatures.add(new CallableSignatureRef(moduleId, moduleInitName, 0, "() -> unit"));
|
||||||
|
moduleInitCallableIds.put(moduleId, moduleInitCallableId);
|
||||||
|
|
||||||
|
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
|
||||||
|
IRBackendExecutableFunction anchor = null;
|
||||||
|
for (final var fragment : module.fileInitFragments()) {
|
||||||
|
final var executable = resolveExecutable(backend, fragment);
|
||||||
|
instructions.add(callInstruction(executable, fragment.origin()));
|
||||||
|
anchor = executable;
|
||||||
|
}
|
||||||
|
instructions.add(retInstruction(anchor.span()));
|
||||||
|
executableFunctions.add(new IRBackendExecutableFunction(
|
||||||
|
anchor.fileId(),
|
||||||
|
moduleId,
|
||||||
|
moduleInitName,
|
||||||
|
moduleInitCallableId,
|
||||||
|
anchor.sourceStart(),
|
||||||
|
anchor.sourceEnd(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.wrap(instructions),
|
||||||
|
anchor.span()));
|
||||||
|
syntheticFunctions.add(new IRSyntheticFunction(
|
||||||
|
moduleId,
|
||||||
|
moduleInitName,
|
||||||
|
IRSyntheticCallableKind.MODULE_INIT,
|
||||||
|
new IRSyntheticOrigin(anchor.fileId(), anchor.callableName(), anchor.span())));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var frameExecutable = resolveExecutable(backend, declaration.frameRoot());
|
||||||
|
final var entryPointModuleId = frameExecutable.moduleId();
|
||||||
|
|
||||||
|
CallableId projectInitCallableId = null;
|
||||||
|
if (declaration.projectInit() != null) {
|
||||||
|
final var projectInitTarget = resolveExecutable(backend, declaration.projectInit());
|
||||||
|
final var projectInitName = projectInitCallableName(entryPointModuleId);
|
||||||
|
projectInitCallableId = new CallableId(callableSignatures.size());
|
||||||
|
callableSignatures.add(new CallableSignatureRef(
|
||||||
|
entryPointModuleId,
|
||||||
|
projectInitName,
|
||||||
|
0,
|
||||||
|
"() -> unit"));
|
||||||
|
executableFunctions.add(new IRBackendExecutableFunction(
|
||||||
|
projectInitTarget.fileId(),
|
||||||
|
entryPointModuleId,
|
||||||
|
projectInitName,
|
||||||
|
projectInitCallableId,
|
||||||
|
projectInitTarget.sourceStart(),
|
||||||
|
projectInitTarget.sourceEnd(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(
|
||||||
|
callInstruction(projectInitTarget, projectInitTarget.span()),
|
||||||
|
retInstruction(projectInitTarget.span())),
|
||||||
|
projectInitTarget.span()));
|
||||||
|
syntheticFunctions.add(new IRSyntheticFunction(
|
||||||
|
entryPointModuleId,
|
||||||
|
projectInitName,
|
||||||
|
IRSyntheticCallableKind.PROJECT_INIT,
|
||||||
|
new IRSyntheticOrigin(
|
||||||
|
projectInitTarget.fileId(),
|
||||||
|
projectInitTarget.callableName(),
|
||||||
|
projectInitTarget.span())));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var entryPointCallableName = frameWrapperCallableName(entryPointModuleId);
|
||||||
|
final var bootGuardSlot = nextGlobalSlot(globals, entryPointModuleId);
|
||||||
|
final var wrapperCallableId = new CallableId(callableSignatures.size());
|
||||||
|
callableSignatures.add(new CallableSignatureRef(
|
||||||
|
entryPointModuleId,
|
||||||
|
entryPointCallableName,
|
||||||
|
0,
|
||||||
|
"() -> unit"));
|
||||||
|
|
||||||
|
final var wrapperInstructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
|
||||||
|
wrapperInstructions.add(getGlobalInstruction(bootGuardSlot, frameExecutable.span()));
|
||||||
|
wrapperInstructions.add(pushBoolInstruction(true, frameExecutable.span()));
|
||||||
|
wrapperInstructions.add(eqInstruction(frameExecutable.span()));
|
||||||
|
wrapperInstructions.add(jumpIfTrueInstruction("bootstrap_done", frameExecutable.span()));
|
||||||
|
for (final var module : declaration.modules()) {
|
||||||
|
final var moduleInitCallableId = moduleInitCallableIds.get(module.moduleId());
|
||||||
|
if (moduleInitCallableId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
wrapperInstructions.add(new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
||||||
|
module.moduleId(),
|
||||||
|
moduleInitCallableName(module.moduleId()),
|
||||||
|
moduleInitCallableId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
frameExecutable.span()));
|
||||||
|
}
|
||||||
|
if (projectInitCallableId != null) {
|
||||||
|
wrapperInstructions.add(new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
||||||
|
entryPointModuleId,
|
||||||
|
projectInitCallableName(entryPointModuleId),
|
||||||
|
projectInitCallableId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
frameExecutable.span()));
|
||||||
|
}
|
||||||
|
wrapperInstructions.add(pushBoolInstruction(true, frameExecutable.span()));
|
||||||
|
wrapperInstructions.add(setGlobalInstruction(bootGuardSlot, frameExecutable.span()));
|
||||||
|
wrapperInstructions.add(labelInstruction("bootstrap_done", frameExecutable.span()));
|
||||||
|
wrapperInstructions.add(callInstruction(frameExecutable, frameExecutable.span()));
|
||||||
|
wrapperInstructions.add(retInstruction(frameExecutable.span()));
|
||||||
|
executableFunctions.add(new IRBackendExecutableFunction(
|
||||||
|
frameExecutable.fileId(),
|
||||||
|
entryPointModuleId,
|
||||||
|
entryPointCallableName,
|
||||||
|
wrapperCallableId,
|
||||||
|
frameExecutable.sourceStart(),
|
||||||
|
frameExecutable.sourceEnd(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
ReadOnlyList.wrap(wrapperInstructions),
|
||||||
|
frameExecutable.span()));
|
||||||
|
syntheticFunctions.add(new IRSyntheticFunction(
|
||||||
|
entryPointModuleId,
|
||||||
|
entryPointCallableName,
|
||||||
|
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
||||||
|
new IRSyntheticOrigin(
|
||||||
|
frameExecutable.fileId(),
|
||||||
|
frameExecutable.callableName(),
|
||||||
|
frameExecutable.span())));
|
||||||
|
|
||||||
|
globals.add(new IRGlobal(
|
||||||
|
frameExecutable.fileId(),
|
||||||
|
entryPointModuleId,
|
||||||
|
bootGuardGlobalName(entryPointModuleId),
|
||||||
|
"bool",
|
||||||
|
bootGuardSlot,
|
||||||
|
IRGlobalVisibility.MODULE,
|
||||||
|
true,
|
||||||
|
IRHiddenGlobalKind.BOOT_GUARD,
|
||||||
|
new IRGlobalOrigin(
|
||||||
|
frameExecutable.fileId(),
|
||||||
|
entryPointModuleId,
|
||||||
|
frameExecutable.callableName(),
|
||||||
|
frameExecutable.span(),
|
||||||
|
"BOOT_GUARD"),
|
||||||
|
frameExecutable.span()));
|
||||||
|
|
||||||
|
return IRBackend.builder()
|
||||||
|
.entryPointCallableName(entryPointCallableName)
|
||||||
|
.entryPointModuleId(entryPointModuleId)
|
||||||
|
.functions(backend.getFunctions())
|
||||||
|
.syntheticFunctions(ReadOnlyList.wrap(syntheticFunctions))
|
||||||
|
.globals(ReadOnlyList.wrap(normalizeGlobalSlots(globals)))
|
||||||
|
.executableFunctions(ReadOnlyList.wrap(executableFunctions))
|
||||||
|
.modulePool(backend.getModulePool())
|
||||||
|
.callableSignatures(ReadOnlyList.wrap(callableSignatures))
|
||||||
|
.intrinsicPool(backend.getIntrinsicPool())
|
||||||
|
.reservedMetadata(backend.getReservedMetadata())
|
||||||
|
.lifecycleDeclaration(declaration)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validate(
|
||||||
|
final IRBackend backend,
|
||||||
|
final IRLifecycleDeclaration declaration,
|
||||||
|
final BuildingIssueSink issues) {
|
||||||
|
var valid = true;
|
||||||
|
for (final var synthetic : backend.getSyntheticFunctions()) {
|
||||||
|
if (synthetic.kind() != IRSyntheticCallableKind.FILE_INIT_FRAGMENT) {
|
||||||
|
report(issues, synthetic.origin().span(),
|
||||||
|
"frontend handoff contains pre-materialized lifecycle artifact: " + synthetic.kind());
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final var global : backend.getGlobals()) {
|
||||||
|
if (global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD) {
|
||||||
|
report(issues, global.span(), "frontend handoff contains pre-materialized boot guard");
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<Integer> moduleIds = new HashSet<>();
|
||||||
|
final Set<Integer> lifecycleCallableIds = new HashSet<>();
|
||||||
|
for (final var module : declaration.modules()) {
|
||||||
|
final var moduleIndex = module.moduleId().isNone() ? -1 : module.moduleId().getIndex();
|
||||||
|
if (!moduleIds.add(moduleIndex)) {
|
||||||
|
report(issues, null, "duplicate lifecycle module: " + moduleIndex);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
for (final var fragment : module.fileInitFragments()) {
|
||||||
|
valid &= validateReference(
|
||||||
|
backend,
|
||||||
|
fragment,
|
||||||
|
IRLifecycleCallableRole.FILE_INIT_FRAGMENT,
|
||||||
|
module.moduleId(),
|
||||||
|
lifecycleCallableIds,
|
||||||
|
issues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (declaration.projectInit() != null) {
|
||||||
|
valid &= validateReference(
|
||||||
|
backend,
|
||||||
|
declaration.projectInit(),
|
||||||
|
IRLifecycleCallableRole.PROJECT_INIT,
|
||||||
|
declaration.projectInit().moduleId(),
|
||||||
|
lifecycleCallableIds,
|
||||||
|
issues);
|
||||||
|
}
|
||||||
|
if (declaration.frameRoot() == null) {
|
||||||
|
report(issues, null, "executable lifecycle declaration is missing frame root");
|
||||||
|
valid = false;
|
||||||
|
} else {
|
||||||
|
valid &= validateReference(
|
||||||
|
backend,
|
||||||
|
declaration.frameRoot(),
|
||||||
|
IRLifecycleCallableRole.FRAME_ROOT,
|
||||||
|
declaration.frameRoot().moduleId(),
|
||||||
|
lifecycleCallableIds,
|
||||||
|
issues);
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateReference(
|
||||||
|
final IRBackend backend,
|
||||||
|
final IRLifecycleCallableReference reference,
|
||||||
|
final IRLifecycleCallableRole expectedRole,
|
||||||
|
final ModuleId expectedModuleId,
|
||||||
|
final Set<Integer> lifecycleCallableIds,
|
||||||
|
final BuildingIssueSink issues) {
|
||||||
|
if (reference == null) {
|
||||||
|
report(issues, null, "missing lifecycle reference for role " + expectedRole);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var valid = true;
|
||||||
|
if (reference.role() != expectedRole) {
|
||||||
|
report(issues, reference.origin(), "incompatible lifecycle role: expected "
|
||||||
|
+ expectedRole + " but found " + reference.role());
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (!sameModuleId(reference.moduleId(), expectedModuleId)) {
|
||||||
|
report(issues, reference.origin(), "lifecycle reference module does not match declaration module");
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (reference.origin() == null || reference.origin().isNone()) {
|
||||||
|
report(issues, reference.origin(), "lifecycle reference origin is missing for role " + expectedRole);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
final var callableIndex = reference.callableId().getIndex();
|
||||||
|
if (!lifecycleCallableIds.add(callableIndex)) {
|
||||||
|
report(issues, reference.origin(), "callable is assigned to multiple lifecycle roles: " + callableIndex);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (callableIndex < 0 || callableIndex >= backend.getCallableSignatures().size()) {
|
||||||
|
report(issues, reference.origin(), "unresolved lifecycle callable id: " + callableIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final var signature = backend.getCallableSignatures().get(callableIndex);
|
||||||
|
if (!sameModuleId(signature.moduleId(), reference.moduleId())) {
|
||||||
|
report(issues, reference.origin(), "lifecycle callable signature module mismatch: " + callableIndex);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (resolveExecutable(backend, reference) == null) {
|
||||||
|
report(issues, reference.origin(), "lifecycle callable has no executable body: " + callableIndex);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction resolveExecutable(
|
||||||
|
final IRBackend backend,
|
||||||
|
final IRLifecycleCallableReference reference) {
|
||||||
|
if (reference == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (final var executable : backend.getExecutableFunctions()) {
|
||||||
|
if (executable.callableId().getIndex() == reference.callableId().getIndex()
|
||||||
|
&& sameModuleId(executable.moduleId(), reference.moduleId())) {
|
||||||
|
return executable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void report(
|
||||||
|
final BuildingIssueSink issues,
|
||||||
|
final Span span,
|
||||||
|
final String message) {
|
||||||
|
final var effectiveSpan = span == null ? Span.none() : span;
|
||||||
|
issues.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.phase(PHASE)
|
||||||
|
.code(INVALID_DECLARATION)
|
||||||
|
.fileId(effectiveSpan.getFileId().isNone() ? null : effectiveSpan.getFileId().getId())
|
||||||
|
.start(safeToInt(effectiveSpan.getStart()))
|
||||||
|
.end(safeToInt(effectiveSpan.getEnd()))
|
||||||
|
.message(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int nextGlobalSlot(
|
||||||
|
final ArrayList<IRGlobal> globals,
|
||||||
|
final ModuleId moduleId) {
|
||||||
|
var nextSlot = 0;
|
||||||
|
for (final var global : globals) {
|
||||||
|
if (sameModuleId(moduleId, global.moduleId())) {
|
||||||
|
nextSlot = Math.max(nextSlot, global.slot() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<IRGlobal> normalizeGlobalSlots(final ArrayList<IRGlobal> globals) {
|
||||||
|
final var nextSlotByModule = new LinkedHashMap<ModuleId, Integer>();
|
||||||
|
final var normalized = new ArrayList<IRGlobal>(globals.size());
|
||||||
|
for (final var global : globals) {
|
||||||
|
final var moduleId = normalizeModuleId(global.moduleId());
|
||||||
|
final var slot = nextSlotByModule.getOrDefault(moduleId, 0);
|
||||||
|
nextSlotByModule.put(moduleId, slot + 1);
|
||||||
|
normalized.add(new IRGlobal(
|
||||||
|
global.fileId(),
|
||||||
|
moduleId,
|
||||||
|
global.name(),
|
||||||
|
global.declaredTypeSurface(),
|
||||||
|
slot,
|
||||||
|
global.visibility(),
|
||||||
|
global.isHidden(),
|
||||||
|
global.hiddenKind(),
|
||||||
|
global.origin(),
|
||||||
|
global.span()));
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction callInstruction(
|
||||||
|
final IRBackendExecutableFunction callee,
|
||||||
|
final Span span) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
||||||
|
callee.moduleId(),
|
||||||
|
callee.callableName(),
|
||||||
|
callee.callableId(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
callee.paramSlots(),
|
||||||
|
callee.returnSlots(),
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction retInstruction(final Span span) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction getGlobalInstruction(final int slot, final Span span) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.GET_GLOBAL,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
slot,
|
||||||
|
null,
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction setGlobalInstruction(final int slot, final Span span) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.SET_GLOBAL,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
slot,
|
||||||
|
null,
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction pushBoolInstruction(final boolean value, final Span span) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.PUSH_BOOL,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
value ? "true" : "false",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction eqInstruction(final Span span) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.EQ,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction labelInstruction(final String label, final Span span) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.LABEL,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
label,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction jumpIfTrueInstruction(final String label, final Span span) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
label,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModuleId normalizeModuleId(final ModuleId moduleId) {
|
||||||
|
return moduleId == null ? ModuleId.none() : moduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sameModuleId(final ModuleId left, final ModuleId right) {
|
||||||
|
final var normalizedLeft = normalizeModuleId(left);
|
||||||
|
final var normalizedRight = normalizeModuleId(right);
|
||||||
|
if (normalizedLeft.isNone() || normalizedRight.isNone()) {
|
||||||
|
return normalizedLeft.isNone() && normalizedRight.isNone();
|
||||||
|
}
|
||||||
|
return normalizedLeft.getIndex() == normalizedRight.getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String moduleInitCallableName(final ModuleId moduleId) {
|
||||||
|
return "__prometeu.lifecycle.module_init$m" + normalizedModuleIndex(moduleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String projectInitCallableName(final ModuleId moduleId) {
|
||||||
|
return "__prometeu.lifecycle.project_init$m" + normalizedModuleIndex(moduleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String frameWrapperCallableName(final ModuleId moduleId) {
|
||||||
|
return "__prometeu.lifecycle.frame_wrapper$m" + normalizedModuleIndex(moduleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String bootGuardGlobalName(final ModuleId moduleId) {
|
||||||
|
return "__prometeu.lifecycle.boot_guard$m" + normalizedModuleIndex(moduleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int normalizedModuleIndex(final ModuleId moduleId) {
|
||||||
|
final var normalized = normalizeModuleId(moduleId);
|
||||||
|
return normalized.isNone() ? -1 : normalized.getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int safeToInt(final long value) {
|
||||||
|
if (value > Integer.MAX_VALUE) {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
if (value < Integer.MIN_VALUE) {
|
||||||
|
return Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
return (int) value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,7 +23,8 @@ public class BuilderPipelineService {
|
|||||||
final var analyses = List.<PipelineStage>of(
|
final var analyses = List.<PipelineStage>of(
|
||||||
new ResolveDepsPipelineStage(),
|
new ResolveDepsPipelineStage(),
|
||||||
new LoadSourcesPipelineStage(),
|
new LoadSourcesPipelineStage(),
|
||||||
new FrontendPhasePipelineStage()
|
new FrontendPhasePipelineStage(),
|
||||||
|
new AssembleLifecyclePipelineStage()
|
||||||
);
|
);
|
||||||
|
|
||||||
final var compile = List.<PipelineStage>of(
|
final var compile = List.<PipelineStage>of(
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
package p.studio.compiler.workspaces.stages;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import p.studio.compiler.lifecycle.LifecycleAssemblerService;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.models.BuilderPipelineContext;
|
||||||
|
import p.studio.compiler.workspaces.PipelineStage;
|
||||||
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public final class AssembleLifecyclePipelineStage implements PipelineStage {
|
||||||
|
private final LifecycleAssemblerService assembler;
|
||||||
|
|
||||||
|
public AssembleLifecyclePipelineStage() {
|
||||||
|
this(new LifecycleAssemblerService());
|
||||||
|
}
|
||||||
|
|
||||||
|
AssembleLifecyclePipelineStage(final LifecycleAssemblerService assembler) {
|
||||||
|
this.assembler = assembler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
final var assembled = assembler.assemble(ctx.irBackend, issues);
|
||||||
|
if (!issues.hasErrors()) {
|
||||||
|
ctx.irBackend = assembled;
|
||||||
|
logs.using(log).debug("Common lifecycle assembly completed successfully!");
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -67,31 +67,31 @@ class LowerToIRVMServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void lowerMustPublishSyntheticWrapperAtFunctionZeroAndKeepFinalRetInWrapperPath() {
|
void lowerMustPublishSyntheticWrapperAtFunctionZeroAndKeepFinalRetInWrapperPath() {
|
||||||
final var backend = IRBackend.builder()
|
final var backend = IRBackend.builder()
|
||||||
.entryPointCallableName("__pbs.frame_wrapper$m0")
|
.entryPointCallableName("__prometeu.lifecycle.frame_wrapper$m0")
|
||||||
.entryPointModuleId(new ModuleId(0))
|
.entryPointModuleId(new ModuleId(0))
|
||||||
.globals(ReadOnlyList.from(hiddenBootGuard(0)))
|
.globals(ReadOnlyList.from(hiddenBootGuard(0)))
|
||||||
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
||||||
new ModuleId(0),
|
new ModuleId(0),
|
||||||
"__pbs.frame_wrapper$m0",
|
"__prometeu.lifecycle.frame_wrapper$m0",
|
||||||
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
||||||
new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
|
new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
|
||||||
.executableFunctions(ReadOnlyList.from(
|
.executableFunctions(ReadOnlyList.from(
|
||||||
fn("__pbs.file_init$f1", "app", 30, ReadOnlyList.from(ret())),
|
fn("__prometeu.lifecycle.file_init$f1", "app", 30, ReadOnlyList.from(ret())),
|
||||||
fn("__pbs.module_init$m0", "app", 31, ReadOnlyList.from(
|
fn("__prometeu.lifecycle.module_init$m0", "app", 31, ReadOnlyList.from(
|
||||||
callFunc("app", "__pbs.file_init$f1", 30),
|
callFunc("app", "__prometeu.lifecycle.file_init$f1", 30),
|
||||||
ret())),
|
ret())),
|
||||||
fn("__pbs.project_init$m0", "app", 32, ReadOnlyList.from(
|
fn("__prometeu.lifecycle.project_init$m0", "app", 32, ReadOnlyList.from(
|
||||||
callFunc("app", "boot", 10),
|
callFunc("app", "boot", 10),
|
||||||
ret())),
|
ret())),
|
||||||
fn("boot", "app", 10, ReadOnlyList.from(ret())),
|
fn("boot", "app", 10, ReadOnlyList.from(ret())),
|
||||||
fn("frame", "app", 20, ReadOnlyList.from(ret())),
|
fn("frame", "app", 20, ReadOnlyList.from(ret())),
|
||||||
fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
|
fn("__prometeu.lifecycle.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
|
||||||
getGlobal(0),
|
getGlobal(0),
|
||||||
pushBool(true),
|
pushBool(true),
|
||||||
eq(),
|
eq(),
|
||||||
jmpIfTrue("bootstrap_done"),
|
jmpIfTrue("bootstrap_done"),
|
||||||
callFunc("app", "__pbs.module_init$m0", 31),
|
callFunc("app", "__prometeu.lifecycle.module_init$m0", 31),
|
||||||
callFunc("app", "__pbs.project_init$m0", 32),
|
callFunc("app", "__prometeu.lifecycle.project_init$m0", 32),
|
||||||
pushBool(true),
|
pushBool(true),
|
||||||
setGlobal(0),
|
setGlobal(0),
|
||||||
label("bootstrap_done"),
|
label("bootstrap_done"),
|
||||||
@ -101,7 +101,7 @@ class LowerToIRVMServiceTest {
|
|||||||
|
|
||||||
final var lowered = new LowerToIRVMService().lower(backend);
|
final var lowered = new LowerToIRVMService().lower(backend);
|
||||||
|
|
||||||
assertEquals("__pbs.frame_wrapper$m0", lowered.module().functions().get(0).name());
|
assertEquals("__prometeu.lifecycle.frame_wrapper$m0", lowered.module().functions().get(0).name());
|
||||||
assertEquals(IRVMOp.GET_GLOBAL, lowered.module().functions().get(0).instructions().get(0).op());
|
assertEquals(IRVMOp.GET_GLOBAL, lowered.module().functions().get(0).instructions().get(0).op());
|
||||||
assertEquals(IRVMOp.PUSH_BOOL, lowered.module().functions().get(0).instructions().get(1).op());
|
assertEquals(IRVMOp.PUSH_BOOL, lowered.module().functions().get(0).instructions().get(1).op());
|
||||||
assertEquals(IRVMOp.EQ, lowered.module().functions().get(0).instructions().get(2).op());
|
assertEquals(IRVMOp.EQ, lowered.module().functions().get(0).instructions().get(2).op());
|
||||||
@ -124,12 +124,12 @@ class LowerToIRVMServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void lowerMustRejectWhenSyntheticWrapperEntrypointIsMissing() {
|
void lowerMustRejectWhenSyntheticWrapperEntrypointIsMissing() {
|
||||||
final var backend = IRBackend.builder()
|
final var backend = IRBackend.builder()
|
||||||
.entryPointCallableName("__pbs.frame_wrapper$m0")
|
.entryPointCallableName("__prometeu.lifecycle.frame_wrapper$m0")
|
||||||
.entryPointModuleId(new ModuleId(0))
|
.entryPointModuleId(new ModuleId(0))
|
||||||
.globals(ReadOnlyList.from(hiddenBootGuard(0)))
|
.globals(ReadOnlyList.from(hiddenBootGuard(0)))
|
||||||
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
||||||
new ModuleId(0),
|
new ModuleId(0),
|
||||||
"__pbs.frame_wrapper$m0",
|
"__prometeu.lifecycle.frame_wrapper$m0",
|
||||||
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
||||||
new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
|
new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
|
||||||
.executableFunctions(ReadOnlyList.from(
|
.executableFunctions(ReadOnlyList.from(
|
||||||
@ -148,11 +148,11 @@ class LowerToIRVMServiceTest {
|
|||||||
.globals(ReadOnlyList.from(hiddenBootGuard(0)))
|
.globals(ReadOnlyList.from(hiddenBootGuard(0)))
|
||||||
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
||||||
new ModuleId(0),
|
new ModuleId(0),
|
||||||
"__pbs.frame_wrapper$m0",
|
"__prometeu.lifecycle.frame_wrapper$m0",
|
||||||
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
||||||
new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
|
new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
|
||||||
.executableFunctions(ReadOnlyList.from(
|
.executableFunctions(ReadOnlyList.from(
|
||||||
fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
|
fn("__prometeu.lifecycle.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
|
||||||
callFunc("app", "frame", 20),
|
callFunc("app", "frame", 20),
|
||||||
ret())),
|
ret())),
|
||||||
fn("frame", "app", 20, ReadOnlyList.from(ret()))))
|
fn("frame", "app", 20, ReadOnlyList.from(ret()))))
|
||||||
@ -165,15 +165,15 @@ class LowerToIRVMServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void lowerMustRejectWhenHiddenBootGuardIsMissing() {
|
void lowerMustRejectWhenHiddenBootGuardIsMissing() {
|
||||||
final var backend = IRBackend.builder()
|
final var backend = IRBackend.builder()
|
||||||
.entryPointCallableName("__pbs.frame_wrapper$m0")
|
.entryPointCallableName("__prometeu.lifecycle.frame_wrapper$m0")
|
||||||
.entryPointModuleId(new ModuleId(0))
|
.entryPointModuleId(new ModuleId(0))
|
||||||
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
||||||
new ModuleId(0),
|
new ModuleId(0),
|
||||||
"__pbs.frame_wrapper$m0",
|
"__prometeu.lifecycle.frame_wrapper$m0",
|
||||||
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
||||||
new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
|
new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
|
||||||
.executableFunctions(ReadOnlyList.from(
|
.executableFunctions(ReadOnlyList.from(
|
||||||
fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
|
fn("__prometeu.lifecycle.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
|
||||||
callFunc("app", "frame", 20),
|
callFunc("app", "frame", 20),
|
||||||
ret())),
|
ret())),
|
||||||
fn("frame", "app", 20, ReadOnlyList.from(ret()))))
|
fn("frame", "app", 20, ReadOnlyList.from(ret()))))
|
||||||
@ -186,16 +186,16 @@ class LowerToIRVMServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void lowerMustRejectWhenSyntheticCallableOriginIsMissing() {
|
void lowerMustRejectWhenSyntheticCallableOriginIsMissing() {
|
||||||
final var backend = IRBackend.builder()
|
final var backend = IRBackend.builder()
|
||||||
.entryPointCallableName("__pbs.frame_wrapper$m0")
|
.entryPointCallableName("__prometeu.lifecycle.frame_wrapper$m0")
|
||||||
.entryPointModuleId(new ModuleId(0))
|
.entryPointModuleId(new ModuleId(0))
|
||||||
.globals(ReadOnlyList.from(userVisibleGlobal("__pbs.boot_guard$m0", 0)))
|
.globals(ReadOnlyList.from(userVisibleGlobal("__prometeu.lifecycle.boot_guard$m0", 0)))
|
||||||
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
.syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
|
||||||
new ModuleId(0),
|
new ModuleId(0),
|
||||||
"__pbs.frame_wrapper$m0",
|
"__prometeu.lifecycle.frame_wrapper$m0",
|
||||||
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
||||||
new IRSyntheticOrigin(new FileId(0), "", Span.none()))))
|
new IRSyntheticOrigin(new FileId(0), "", Span.none()))))
|
||||||
.executableFunctions(ReadOnlyList.from(
|
.executableFunctions(ReadOnlyList.from(
|
||||||
fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
|
fn("__prometeu.lifecycle.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
|
||||||
callFunc("app", "frame", 20),
|
callFunc("app", "frame", 20),
|
||||||
ret())),
|
ret())),
|
||||||
fn("frame", "app", 20, ReadOnlyList.from(ret()))))
|
fn("frame", "app", 20, ReadOnlyList.from(ret()))))
|
||||||
@ -450,7 +450,7 @@ class LowerToIRVMServiceTest {
|
|||||||
return new IRGlobal(
|
return new IRGlobal(
|
||||||
fileId,
|
fileId,
|
||||||
ownerModuleId,
|
ownerModuleId,
|
||||||
"__pbs.boot_guard$m" + moduleId,
|
"__prometeu.lifecycle.boot_guard$m" + moduleId,
|
||||||
"bool",
|
"bool",
|
||||||
0,
|
0,
|
||||||
IRGlobalVisibility.MODULE,
|
IRGlobalVisibility.MODULE,
|
||||||
|
|||||||
@ -0,0 +1,367 @@
|
|||||||
|
package p.studio.compiler.lifecycle;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.models.IRBackend;
|
||||||
|
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||||
|
import p.studio.compiler.models.IRGlobal;
|
||||||
|
import p.studio.compiler.models.IRGlobalOrigin;
|
||||||
|
import p.studio.compiler.models.IRGlobalVisibility;
|
||||||
|
import p.studio.compiler.models.IRHiddenGlobalKind;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableReference;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableRole;
|
||||||
|
import p.studio.compiler.models.IRLifecycleDeclaration;
|
||||||
|
import p.studio.compiler.models.IRLifecycleModuleDeclaration;
|
||||||
|
import p.studio.compiler.models.IRSyntheticCallableKind;
|
||||||
|
import p.studio.compiler.models.IRSyntheticFunction;
|
||||||
|
import p.studio.compiler.models.IRSyntheticOrigin;
|
||||||
|
import p.studio.compiler.source.Span;
|
||||||
|
import p.studio.compiler.source.identifiers.CallableId;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
import p.studio.compiler.source.tables.CallableSignatureRef;
|
||||||
|
import p.studio.compiler.source.tables.ModuleReference;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class LifecycleAssemblerServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustMaterializeNeutralLifecycleArtifactsInDeclaredOrder() {
|
||||||
|
final var backend = declarativeBackend();
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
final var assembled = new LifecycleAssemblerService().assemble(backend, issues);
|
||||||
|
|
||||||
|
assertFalse(issues.hasErrors(), issues.asCollection().toString());
|
||||||
|
assertSame(backend.getLifecycleDeclaration(), assembled.getLifecycleDeclaration());
|
||||||
|
assertEquals("__prometeu.lifecycle.frame_wrapper$m1", assembled.getEntryPointCallableName());
|
||||||
|
assertEquals(1, assembled.getEntryPointModuleId().getIndex());
|
||||||
|
assertTrue(assembled.getSyntheticFunctions().stream().anyMatch(fn ->
|
||||||
|
fn.kind() == IRSyntheticCallableKind.MODULE_INIT
|
||||||
|
&& "__prometeu.lifecycle.module_init$m0".equals(fn.callableName())));
|
||||||
|
assertTrue(assembled.getSyntheticFunctions().stream().anyMatch(fn ->
|
||||||
|
fn.kind() == IRSyntheticCallableKind.PROJECT_INIT
|
||||||
|
&& "__prometeu.lifecycle.project_init$m1".equals(fn.callableName())));
|
||||||
|
assertTrue(assembled.getSyntheticFunctions().stream().anyMatch(fn ->
|
||||||
|
fn.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER
|
||||||
|
&& "__prometeu.lifecycle.frame_wrapper$m1".equals(fn.callableName())));
|
||||||
|
assertTrue(assembled.getGlobals().stream().anyMatch(global ->
|
||||||
|
global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD
|
||||||
|
&& global.isHidden()
|
||||||
|
&& "__prometeu.lifecycle.boot_guard$m1".equals(global.name())));
|
||||||
|
assertTrue(assembled.getSyntheticFunctions().stream().noneMatch(fn ->
|
||||||
|
fn.callableName().contains("__pbs.")));
|
||||||
|
assertTrue(assembled.getGlobals().stream().noneMatch(global ->
|
||||||
|
global.name().contains("__pbs.")));
|
||||||
|
|
||||||
|
final var wrapper = executable(assembled, "__prometeu.lifecycle.frame_wrapper$m1");
|
||||||
|
final var calledNames = wrapper.instructions().stream()
|
||||||
|
.filter(instruction -> instruction.kind() == IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
|
||||||
|
.map(IRBackendExecutableFunction.Instruction::calleeCallableName)
|
||||||
|
.toList();
|
||||||
|
assertEquals(
|
||||||
|
List.of(
|
||||||
|
"__prometeu.lifecycle.module_init$m0",
|
||||||
|
"__prometeu.lifecycle.module_init$m1",
|
||||||
|
"__prometeu.lifecycle.project_init$m1",
|
||||||
|
"frame"),
|
||||||
|
calledNames);
|
||||||
|
assertEquals(
|
||||||
|
1,
|
||||||
|
calledNames.stream().filter("__prometeu.lifecycle.project_init$m1"::equals).count());
|
||||||
|
assertEquals(IRBackendExecutableFunction.InstructionKind.RET, wrapper.instructions().getLast().kind());
|
||||||
|
|
||||||
|
final var moduleInit = executable(assembled, "__prometeu.lifecycle.module_init$m0");
|
||||||
|
assertEquals(
|
||||||
|
List.of("file_init_dep"),
|
||||||
|
moduleInit.instructions().stream()
|
||||||
|
.filter(instruction -> instruction.kind() == IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
|
||||||
|
.map(IRBackendExecutableFunction.Instruction::calleeCallableName)
|
||||||
|
.toList());
|
||||||
|
final var hostModuleInit = executable(assembled, "__prometeu.lifecycle.module_init$m1");
|
||||||
|
assertEquals(
|
||||||
|
List.of("file_init_host"),
|
||||||
|
hostModuleInit.instructions().stream()
|
||||||
|
.filter(instruction -> instruction.kind() == IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
|
||||||
|
.map(IRBackendExecutableFunction.Instruction::calleeCallableName)
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustInvokeProjectInitExactlyOnceAndExcludeItFromFileInit() {
|
||||||
|
final var assembled = new LifecycleAssemblerService().assemble(declarativeBackend(), BuildingIssueSink.empty());
|
||||||
|
final var wrapperCalls = executable(assembled, assembled.getEntryPointCallableName()).instructions().stream()
|
||||||
|
.filter(instruction -> instruction.kind() == IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
|
||||||
|
.map(IRBackendExecutableFunction.Instruction::calleeCallableName)
|
||||||
|
.toList();
|
||||||
|
final var fileInitCalls = executable(assembled, "file_init_host").instructions().stream()
|
||||||
|
.filter(instruction -> instruction.kind() == IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
|
||||||
|
.map(IRBackendExecutableFunction.Instruction::calleeCallableName)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
assertEquals(1, wrapperCalls.stream().filter("__prometeu.lifecycle.project_init$m1"::equals).count());
|
||||||
|
assertTrue(wrapperCalls.contains("frame"));
|
||||||
|
assertTrue(fileInitCalls.stream().noneMatch("boot"::equals));
|
||||||
|
final var projectInitWrapper = executable(assembled, "__prometeu.lifecycle.project_init$m1");
|
||||||
|
assertEquals(
|
||||||
|
List.of("boot"),
|
||||||
|
projectInitWrapper.instructions().stream()
|
||||||
|
.filter(instruction -> instruction.kind() == IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
|
||||||
|
.map(IRBackendExecutableFunction.Instruction::calleeCallableName)
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustNoOpWhenLifecycleDeclarationIsAbsent() {
|
||||||
|
final var backend = IRBackend.builder()
|
||||||
|
.entryPointCallableName("main")
|
||||||
|
.entryPointModuleId(new ModuleId(0))
|
||||||
|
.executableFunctions(ReadOnlyList.from(fn("main", 0, 0, 0, span(1))))
|
||||||
|
.callableSignatures(ReadOnlyList.from(new CallableSignatureRef(new ModuleId(0), "main", 0, "() -> unit")))
|
||||||
|
.build();
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
final var assembled = new LifecycleAssemblerService().assemble(backend, issues);
|
||||||
|
|
||||||
|
assertFalse(issues.hasErrors());
|
||||||
|
assertSame(backend, assembled);
|
||||||
|
assertNull(assembled.getLifecycleDeclaration());
|
||||||
|
assertTrue(assembled.getSyntheticFunctions().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustRejectMissingFrameRootWithoutRepair() {
|
||||||
|
final var backend = backendWithDeclaration(new IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList.from(new IRLifecycleModuleDeclaration(new ModuleId(0), ReadOnlyList.empty())),
|
||||||
|
null,
|
||||||
|
null));
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
final var assembled = new LifecycleAssemblerService().assemble(backend, issues);
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertSame(backend, assembled);
|
||||||
|
assertEquals(LifecycleAssemblerService.INVALID_DECLARATION, firstCode(issues));
|
||||||
|
assertTrue(firstMessage(issues).contains("frame root"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustRejectUnresolvedCallableIds() {
|
||||||
|
final var backend = backendWithDeclaration(new IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
null,
|
||||||
|
reference(0, 1, 99, IRLifecycleCallableRole.FRAME_ROOT, span(3))));
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
new LifecycleAssemblerService().assemble(backend, issues);
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertTrue(firstMessage(issues).contains("unresolved lifecycle callable id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustRejectIncompatibleRoles() {
|
||||||
|
final var backend = backendWithDeclaration(new IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
null,
|
||||||
|
reference(0, 1, 2, IRLifecycleCallableRole.PROJECT_INIT, span(3))));
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
new LifecycleAssemblerService().assemble(backend, issues);
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertTrue(firstMessage(issues).contains("incompatible lifecycle role"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustRejectMissingOrigin() {
|
||||||
|
final var backend = backendWithDeclaration(new IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
null,
|
||||||
|
reference(0, 1, 2, IRLifecycleCallableRole.FRAME_ROOT, Span.none())));
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
new LifecycleAssemblerService().assemble(backend, issues);
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertTrue(firstMessage(issues).contains("origin is missing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustRejectPreMaterializedDerivedArtifacts() {
|
||||||
|
final var backend = declarativeBackend(new IRSyntheticFunction(
|
||||||
|
new ModuleId(1),
|
||||||
|
"already_wrapped",
|
||||||
|
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
|
||||||
|
new IRSyntheticOrigin(new FileId(2), "frame", span(2))));
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
final var assembled = new LifecycleAssemblerService().assemble(backend, issues);
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertSame(backend, assembled);
|
||||||
|
assertTrue(firstMessage(issues).contains("pre-materialized lifecycle artifact"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustRejectDuplicateModuleContributions() {
|
||||||
|
final var module = new IRLifecycleModuleDeclaration(
|
||||||
|
new ModuleId(0),
|
||||||
|
ReadOnlyList.from(reference(0, 1, 0, IRLifecycleCallableRole.FILE_INIT_FRAGMENT, span(1))));
|
||||||
|
final var backend = backendWithDeclaration(new IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList.from(module, module),
|
||||||
|
reference(1, 2, 3, IRLifecycleCallableRole.PROJECT_INIT, span(2)),
|
||||||
|
reference(1, 2, 2, IRLifecycleCallableRole.FRAME_ROOT, span(2))));
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
new LifecycleAssemblerService().assemble(backend, issues);
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertTrue(firstMessage(issues).contains("duplicate lifecycle module"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void assembleMustRejectNullBackend() {
|
||||||
|
final var issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
|
final var assembled = new LifecycleAssemblerService().assemble(null, issues);
|
||||||
|
|
||||||
|
assertNull(assembled);
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertEquals(LifecycleAssemblerService.PHASE, issues.asCollection().iterator().next().getPhase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackend declarativeBackend(final IRSyntheticFunction... extraSynthetics) {
|
||||||
|
return declarativeBackend(null, extraSynthetics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackend backendWithDeclaration(final IRLifecycleDeclaration declaration) {
|
||||||
|
return declarativeBackend(declaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackend declarativeBackend(
|
||||||
|
final IRLifecycleDeclaration declarationOverride,
|
||||||
|
final IRSyntheticFunction... extraSynthetics) {
|
||||||
|
final var depSpan = span(1);
|
||||||
|
final var hostSpan = span(2);
|
||||||
|
final var synthetics = new ArrayList<IRSyntheticFunction>();
|
||||||
|
synthetics.add(new IRSyntheticFunction(
|
||||||
|
new ModuleId(0),
|
||||||
|
"file_init_dep",
|
||||||
|
IRSyntheticCallableKind.FILE_INIT_FRAGMENT,
|
||||||
|
new IRSyntheticOrigin(new FileId(1), "warmup", depSpan)));
|
||||||
|
synthetics.add(new IRSyntheticFunction(
|
||||||
|
new ModuleId(1),
|
||||||
|
"file_init_host",
|
||||||
|
IRSyntheticCallableKind.FILE_INIT_FRAGMENT,
|
||||||
|
new IRSyntheticOrigin(new FileId(2), "boot", hostSpan)));
|
||||||
|
synthetics.addAll(Arrays.asList(extraSynthetics));
|
||||||
|
final var declaration = declarationOverride != null
|
||||||
|
? declarationOverride
|
||||||
|
: new IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList.from(
|
||||||
|
new IRLifecycleModuleDeclaration(
|
||||||
|
new ModuleId(0),
|
||||||
|
ReadOnlyList.from(reference(0, 1, 0, IRLifecycleCallableRole.FILE_INIT_FRAGMENT, depSpan))),
|
||||||
|
new IRLifecycleModuleDeclaration(
|
||||||
|
new ModuleId(1),
|
||||||
|
ReadOnlyList.from(reference(1, 2, 1, IRLifecycleCallableRole.FILE_INIT_FRAGMENT, hostSpan)))),
|
||||||
|
reference(1, 2, 3, IRLifecycleCallableRole.PROJECT_INIT, hostSpan),
|
||||||
|
reference(1, 2, 2, IRLifecycleCallableRole.FRAME_ROOT, hostSpan));
|
||||||
|
return IRBackend.builder()
|
||||||
|
.entryPointCallableName("frame")
|
||||||
|
.entryPointModuleId(new ModuleId(1))
|
||||||
|
.modulePool(ReadOnlyList.from(
|
||||||
|
new ModuleReference("app", ReadOnlyList.from("zzz")),
|
||||||
|
new ModuleReference("app", ReadOnlyList.from("aaa"))))
|
||||||
|
.callableSignatures(ReadOnlyList.from(
|
||||||
|
new CallableSignatureRef(new ModuleId(0), "file_init_dep", 0, "() -> unit"),
|
||||||
|
new CallableSignatureRef(new ModuleId(1), "file_init_host", 0, "() -> unit"),
|
||||||
|
new CallableSignatureRef(new ModuleId(1), "frame", 0, "() -> unit"),
|
||||||
|
new CallableSignatureRef(new ModuleId(1), "boot", 0, "() -> unit")))
|
||||||
|
.executableFunctions(ReadOnlyList.from(
|
||||||
|
fn("file_init_dep", 1, 0, 0, depSpan),
|
||||||
|
fn("file_init_host", 2, 1, 1, hostSpan),
|
||||||
|
fn("frame", 2, 1, 2, hostSpan),
|
||||||
|
fn("boot", 2, 1, 3, hostSpan)))
|
||||||
|
.syntheticFunctions(ReadOnlyList.wrap(synthetics))
|
||||||
|
.globals(ReadOnlyList.from(new IRGlobal(
|
||||||
|
new FileId(1),
|
||||||
|
new ModuleId(0),
|
||||||
|
"SCORE",
|
||||||
|
"int",
|
||||||
|
0,
|
||||||
|
IRGlobalVisibility.MODULE,
|
||||||
|
false,
|
||||||
|
IRHiddenGlobalKind.NONE,
|
||||||
|
new IRGlobalOrigin(new FileId(1), new ModuleId(0), "SCORE", depSpan, "SCORE"),
|
||||||
|
depSpan)))
|
||||||
|
.lifecycleDeclaration(declaration)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRLifecycleCallableReference reference(
|
||||||
|
final int moduleId,
|
||||||
|
final int fileId,
|
||||||
|
final int callableId,
|
||||||
|
final IRLifecycleCallableRole role,
|
||||||
|
final Span origin) {
|
||||||
|
return new IRLifecycleCallableReference(
|
||||||
|
new ModuleId(moduleId),
|
||||||
|
new FileId(fileId),
|
||||||
|
new CallableId(callableId),
|
||||||
|
role,
|
||||||
|
origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackendExecutableFunction fn(
|
||||||
|
final String name,
|
||||||
|
final int fileId,
|
||||||
|
final int moduleId,
|
||||||
|
final int callableId,
|
||||||
|
final Span span) {
|
||||||
|
return new IRBackendExecutableFunction(
|
||||||
|
new FileId(fileId),
|
||||||
|
new ModuleId(moduleId),
|
||||||
|
name,
|
||||||
|
new CallableId(callableId),
|
||||||
|
(int) span.getStart(),
|
||||||
|
(int) span.getEnd(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span)),
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackendExecutableFunction executable(final IRBackend backend, final String name) {
|
||||||
|
return backend.getExecutableFunctions().stream()
|
||||||
|
.filter(function -> name.equals(function.callableName()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new AssertionError("missing executable " + name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Span span(final int fileId) {
|
||||||
|
return new Span(new FileId(fileId), 4, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String firstCode(final BuildingIssueSink issues) {
|
||||||
|
return issues.asCollection().iterator().next().getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String firstMessage(final BuildingIssueSink issues) {
|
||||||
|
return issues.asCollection().iterator().next().getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,6 +42,9 @@ class BackendConformanceMatrixSpecTest {
|
|||||||
"G20-4.2.1",
|
"G20-4.2.1",
|
||||||
"G20-4.2.2",
|
"G20-4.2.2",
|
||||||
"G20-4.2.3",
|
"G20-4.2.3",
|
||||||
|
"G20-4.3.1",
|
||||||
|
"G20-4.3.2",
|
||||||
|
"G20-4.3.3",
|
||||||
"G20-6.2",
|
"G20-6.2",
|
||||||
"G20-6.3",
|
"G20-6.3",
|
||||||
"G20-6.4",
|
"G20-6.4",
|
||||||
@ -81,7 +84,11 @@ class BackendConformanceMatrixSpecTest {
|
|||||||
"PBS13-12.3.2",
|
"PBS13-12.3.2",
|
||||||
"PBS13-12.4.1",
|
"PBS13-12.4.1",
|
||||||
"PBS13-12.4.2",
|
"PBS13-12.4.2",
|
||||||
"PBS13-12.5");
|
"PBS13-12.5",
|
||||||
|
"PBS13-12.7",
|
||||||
|
"PBS13-12.8",
|
||||||
|
"PBS13-12.9",
|
||||||
|
"G23-5.2");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void matrixMustExistAndContainRequiredRequirementRows() throws IOException {
|
void matrixMustExistAndContainRequiredRequirementRows() throws IOException {
|
||||||
|
|||||||
@ -6,30 +6,53 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
class CommonBackendArchitectureTest {
|
class CommonBackendArchitectureTest {
|
||||||
private static final String BACKEND_SOURCE_ROOT =
|
private static final String COMPILER_SOURCE_ROOT =
|
||||||
"prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend";
|
"prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler";
|
||||||
|
private static final List<String> GUARDED_RELATIVE_ROOTS = List.of(
|
||||||
|
"backend",
|
||||||
|
"lifecycle",
|
||||||
|
"workspaces/stages/AssembleLifecyclePipelineStage.java");
|
||||||
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 String FORBIDDEN_PBS_PREFIX = "__pbs.";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void commonBackendMustNotImportPbsFrontendPackages() throws IOException {
|
void commonBackendMustNotImportPbsFrontendPackages() throws IOException {
|
||||||
final var backendRoot = locateRepoRoot().resolve(BACKEND_SOURCE_ROOT);
|
|
||||||
final var violations = new ArrayList<String>();
|
final var violations = new ArrayList<String>();
|
||||||
|
walkGuardedSources(path -> assertNoPbsImport(path, violations));
|
||||||
try (final var paths = Files.walk(backendRoot)) {
|
|
||||||
paths.filter(path -> path.toString().endsWith(".java"))
|
|
||||||
.forEach(path -> assertNoPbsImport(path, violations));
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
violations.isEmpty(),
|
violations.isEmpty(),
|
||||||
"common backend must not import PBS frontend packages: " + violations);
|
"common backend must not import PBS frontend packages: " + violations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void commonLifecycleAndBackendMustNotDiscoverRolesFromPbsNamePrefixes() throws IOException {
|
||||||
|
final var violations = new ArrayList<String>();
|
||||||
|
walkGuardedSources(path -> assertNoPbsPrefixMatching(path, violations));
|
||||||
|
assertTrue(
|
||||||
|
violations.isEmpty(),
|
||||||
|
"common lifecycle/backend code must not inspect __pbs. prefixes: " + violations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void walkGuardedSources(final java.util.function.Consumer<Path> consumer) throws IOException {
|
||||||
|
final var compilerRoot = locateRepoRoot().resolve(COMPILER_SOURCE_ROOT);
|
||||||
|
for (final var relativeRoot : GUARDED_RELATIVE_ROOTS) {
|
||||||
|
final var root = compilerRoot.resolve(relativeRoot);
|
||||||
|
if (Files.isRegularFile(root)) {
|
||||||
|
consumer.accept(root);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try (final var paths = Files.walk(root)) {
|
||||||
|
paths.filter(path -> path.toString().endsWith(".java")).forEach(consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void assertNoPbsImport(
|
private static void assertNoPbsImport(
|
||||||
final Path path,
|
final Path path,
|
||||||
final ArrayList<String> violations) {
|
final ArrayList<String> violations) {
|
||||||
@ -44,6 +67,19 @@ class CommonBackendArchitectureTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void assertNoPbsPrefixMatching(
|
||||||
|
final Path path,
|
||||||
|
final ArrayList<String> violations) {
|
||||||
|
try {
|
||||||
|
final var content = Files.readString(path);
|
||||||
|
if (content.contains(FORBIDDEN_PBS_PREFIX)) {
|
||||||
|
violations.add(locateRepoRoot().relativize(path).toString());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("failed to read source file: " + path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Path locateRepoRoot() {
|
private static Path locateRepoRoot() {
|
||||||
var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize();
|
var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize();
|
||||||
while (cursor != null) {
|
while (cursor != null) {
|
||||||
|
|||||||
@ -0,0 +1,156 @@
|
|||||||
|
package p.studio.compiler.workspaces;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.AppMode;
|
||||||
|
import p.studio.compiler.lifecycle.LifecycleAssemblerService;
|
||||||
|
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||||
|
import p.studio.compiler.models.BuilderPipelineContext;
|
||||||
|
import p.studio.compiler.models.IRBackend;
|
||||||
|
import p.studio.compiler.models.ResolvedWorkspace;
|
||||||
|
import p.studio.compiler.source.identifiers.ProjectId;
|
||||||
|
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableReference;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableRole;
|
||||||
|
import p.studio.compiler.models.IRLifecycleDeclaration;
|
||||||
|
import p.studio.compiler.models.IRLifecycleModuleDeclaration;
|
||||||
|
import p.studio.compiler.models.IRSyntheticCallableKind;
|
||||||
|
import p.studio.compiler.source.Span;
|
||||||
|
import p.studio.compiler.source.identifiers.CallableId;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
import p.studio.compiler.source.tables.CallableSignatureRef;
|
||||||
|
import p.studio.compiler.source.tables.ModuleReference;
|
||||||
|
import p.studio.compiler.workspaces.stages.AssembleLifecyclePipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.EmitBytecodePipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.FrontendPhasePipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.LinkBytecodePipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.LoadSourcesPipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.LowerToIRVMPipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.OptimizeIRVMPipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.ResolveDepsPipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.VerifyBytecodePipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.WriteBytecodeArtifactPipelineStage;
|
||||||
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class BuilderPipelineServiceOrderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void canonicalOrderMustContainOptimizeBetweenLowerAndEmit() {
|
||||||
|
final var analyses = stageNames("analyses");
|
||||||
|
final var compile = stageNames("compile");
|
||||||
|
final var build = stageNames("build");
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
List.of(
|
||||||
|
ResolveDepsPipelineStage.class.getSimpleName(),
|
||||||
|
LoadSourcesPipelineStage.class.getSimpleName(),
|
||||||
|
FrontendPhasePipelineStage.class.getSimpleName(),
|
||||||
|
AssembleLifecyclePipelineStage.class.getSimpleName()),
|
||||||
|
analyses);
|
||||||
|
assertEquals(
|
||||||
|
List.of(
|
||||||
|
LowerToIRVMPipelineStage.class.getSimpleName(),
|
||||||
|
OptimizeIRVMPipelineStage.class.getSimpleName(),
|
||||||
|
EmitBytecodePipelineStage.class.getSimpleName(),
|
||||||
|
LinkBytecodePipelineStage.class.getSimpleName(),
|
||||||
|
VerifyBytecodePipelineStage.class.getSimpleName()),
|
||||||
|
compile);
|
||||||
|
assertEquals(List.of(WriteBytecodeArtifactPipelineStage.class.getSimpleName()), build);
|
||||||
|
assertEquals(
|
||||||
|
FrontendPhasePipelineStage.class.getSimpleName(),
|
||||||
|
analyses.get(analyses.size() - 2));
|
||||||
|
assertEquals(
|
||||||
|
AssembleLifecyclePipelineStage.class.getSimpleName(),
|
||||||
|
analyses.getLast());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void analyzeMustTerminateAfterLifecycleAssemblyWithoutBackendArtifacts() {
|
||||||
|
final var ctx = BuilderPipelineContext.fromConfig(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.irBackend = validDeclaration();
|
||||||
|
ctx.resolvedWorkspace = new ResolvedWorkspace(
|
||||||
|
new ProjectId(0),
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
AppMode.Game,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
final var pipeline = new BuilderPipelineService(
|
||||||
|
List.of(new AssembleLifecyclePipelineStage()),
|
||||||
|
List.of(new LowerToIRVMPipelineStage()),
|
||||||
|
List.of());
|
||||||
|
|
||||||
|
final var snapshot = pipeline.analyze(ctx, LogAggregator.empty());
|
||||||
|
|
||||||
|
assertNotNull(snapshot.irBackend());
|
||||||
|
assertTrue(snapshot.irBackend().getSyntheticFunctions().stream().anyMatch(fn ->
|
||||||
|
fn.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER));
|
||||||
|
assertEquals(
|
||||||
|
"__prometeu.lifecycle.frame_wrapper$m0",
|
||||||
|
snapshot.irBackend().getEntryPointCallableName());
|
||||||
|
assertSame(ctx.irBackend.getLifecycleDeclaration(), snapshot.irBackend().getLifecycleDeclaration());
|
||||||
|
assertNull(ctx.irvm);
|
||||||
|
assertNull(ctx.optimizedIrvm);
|
||||||
|
assertNull(ctx.bytecodeModule);
|
||||||
|
assertNull(ctx.bytecodeBytes);
|
||||||
|
assertNull(ctx.bytecodeArtifactPath);
|
||||||
|
assertTrue(snapshot.diagnostics().stream().noneMatch(issue ->
|
||||||
|
LifecycleAssemblerService.INVALID_DECLARATION.equals(issue.getCode())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static List<String> stageNames(final String fieldName) {
|
||||||
|
try {
|
||||||
|
final Field field = BuilderPipelineService.class.getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
final var stages = (List<PipelineStage>) field.get(BuilderPipelineService.INSTANCE);
|
||||||
|
return stages.stream().map(stage -> stage.getClass().getSimpleName()).toList();
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new AssertionError("failed to inspect pipeline field " + fieldName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackend validDeclaration() {
|
||||||
|
final var span = new Span(new FileId(1), 1, 8);
|
||||||
|
return IRBackend.builder()
|
||||||
|
.entryPointCallableName("frame")
|
||||||
|
.entryPointModuleId(new ModuleId(0))
|
||||||
|
.modulePool(ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("main"))))
|
||||||
|
.callableSignatures(ReadOnlyList.from(
|
||||||
|
new CallableSignatureRef(new ModuleId(0), "frame", 0, "() -> unit")))
|
||||||
|
.executableFunctions(ReadOnlyList.from(new IRBackendExecutableFunction(
|
||||||
|
new FileId(1),
|
||||||
|
new ModuleId(0),
|
||||||
|
"frame",
|
||||||
|
new CallableId(0),
|
||||||
|
1,
|
||||||
|
8,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span)),
|
||||||
|
span)))
|
||||||
|
.lifecycleDeclaration(new IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList.from(new IRLifecycleModuleDeclaration(new ModuleId(0), ReadOnlyList.empty())),
|
||||||
|
null,
|
||||||
|
new IRLifecycleCallableReference(
|
||||||
|
new ModuleId(0),
|
||||||
|
new FileId(1),
|
||||||
|
new CallableId(0),
|
||||||
|
IRLifecycleCallableRole.FRAME_ROOT,
|
||||||
|
span)))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
package p.studio.compiler.workspaces.stages;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.lifecycle.LifecycleAssemblerService;
|
||||||
|
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.models.BuilderPipelineContext;
|
||||||
|
import p.studio.compiler.models.IRBackend;
|
||||||
|
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableReference;
|
||||||
|
import p.studio.compiler.models.IRLifecycleCallableRole;
|
||||||
|
import p.studio.compiler.models.IRLifecycleDeclaration;
|
||||||
|
import p.studio.compiler.models.IRLifecycleModuleDeclaration;
|
||||||
|
import p.studio.compiler.models.IRSyntheticCallableKind;
|
||||||
|
import p.studio.compiler.source.Span;
|
||||||
|
import p.studio.compiler.source.identifiers.CallableId;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
import p.studio.compiler.source.tables.CallableSignatureRef;
|
||||||
|
import p.studio.compiler.source.tables.ModuleReference;
|
||||||
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class AssembleLifecyclePipelineStageTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustAssignAssembledBackendOnSuccess() {
|
||||||
|
final var ctx = BuilderPipelineContext.fromConfig(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.irBackend = validDeclaration();
|
||||||
|
final var original = ctx.irBackend;
|
||||||
|
|
||||||
|
final var issues = new AssembleLifecyclePipelineStage().run(ctx, LogAggregator.empty());
|
||||||
|
|
||||||
|
assertFalse(issues.hasErrors(), issues.asCollection().toString());
|
||||||
|
assertNotSame(original, ctx.irBackend);
|
||||||
|
assertTrue(ctx.irBackend.getSyntheticFunctions().stream().anyMatch(fn ->
|
||||||
|
fn.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER));
|
||||||
|
assertEquals(
|
||||||
|
original.getLifecycleDeclaration(),
|
||||||
|
ctx.irBackend.getLifecycleDeclaration());
|
||||||
|
assertNull(ctx.irvm);
|
||||||
|
assertNull(ctx.bytecodeModule);
|
||||||
|
assertNull(ctx.bytecodeBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustKeepOriginalBackendAndReportStructuralErrors() {
|
||||||
|
final var ctx = BuilderPipelineContext.fromConfig(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.irBackend = IRBackend.builder()
|
||||||
|
.entryPointCallableName("frame")
|
||||||
|
.entryPointModuleId(new ModuleId(0))
|
||||||
|
.executableFunctions(ReadOnlyList.from(fn("frame", 0, 0)))
|
||||||
|
.callableSignatures(ReadOnlyList.from(new CallableSignatureRef(new ModuleId(0), "frame", 0, "() -> unit")))
|
||||||
|
.lifecycleDeclaration(new IRLifecycleDeclaration(ReadOnlyList.empty(), null, null))
|
||||||
|
.build();
|
||||||
|
final var original = ctx.irBackend;
|
||||||
|
|
||||||
|
final var issues = new AssembleLifecyclePipelineStage().run(ctx, LogAggregator.empty());
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertSame(original, ctx.irBackend);
|
||||||
|
assertEquals(LifecycleAssemblerService.INVALID_DECLARATION, issues.asCollection().iterator().next().getCode());
|
||||||
|
assertEquals(LifecycleAssemblerService.PHASE, issues.asCollection().iterator().next().getPhase());
|
||||||
|
assertNull(ctx.irvm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustNoOpForDeliberatelyNonExecutableHandoff() {
|
||||||
|
final var ctx = BuilderPipelineContext.fromConfig(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.irBackend = IRBackend.builder()
|
||||||
|
.entryPointCallableName("main")
|
||||||
|
.entryPointModuleId(new ModuleId(0))
|
||||||
|
.executableFunctions(ReadOnlyList.from(fn("main", 0, 0)))
|
||||||
|
.build();
|
||||||
|
final var original = ctx.irBackend;
|
||||||
|
|
||||||
|
final var issues = new AssembleLifecyclePipelineStage().run(ctx, LogAggregator.empty());
|
||||||
|
|
||||||
|
assertFalse(issues.hasErrors());
|
||||||
|
assertSame(original, ctx.irBackend);
|
||||||
|
assertTrue(ctx.irBackend.getSyntheticFunctions().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackend validDeclaration() {
|
||||||
|
final var span = new Span(new FileId(1), 1, 8);
|
||||||
|
return IRBackend.builder()
|
||||||
|
.entryPointCallableName("frame")
|
||||||
|
.entryPointModuleId(new ModuleId(0))
|
||||||
|
.modulePool(ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("main"))))
|
||||||
|
.callableSignatures(ReadOnlyList.from(
|
||||||
|
new CallableSignatureRef(new ModuleId(0), "frame", 0, "() -> unit")))
|
||||||
|
.executableFunctions(ReadOnlyList.from(fn("frame", 0, 0)))
|
||||||
|
.lifecycleDeclaration(new IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList.from(new IRLifecycleModuleDeclaration(new ModuleId(0), ReadOnlyList.empty())),
|
||||||
|
null,
|
||||||
|
new IRLifecycleCallableReference(
|
||||||
|
new ModuleId(0),
|
||||||
|
new FileId(1),
|
||||||
|
new CallableId(0),
|
||||||
|
IRLifecycleCallableRole.FRAME_ROOT,
|
||||||
|
span)))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackendExecutableFunction fn(
|
||||||
|
final String name,
|
||||||
|
final int moduleId,
|
||||||
|
final int callableId) {
|
||||||
|
final var span = new Span(new FileId(1), 1, 8);
|
||||||
|
return new IRBackendExecutableFunction(
|
||||||
|
new FileId(1),
|
||||||
|
new ModuleId(moduleId),
|
||||||
|
name,
|
||||||
|
new CallableId(callableId),
|
||||||
|
1,
|
||||||
|
8,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span)),
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,6 +34,7 @@ public class IRBackend implements IRBackendReader {
|
|||||||
private final ReadOnlyList<IntrinsicReference> intrinsicPool = ReadOnlyList.empty();
|
private final ReadOnlyList<IntrinsicReference> intrinsicPool = ReadOnlyList.empty();
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty();
|
private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty();
|
||||||
|
private final IRLifecycleDeclaration lifecycleDeclaration;
|
||||||
|
|
||||||
public static IRBackendAggregator aggregator() {
|
public static IRBackendAggregator aggregator() {
|
||||||
return new IRBackendAggregator();
|
return new IRBackendAggregator();
|
||||||
@ -53,6 +54,8 @@ public class IRBackend implements IRBackendReader {
|
|||||||
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
|
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
|
||||||
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
|
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
|
||||||
private final LinkedHashSet<String> requiredCapabilities = new LinkedHashSet<>();
|
private final LinkedHashSet<String> requiredCapabilities = new LinkedHashSet<>();
|
||||||
|
private final ArrayList<IRLifecycleFileDeclaration> lifecycleFiles = new ArrayList<>();
|
||||||
|
private ReadOnlyList<ModuleId> lifecycleModuleOrder = ReadOnlyList.empty();
|
||||||
|
|
||||||
public void merge(final IRBackendFile backendFile) {
|
public void merge(final IRBackendFile backendFile) {
|
||||||
if (backendFile == null) {
|
if (backendFile == null) {
|
||||||
@ -90,6 +93,12 @@ public class IRBackend implements IRBackendReader {
|
|||||||
for (final var function : backendFile.executableFunctions()) {
|
for (final var function : backendFile.executableFunctions()) {
|
||||||
executableFunctions.add(remapExecutableFunction(function, moduleRemap, callableRemap, intrinsicRemap));
|
executableFunctions.add(remapExecutableFunction(function, moduleRemap, callableRemap, intrinsicRemap));
|
||||||
}
|
}
|
||||||
|
if (backendFile.lifecycleDeclaration() != null && !backendFile.lifecycleDeclaration().isEmpty()) {
|
||||||
|
lifecycleFiles.add(remapLifecycleFileDeclaration(
|
||||||
|
backendFile.lifecycleDeclaration(),
|
||||||
|
moduleRemap,
|
||||||
|
callableRemap));
|
||||||
|
}
|
||||||
final var metadata = backendFile.reservedMetadata();
|
final var metadata = backendFile.reservedMetadata();
|
||||||
hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
|
hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
|
||||||
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
|
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
|
||||||
@ -111,6 +120,11 @@ public class IRBackend implements IRBackendReader {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IRBackendAggregator lifecycleModuleOrder(final ReadOnlyList<ModuleId> moduleOrder) {
|
||||||
|
lifecycleModuleOrder = moduleOrder == null ? ReadOnlyList.empty() : moduleOrder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private ModuleId[] reindexModules(final ReadOnlyList<ModuleReference> localModulePool) {
|
private ModuleId[] reindexModules(final ReadOnlyList<ModuleReference> localModulePool) {
|
||||||
if (localModulePool == null || localModulePool.isEmpty()) {
|
if (localModulePool == null || localModulePool.isEmpty()) {
|
||||||
return new ModuleId[0];
|
return new ModuleId[0];
|
||||||
@ -203,6 +217,37 @@ public class IRBackend implements IRBackendReader {
|
|||||||
function.span());
|
function.span());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IRLifecycleFileDeclaration remapLifecycleFileDeclaration(
|
||||||
|
final IRLifecycleFileDeclaration declaration,
|
||||||
|
final ModuleId[] moduleRemap,
|
||||||
|
final CallableId[] callableRemap) {
|
||||||
|
final var remappedModuleId = remapModuleId(
|
||||||
|
declaration.moduleId(),
|
||||||
|
moduleRemap,
|
||||||
|
"lifecycle file");
|
||||||
|
return new IRLifecycleFileDeclaration(
|
||||||
|
remappedModuleId,
|
||||||
|
declaration.fileId(),
|
||||||
|
remapLifecycleCallable(declaration.fileInitFragment(), moduleRemap, callableRemap),
|
||||||
|
remapLifecycleCallable(declaration.projectInit(), moduleRemap, callableRemap),
|
||||||
|
remapLifecycleCallable(declaration.frameRoot(), moduleRemap, callableRemap));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRLifecycleCallableReference remapLifecycleCallable(
|
||||||
|
final IRLifecycleCallableReference reference,
|
||||||
|
final ModuleId[] moduleRemap,
|
||||||
|
final CallableId[] callableRemap) {
|
||||||
|
if (reference == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new IRLifecycleCallableReference(
|
||||||
|
remapModuleId(reference.moduleId(), moduleRemap, "lifecycle callable"),
|
||||||
|
reference.fileId(),
|
||||||
|
remapCallableId(reference.callableId(), callableRemap, "lifecycle callable"),
|
||||||
|
reference.role(),
|
||||||
|
reference.origin());
|
||||||
|
}
|
||||||
|
|
||||||
private ModuleId remapModuleId(
|
private ModuleId remapModuleId(
|
||||||
final ModuleId localModuleId,
|
final ModuleId localModuleId,
|
||||||
final ModuleId[] moduleRemap,
|
final ModuleId[] moduleRemap,
|
||||||
@ -273,6 +318,52 @@ public class IRBackend implements IRBackendReader {
|
|||||||
return ReadOnlyList.wrap(pool);
|
return ReadOnlyList.wrap(pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IRLifecycleDeclaration emitLifecycleDeclaration() {
|
||||||
|
if (lifecycleFiles.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (lifecycleModuleOrder.isEmpty()) {
|
||||||
|
throw new IllegalStateException("lifecycle module order is required for executable handoff");
|
||||||
|
}
|
||||||
|
|
||||||
|
final var modules = new ArrayList<IRLifecycleModuleDeclaration>();
|
||||||
|
IRLifecycleCallableReference projectInit = null;
|
||||||
|
IRLifecycleCallableReference frameRoot = null;
|
||||||
|
for (final var moduleId : lifecycleModuleOrder) {
|
||||||
|
final var fragments = new ArrayList<IRLifecycleCallableReference>();
|
||||||
|
var hasLifecycleContribution = false;
|
||||||
|
for (final var lifecycleFile : lifecycleFiles) {
|
||||||
|
if (!sameModuleId(moduleId, lifecycleFile.moduleId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
hasLifecycleContribution = true;
|
||||||
|
if (lifecycleFile.fileInitFragment() != null) {
|
||||||
|
fragments.add(lifecycleFile.fileInitFragment());
|
||||||
|
}
|
||||||
|
if (projectInit == null && lifecycleFile.projectInit() != null) {
|
||||||
|
projectInit = lifecycleFile.projectInit();
|
||||||
|
}
|
||||||
|
if (frameRoot == null && lifecycleFile.frameRoot() != null) {
|
||||||
|
frameRoot = lifecycleFile.frameRoot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasLifecycleContribution) {
|
||||||
|
modules.add(new IRLifecycleModuleDeclaration(moduleId, ReadOnlyList.wrap(fragments)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new IRLifecycleDeclaration(ReadOnlyList.wrap(modules), projectInit, frameRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sameModuleId(final ModuleId left, final ModuleId right) {
|
||||||
|
if (left == null || right == null) {
|
||||||
|
return left == right;
|
||||||
|
}
|
||||||
|
if (left.isNone() || right.isNone()) {
|
||||||
|
return left.isNone() && right.isNone();
|
||||||
|
}
|
||||||
|
return left.getIndex() == right.getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
public IRBackend emit() {
|
public IRBackend emit() {
|
||||||
return IRBackend
|
return IRBackend
|
||||||
.builder()
|
.builder()
|
||||||
@ -290,6 +381,7 @@ public class IRBackend implements IRBackendReader {
|
|||||||
ReadOnlyList.wrap(builtinTypeSurfaces),
|
ReadOnlyList.wrap(builtinTypeSurfaces),
|
||||||
ReadOnlyList.wrap(builtinConstSurfaces),
|
ReadOnlyList.wrap(builtinConstSurfaces),
|
||||||
ReadOnlyList.wrap(requiredCapabilities.stream().toList())))
|
ReadOnlyList.wrap(requiredCapabilities.stream().toList())))
|
||||||
|
.lifecycleDeclaration(emitLifecycleDeclaration())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,6 +413,7 @@ public class IRBackend implements IRBackendReader {
|
|||||||
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
|
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
|
||||||
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())
|
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())
|
||||||
.append(", requiredCapabilities=").append(reservedMetadata.requiredCapabilities().size())
|
.append(", requiredCapabilities=").append(reservedMetadata.requiredCapabilities().size())
|
||||||
|
.append(", lifecycle=").append(lifecycleDeclaration == null ? "none" : "declared")
|
||||||
.append('}');
|
.append('}');
|
||||||
|
|
||||||
if (functions.isEmpty()) {
|
if (functions.isEmpty()) {
|
||||||
|
|||||||
@ -17,7 +17,8 @@ public record IRBackendFile(
|
|||||||
IRReservedMetadata reservedMetadata,
|
IRReservedMetadata reservedMetadata,
|
||||||
ReadOnlyList<ModuleReference> modulePool,
|
ReadOnlyList<ModuleReference> modulePool,
|
||||||
ReadOnlyList<CallableSignatureRef> callableSignatures,
|
ReadOnlyList<CallableSignatureRef> callableSignatures,
|
||||||
ReadOnlyList<IntrinsicReference> intrinsicPool) {
|
ReadOnlyList<IntrinsicReference> intrinsicPool,
|
||||||
|
IRLifecycleFileDeclaration lifecycleDeclaration) {
|
||||||
public IRBackendFile {
|
public IRBackendFile {
|
||||||
fileId = Objects.requireNonNull(fileId, "fileId");
|
fileId = Objects.requireNonNull(fileId, "fileId");
|
||||||
functions = functions == null ? ReadOnlyList.empty() : functions;
|
functions = functions == null ? ReadOnlyList.empty() : functions;
|
||||||
@ -30,6 +31,29 @@ public record IRBackendFile(
|
|||||||
intrinsicPool = intrinsicPool == null ? ReadOnlyList.empty() : intrinsicPool;
|
intrinsicPool = intrinsicPool == null ? ReadOnlyList.empty() : intrinsicPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IRBackendFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final ReadOnlyList<IRFunction> functions,
|
||||||
|
final ReadOnlyList<IRSyntheticFunction> syntheticFunctions,
|
||||||
|
final ReadOnlyList<IRGlobal> globals,
|
||||||
|
final ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
|
||||||
|
final IRReservedMetadata reservedMetadata,
|
||||||
|
final ReadOnlyList<ModuleReference> modulePool,
|
||||||
|
final ReadOnlyList<CallableSignatureRef> callableSignatures,
|
||||||
|
final ReadOnlyList<IntrinsicReference> intrinsicPool) {
|
||||||
|
this(
|
||||||
|
fileId,
|
||||||
|
functions,
|
||||||
|
syntheticFunctions,
|
||||||
|
globals,
|
||||||
|
executableFunctions,
|
||||||
|
reservedMetadata,
|
||||||
|
modulePool,
|
||||||
|
callableSignatures,
|
||||||
|
intrinsicPool,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
public IRBackendFile(
|
public IRBackendFile(
|
||||||
final FileId fileId,
|
final FileId fileId,
|
||||||
final ReadOnlyList<IRFunction> functions) {
|
final ReadOnlyList<IRFunction> functions) {
|
||||||
@ -42,7 +66,8 @@ public record IRBackendFile(
|
|||||||
IRReservedMetadata.empty(),
|
IRReservedMetadata.empty(),
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
ReadOnlyList.empty());
|
ReadOnlyList.empty(),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IRBackendFile empty(final FileId fileId) {
|
public static IRBackendFile empty(final FileId fileId) {
|
||||||
@ -55,7 +80,8 @@ public record IRBackendFile(
|
|||||||
IRReservedMetadata.empty(),
|
IRReservedMetadata.empty(),
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
ReadOnlyList.empty());
|
ReadOnlyList.empty(),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRBackendFile(
|
public IRBackendFile(
|
||||||
@ -71,7 +97,8 @@ public record IRBackendFile(
|
|||||||
reservedMetadata,
|
reservedMetadata,
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
ReadOnlyList.empty());
|
ReadOnlyList.empty(),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRBackendFile(
|
public IRBackendFile(
|
||||||
@ -90,7 +117,8 @@ public record IRBackendFile(
|
|||||||
reservedMetadata,
|
reservedMetadata,
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
callableSignatures,
|
callableSignatures,
|
||||||
intrinsicPool);
|
intrinsicPool,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRBackendFile(
|
public IRBackendFile(
|
||||||
@ -110,6 +138,7 @@ public record IRBackendFile(
|
|||||||
reservedMetadata,
|
reservedMetadata,
|
||||||
modulePool,
|
modulePool,
|
||||||
callableSignatures,
|
callableSignatures,
|
||||||
intrinsicPool);
|
intrinsicPool,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,4 +17,5 @@ public interface IRBackendReader {
|
|||||||
ReadOnlyList<CallableSignatureRef> getCallableSignatures();
|
ReadOnlyList<CallableSignatureRef> getCallableSignatures();
|
||||||
ReadOnlyList<IntrinsicReference> getIntrinsicPool();
|
ReadOnlyList<IntrinsicReference> getIntrinsicPool();
|
||||||
IRReservedMetadata getReservedMetadata();
|
IRReservedMetadata getReservedMetadata();
|
||||||
|
IRLifecycleDeclaration getLifecycleDeclaration();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
package p.studio.compiler.models;
|
||||||
|
|
||||||
|
import p.studio.compiler.source.Span;
|
||||||
|
import p.studio.compiler.source.identifiers.CallableId;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record IRLifecycleCallableReference(
|
||||||
|
ModuleId moduleId,
|
||||||
|
FileId fileId,
|
||||||
|
CallableId callableId,
|
||||||
|
IRLifecycleCallableRole role,
|
||||||
|
Span origin) {
|
||||||
|
public IRLifecycleCallableReference {
|
||||||
|
moduleId = Objects.requireNonNull(moduleId, "moduleId");
|
||||||
|
fileId = Objects.requireNonNull(fileId, "fileId");
|
||||||
|
callableId = Objects.requireNonNull(callableId, "callableId");
|
||||||
|
role = Objects.requireNonNull(role, "role");
|
||||||
|
origin = origin == null ? Span.none() : origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package p.studio.compiler.models;
|
||||||
|
|
||||||
|
public enum IRLifecycleCallableRole {
|
||||||
|
FILE_INIT_FRAGMENT,
|
||||||
|
PROJECT_INIT,
|
||||||
|
FRAME_ROOT
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package p.studio.compiler.models;
|
||||||
|
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
public record IRLifecycleDeclaration(
|
||||||
|
ReadOnlyList<IRLifecycleModuleDeclaration> modules,
|
||||||
|
IRLifecycleCallableReference projectInit,
|
||||||
|
IRLifecycleCallableReference frameRoot) {
|
||||||
|
public IRLifecycleDeclaration {
|
||||||
|
modules = modules == null ? ReadOnlyList.empty() : modules;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package p.studio.compiler.models;
|
||||||
|
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record IRLifecycleFileDeclaration(
|
||||||
|
ModuleId moduleId,
|
||||||
|
FileId fileId,
|
||||||
|
IRLifecycleCallableReference fileInitFragment,
|
||||||
|
IRLifecycleCallableReference projectInit,
|
||||||
|
IRLifecycleCallableReference frameRoot) {
|
||||||
|
public IRLifecycleFileDeclaration {
|
||||||
|
moduleId = Objects.requireNonNull(moduleId, "moduleId");
|
||||||
|
fileId = Objects.requireNonNull(fileId, "fileId");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return fileInitFragment == null && projectInit == null && frameRoot == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package p.studio.compiler.models;
|
||||||
|
|
||||||
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record IRLifecycleModuleDeclaration(
|
||||||
|
ModuleId moduleId,
|
||||||
|
ReadOnlyList<IRLifecycleCallableReference> fileInitFragments) {
|
||||||
|
public IRLifecycleModuleDeclaration {
|
||||||
|
moduleId = Objects.requireNonNull(moduleId, "moduleId");
|
||||||
|
fileInitFragments = fileInitFragments == null ? ReadOnlyList.empty() : fileInitFragments;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -58,6 +58,11 @@ class IRBackendExecutableContractTest {
|
|||||||
IRGlobalOrigin.class,
|
IRGlobalOrigin.class,
|
||||||
IRGlobalVisibility.class,
|
IRGlobalVisibility.class,
|
||||||
IRHiddenGlobalKind.class,
|
IRHiddenGlobalKind.class,
|
||||||
|
IRLifecycleDeclaration.class,
|
||||||
|
IRLifecycleModuleDeclaration.class,
|
||||||
|
IRLifecycleFileDeclaration.class,
|
||||||
|
IRLifecycleCallableReference.class,
|
||||||
|
IRLifecycleCallableRole.class,
|
||||||
IRReservedMetadata.class,
|
IRReservedMetadata.class,
|
||||||
IRReservedMetadata.HostMethodBinding.class,
|
IRReservedMetadata.HostMethodBinding.class,
|
||||||
IRReservedMetadata.BuiltinTypeSurface.class,
|
IRReservedMetadata.BuiltinTypeSurface.class,
|
||||||
@ -80,7 +85,12 @@ class IRBackendExecutableContractTest {
|
|||||||
IRReservedMetadata.class,
|
IRReservedMetadata.class,
|
||||||
IRSyntheticCallableKind.class,
|
IRSyntheticCallableKind.class,
|
||||||
IRSyntheticFunction.class,
|
IRSyntheticFunction.class,
|
||||||
IRSyntheticOrigin.class);
|
IRSyntheticOrigin.class,
|
||||||
|
IRLifecycleDeclaration.class,
|
||||||
|
IRLifecycleModuleDeclaration.class,
|
||||||
|
IRLifecycleFileDeclaration.class,
|
||||||
|
IRLifecycleCallableReference.class,
|
||||||
|
IRLifecycleCallableRole.class);
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
PUBLIC_CONTRACT_TYPES.containsAll(expectedTopLevelModelTypes),
|
PUBLIC_CONTRACT_TYPES.containsAll(expectedTopLevelModelTypes),
|
||||||
@ -137,6 +147,14 @@ class IRBackendExecutableContractTest {
|
|||||||
assertEquals(FileId.class, recordComponentType(IRSyntheticOrigin.class, "fileId"));
|
assertEquals(FileId.class, recordComponentType(IRSyntheticOrigin.class, "fileId"));
|
||||||
assertEquals(ModuleId.class, recordComponentType(IRSyntheticFunction.class, "moduleId"));
|
assertEquals(ModuleId.class, recordComponentType(IRSyntheticFunction.class, "moduleId"));
|
||||||
|
|
||||||
|
assertEquals(ModuleId.class, recordComponentType(IRLifecycleModuleDeclaration.class, "moduleId"));
|
||||||
|
assertEquals(ModuleId.class, recordComponentType(IRLifecycleFileDeclaration.class, "moduleId"));
|
||||||
|
assertEquals(FileId.class, recordComponentType(IRLifecycleFileDeclaration.class, "fileId"));
|
||||||
|
assertEquals(ModuleId.class, recordComponentType(IRLifecycleCallableReference.class, "moduleId"));
|
||||||
|
assertEquals(FileId.class, recordComponentType(IRLifecycleCallableReference.class, "fileId"));
|
||||||
|
assertEquals(CallableId.class, recordComponentType(IRLifecycleCallableReference.class, "callableId"));
|
||||||
|
assertEquals(Span.class, recordComponentType(IRLifecycleCallableReference.class, "origin"));
|
||||||
|
|
||||||
assertEquals(String.class, recordComponentType(ModuleReference.class, "project"));
|
assertEquals(String.class, recordComponentType(ModuleReference.class, "project"));
|
||||||
assertEquals(String.class, recordComponentType(CallableSignatureRef.class, "callableName"));
|
assertEquals(String.class, recordComponentType(CallableSignatureRef.class, "callableName"));
|
||||||
assertEquals(String.class, recordComponentType(IntrinsicReference.class, "canonicalName"));
|
assertEquals(String.class, recordComponentType(IntrinsicReference.class, "canonicalName"));
|
||||||
@ -640,6 +658,146 @@ class IRBackendExecutableContractTest {
|
|||||||
second.getExecutableFunctions().get(1).callableId());
|
second.getExecutableFunctions().get(1).callableId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void aggregatorMustRemapTypedLifecycleReferencesAndPreserveDeclaredModuleOrder() {
|
||||||
|
final var fileASpan = new Span(new FileId(10), 1, 8);
|
||||||
|
final var fileBSpan = new Span(new FileId(20), 2, 9);
|
||||||
|
final var fileA = new IRBackendFile(
|
||||||
|
new FileId(10),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(new IRSyntheticFunction(
|
||||||
|
new ModuleId(0),
|
||||||
|
"file_init_zzz",
|
||||||
|
IRSyntheticCallableKind.FILE_INIT_FRAGMENT,
|
||||||
|
new IRSyntheticOrigin(new FileId(10), "warmup", fileASpan))),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(executable("file_init_zzz", 10, 0, 0, fileASpan)),
|
||||||
|
IRReservedMetadata.empty(),
|
||||||
|
ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("zzz"))),
|
||||||
|
ReadOnlyList.from(new CallableSignatureRef(new ModuleId(0), "file_init_zzz", 0, "() -> unit")),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
new IRLifecycleFileDeclaration(
|
||||||
|
new ModuleId(0),
|
||||||
|
new FileId(10),
|
||||||
|
new IRLifecycleCallableReference(
|
||||||
|
new ModuleId(0),
|
||||||
|
new FileId(10),
|
||||||
|
new CallableId(0),
|
||||||
|
IRLifecycleCallableRole.FILE_INIT_FRAGMENT,
|
||||||
|
fileASpan),
|
||||||
|
null,
|
||||||
|
null));
|
||||||
|
final var fileB = new IRBackendFile(
|
||||||
|
new FileId(20),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(
|
||||||
|
executable("boot", 20, 0, 0, fileBSpan),
|
||||||
|
executable("frame", 20, 0, 1, fileBSpan)),
|
||||||
|
IRReservedMetadata.empty(),
|
||||||
|
ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("aaa"))),
|
||||||
|
ReadOnlyList.from(
|
||||||
|
new CallableSignatureRef(new ModuleId(0), "boot", 0, "() -> unit"),
|
||||||
|
new CallableSignatureRef(new ModuleId(0), "frame", 0, "() -> unit")),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
new IRLifecycleFileDeclaration(
|
||||||
|
new ModuleId(0),
|
||||||
|
new FileId(20),
|
||||||
|
null,
|
||||||
|
new IRLifecycleCallableReference(
|
||||||
|
new ModuleId(0),
|
||||||
|
new FileId(20),
|
||||||
|
new CallableId(0),
|
||||||
|
IRLifecycleCallableRole.PROJECT_INIT,
|
||||||
|
fileBSpan),
|
||||||
|
new IRLifecycleCallableReference(
|
||||||
|
new ModuleId(0),
|
||||||
|
new FileId(20),
|
||||||
|
new CallableId(1),
|
||||||
|
IRLifecycleCallableRole.FRAME_ROOT,
|
||||||
|
fileBSpan)));
|
||||||
|
|
||||||
|
final var aggregator = IRBackend.aggregator();
|
||||||
|
aggregator.merge(fileA);
|
||||||
|
aggregator.merge(fileB);
|
||||||
|
aggregator.lifecycleModuleOrder(ReadOnlyList.from(new ModuleId(1), new ModuleId(0)));
|
||||||
|
final var backend = aggregator.emit();
|
||||||
|
final var declaration = backend.getLifecycleDeclaration();
|
||||||
|
|
||||||
|
assertNotNull(declaration);
|
||||||
|
assertEquals(2, declaration.modules().size());
|
||||||
|
assertEquals(1, declaration.modules().get(0).moduleId().getIndex());
|
||||||
|
assertEquals(0, declaration.modules().get(1).moduleId().getIndex());
|
||||||
|
assertEquals("aaa", backend.getModulePool().get(1).pathSegments().getFirst());
|
||||||
|
assertEquals("zzz", backend.getModulePool().get(0).pathSegments().getFirst());
|
||||||
|
assertEquals(IRLifecycleCallableRole.FILE_INIT_FRAGMENT, declaration.modules().get(1).fileInitFragments().getFirst().role());
|
||||||
|
assertEquals(0, declaration.modules().get(1).fileInitFragments().getFirst().callableId().getIndex());
|
||||||
|
assertEquals(1, declaration.projectInit().callableId().getIndex());
|
||||||
|
assertEquals(2, declaration.frameRoot().callableId().getIndex());
|
||||||
|
assertEquals(20, declaration.frameRoot().fileId().getId());
|
||||||
|
assertEquals(2, declaration.frameRoot().origin().getStart());
|
||||||
|
assertEquals("boot", backend.getExecutableFunctions().get(1).callableName());
|
||||||
|
assertEquals("frame", backend.getExecutableFunctions().get(2).callableName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void aggregatorMustOmitLifecycleDeclarationWhenNoFileContributesOne() {
|
||||||
|
final var aggregator = IRBackend.aggregator();
|
||||||
|
aggregator.merge(new IRBackendFile(
|
||||||
|
new FileId(1),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(new IRBackendExecutableFunction(
|
||||||
|
new FileId(1),
|
||||||
|
"entry",
|
||||||
|
new CallableId(0),
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Span.none())),
|
||||||
|
Span.none())),
|
||||||
|
IRReservedMetadata.empty(),
|
||||||
|
ReadOnlyList.from(new CallableSignatureRef("entry", 0, "() -> unit")),
|
||||||
|
ReadOnlyList.empty()));
|
||||||
|
|
||||||
|
final var backend = aggregator.emit();
|
||||||
|
assertNull(backend.getLifecycleDeclaration());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRBackendExecutableFunction executable(
|
||||||
|
final String name,
|
||||||
|
final int fileId,
|
||||||
|
final int moduleId,
|
||||||
|
final int callableId,
|
||||||
|
final Span span) {
|
||||||
|
return new IRBackendExecutableFunction(
|
||||||
|
new FileId(fileId),
|
||||||
|
new ModuleId(moduleId),
|
||||||
|
name,
|
||||||
|
new CallableId(callableId),
|
||||||
|
(int) span.getStart(),
|
||||||
|
(int) span.getEnd(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
span)),
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
private static IRBackend emitDeterministicAggregateFixture() {
|
private static IRBackend emitDeterministicAggregateFixture() {
|
||||||
final var aggregator = IRBackend.aggregator();
|
final var aggregator = IRBackend.aggregator();
|
||||||
aggregator.merge(new IRBackendFile(
|
aggregator.merge(new IRBackendFile(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user