new test main with code directly (fixes)
This commit is contained in:
parent
14f9b5dd26
commit
5630f8f119
@ -71,7 +71,7 @@ The following inputs are already fixed elsewhere and must not be contradicted he
|
|||||||
- Imports target modules, not files.
|
- Imports target modules, not files.
|
||||||
- Reserved stdlib project spaces are resolved only from the selected stdlib environment.
|
- Reserved stdlib project spaces are resolved only from the selected stdlib environment.
|
||||||
- Only `pub` symbols may be imported from another module.
|
- Only `pub` symbols may be imported from another module.
|
||||||
- Reserved stdlib/interface modules may expose compile-time-only shells for both host-backed and VM-owned surfaces.
|
- Reserved stdlib/interface modules may expose compile-time-only shells for both host-backed and VM-owned surfaces, and may expose service facades over non-public host owners.
|
||||||
- Source-visible builtin names are resolved through imported builtin shell declarations rather than by hardcoded source spelling alone.
|
- Source-visible builtin names are resolved through imported builtin shell declarations rather than by hardcoded source spelling alone.
|
||||||
- Canonical builtin identity and canonical intrinsic identity are governed by builtin metadata rather than by the imported PBS-visible declaration name alone.
|
- Canonical builtin identity and canonical intrinsic identity are governed by builtin metadata rather than by the imported PBS-visible declaration name alone.
|
||||||
- Canonical host identity is governed by host metadata rather than by imported owner spelling alone.
|
- Canonical host identity is governed by host metadata rather than by imported owner spelling alone.
|
||||||
|
|||||||
@ -53,6 +53,7 @@ The following inputs are already fixed elsewhere and must not be contradicted he
|
|||||||
|
|
||||||
- Reserved stdlib project spaces include `@sdk:*` and `@core:*`.
|
- Reserved stdlib project spaces include `@sdk:*` and `@core:*`.
|
||||||
- Stdlib interface modules are compile-time-only and loaded as PBS-like modules.
|
- Stdlib interface modules are compile-time-only and loaded as PBS-like modules.
|
||||||
|
- Stdlib interface modules may include service facades whose bodies delegate to reserved host owners; such bodies are source-surface ergonomics and not standalone runtime modules.
|
||||||
- User code must import stdlib modules explicitly.
|
- User code must import stdlib modules explicitly.
|
||||||
- Canonical host identity comes from reserved host-binding metadata, not from source owner spelling.
|
- Canonical host identity comes from reserved host-binding metadata, not from source owner spelling.
|
||||||
- VM-owned builtin type, builtin constant, and intrinsic surfaces may be exposed through reserved stdlib shells without transferring semantic ownership away from the VM.
|
- VM-owned builtin type, builtin constant, and intrinsic surfaces may be exposed through reserved stdlib shells without transferring semantic ownership away from the VM.
|
||||||
@ -149,7 +150,9 @@ shells.
|
|||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
- host-backed stdlib surfaces use `declare host`,
|
- host-backed stdlib surfaces use `declare host`,
|
||||||
|
- host-backed stdlib modules may also expose `declare service` facades that call internal non-public host owners,
|
||||||
- host-backed method signatures carry `Host(...)`,
|
- host-backed method signatures carry `Host(...)`,
|
||||||
|
- internal low-level host owners can remain non-public via `mod` barrel visibility while higher-level services are exported with `pub`,
|
||||||
- user code may import those host owners through ordinary stdlib module imports,
|
- user code may import those host owners through ordinary stdlib module imports,
|
||||||
- and lowering continues through canonical host identity, `SYSC`, `HOSTCALL`, loader resolution, and final `SYSCALL`.
|
- and lowering continues through canonical host identity, `SYSC`, `HOSTCALL`, loader resolution, and final `SYSCALL`.
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,8 @@ Lowering from `IRBackend` to `IRVM` may start only when:
|
|||||||
2. required callsite categories (`CALL_FUNC`/`CALL_HOST`/`CALL_INTRINSIC`) are available,
|
2. required callsite categories (`CALL_FUNC`/`CALL_HOST`/`CALL_INTRINSIC`) are available,
|
||||||
3. required canonical host/intrinsic metadata is available for admitted callsites,
|
3. required canonical host/intrinsic metadata is available for admitted callsites,
|
||||||
4. dependency-scoped fail-fast exclusions have already been applied at the `IRBackend` boundary,
|
4. dependency-scoped fail-fast exclusions have already been applied at the `IRBackend` boundary,
|
||||||
5. and target `vm_profile` is selected deterministically.
|
5. frontend-declared `IRBackend.entryPointCallableName` is present and non-blank,
|
||||||
|
6. and target `vm_profile` is selected deterministically.
|
||||||
|
|
||||||
## 6. IRVM Model Obligations
|
## 6. IRVM Model Obligations
|
||||||
|
|
||||||
@ -91,9 +92,10 @@ Control-flow lowering must satisfy:
|
|||||||
|
|
||||||
Function indexing must be deterministic:
|
Function indexing must be deterministic:
|
||||||
|
|
||||||
1. entrypoint function id is `0`,
|
1. entrypoint callable is selected by exact name match against `IRBackend.entryPointCallableName`,
|
||||||
2. remaining functions are ordered by `(module_key, callable_name, source_start)`,
|
2. selected entrypoint function id is `0`,
|
||||||
3. and identical admitted input graphs must produce identical function-id assignments.
|
3. remaining functions are ordered by `(module_key, callable_name, source_start)`,
|
||||||
|
4. and identical admitted input graphs must produce identical function-id assignments.
|
||||||
|
|
||||||
## 9. Callsite Lowering Obligations
|
## 9. Callsite Lowering Obligations
|
||||||
|
|
||||||
|
|||||||
@ -132,3 +132,4 @@ Foco: eliminar os dois `partial` remanescentes na matriz backend antes de mover
|
|||||||
|
|
||||||
1. `PR-07.1-optimize-irvm-span-source-hook-preservation.md`
|
1. `PR-07.1-optimize-irvm-span-source-hook-preservation.md`
|
||||||
2. `PR-07.2-pbs-callsite-category-no-textual-heuristics-regression.md`
|
2. `PR-07.2-pbs-callsite-category-no-textual-heuristics-regression.md`
|
||||||
|
3. `PR-07.3-irvm-entrypoint-export-emission-for-runtime-loader.md`
|
||||||
|
|||||||
@ -0,0 +1,77 @@
|
|||||||
|
# PR-07.3 - IRVM Entrypoint Export Emission for Runtime Loader Compatibility
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
Hoje um cartridge pode compilar com `entrypoint: "frame"` no `manifest.json` e `pub fn frame()` no `mod.barrel`, mas ainda falhar em runtime com `EntrypointNotFound`.
|
||||||
|
|
||||||
|
A causa observada e que o `program.pbx` pode ser emitido sem secao `exports` (section kind `4`), enquanto o loader resolve entrypoint nominal usando `program.exports`.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
### Dor atual que esta PR resolve
|
||||||
|
|
||||||
|
1. Build aparentemente valido, mas falha de bootstrap em runtime.
|
||||||
|
2. Divergencia entre contrato de manifesto (`entrypoint` nominal) e artefato PBX emitido.
|
||||||
|
3. Falta de evidencia automatizada para evitar regressao de exports no caminho `IRBackend -> IRVM -> Bytecode`.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
Corrigir o pipeline backend para sempre emitir export nominal do entrypoint no PBX de preload, garantindo compatibilidade com o loader quando o `manifest.json` declara entrypoint por nome.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Prerequisitos diretos:
|
||||||
|
|
||||||
|
1. `PR-05.3` (politica deterministica de entrypoint no indice `0`).
|
||||||
|
2. `PR-05.4` (coerencia unica entre IRVM e EmissionPlan).
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
1. `LowerToIRVMService` deve preencher `EmissionPlan.exports` com o simbolo do entrypoint apontando para `func_idx = 0`.
|
||||||
|
2. Adicionar regressao dedicada para garantir que `coherentEmissionPlan()` preserva export do entrypoint.
|
||||||
|
3. Validar serializacao PBX com secao `exports` presente quando houver entrypoint nominal.
|
||||||
|
4. Cobrir cenario de integracao onde `manifest.entrypoint = "frame"` inicializa VM sem `EntrypointNotFound`.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
1. Nao exportar toda a superficie de funcoes executaveis neste passo.
|
||||||
|
2. Nao alterar formato binario PBX alem da presenca correta da secao `exports`.
|
||||||
|
3. Nao mudar semantica de resolucao por indice numerico de entrypoint.
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
### O que deve ser feito explicitamente
|
||||||
|
|
||||||
|
1. No `LowerToIRVMService`, construir `BytecodeEmitter.EmissionPlan` com `exports` contendo, no minimo, `Export(entryPointCallableName, 0)`.
|
||||||
|
2. Garantir determinismo do export do entrypoint a partir da mesma politica que fixa `func_id = 0`.
|
||||||
|
3. Adicionar teste unitario em `LowerToIRVMServiceTest` para verificar export nominal do entrypoint no plano coerente.
|
||||||
|
4. Adicionar teste de emissao/link para verificar secao `exports` no modulo serializado.
|
||||||
|
5. Adicionar teste de integracao runtime-backed para falhar se regressar para `EntrypointNotFound` em cartridge com entrypoint nominal valido.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. Build de projeto PBS com entrypoint `frame` gera PBX com secao `exports`.
|
||||||
|
2. O simbolo `frame` aponta para `func_idx = 0` no artefato emitido.
|
||||||
|
3. Execucao de cartridge com `manifest.entrypoint = "frame"` nao falha por `EntrypointNotFound` quando o programa contem o entrypoint admitido.
|
||||||
|
4. Rebuilds equivalentes produzem mapping de export deterministico.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
1. Novos testes de backend:
|
||||||
|
- `:prometeu-compiler:prometeu-build-pipeline:test`
|
||||||
|
2. Reexecucao de compatibilidade runtime-backed:
|
||||||
|
- `:prometeu-compiler:prometeu-build-pipeline:test --tests *Runtime*`
|
||||||
|
3. Smoke local recomendado:
|
||||||
|
- `./runtime/prometeu build .`
|
||||||
|
- `./runtime/prometeu run cartridge`
|
||||||
|
|
||||||
|
## Affected Documents
|
||||||
|
|
||||||
|
1. `docs/general/specs/15. Bytecode and PBX Mapping Specification.md`
|
||||||
|
2. `docs/general/specs/20. IRBackend to IRVM Lowering Specification.md`
|
||||||
|
3. `docs/pbs/specs/7. Cartridge Manifest and Runtime Capabilities Specification.md`
|
||||||
|
4. `docs/general/specs/22. Backend Spec-to-Test Conformance Matrix.md`
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
1. Em evolucao futura, devemos exportar apenas o entrypoint ou tambem funcoes explicitamente marcadas para linking externo?
|
||||||
@ -78,6 +78,7 @@ For each admitted source unit and callable in the current lowering slice, `IRBac
|
|||||||
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. frontend-declared executable entrypoint callable name from `FrontendSpec` (`entryPointCallableName`) for backend handoff.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@ -194,3 +195,13 @@ It does not replace:
|
|||||||
1. `docs/general/specs/20. IRBackend to IRVM Lowering Specification.md`,
|
1. `docs/general/specs/20. IRBackend to IRVM Lowering Specification.md`,
|
||||||
2. `docs/general/specs/21. IRVM Optimization Pipeline Specification.md`,
|
2. `docs/general/specs/21. IRVM Optimization Pipeline Specification.md`,
|
||||||
3. or `docs/general/specs/15. Bytecode and PBX Mapping Specification.md`.
|
3. or `docs/general/specs/15. Bytecode and PBX Mapping Specification.md`.
|
||||||
|
|
||||||
|
### 12.7 FrontendSpec Entry Point Obligation
|
||||||
|
|
||||||
|
For executable language frontends, `FrontendSpec` must declare one canonical entrypoint callable name (`entryPointCallableName`).
|
||||||
|
|
||||||
|
At `IRBackend` emission time:
|
||||||
|
|
||||||
|
1. frontend must copy this declaration into `IRBackend.entryPointCallableName`,
|
||||||
|
2. value must be non-blank and deterministic for the same language/spec version,
|
||||||
|
3. and backend stages must treat this field as authoritative for entrypoint selection instead of hardcoded language-agnostic names.
|
||||||
|
|||||||
@ -19,7 +19,8 @@ This document is the authority for how the compiler discovers and loads:
|
|||||||
|
|
||||||
- ordinary project modules,
|
- ordinary project modules,
|
||||||
- reserved stdlib modules such as `@sdk:gfx`,
|
- reserved stdlib modules such as `@sdk:gfx`,
|
||||||
- and compile-time-only host-binding metadata attached to those reserved modules.
|
- compile-time reserved metadata attached to those reserved modules,
|
||||||
|
- and interface-only service facades that wrap host surfaces for source ergonomics.
|
||||||
|
|
||||||
This document does not define runtime load behavior.
|
This document does not define runtime load behavior.
|
||||||
PBX host-binding emission and loader-side syscall resolution are defined by the Host ABI Binding and Loader Resolution Specification.
|
PBX host-binding emission and loader-side syscall resolution are defined by the Host ABI Binding and Loader Resolution Specification.
|
||||||
@ -445,7 +446,7 @@ The current PBS design direction is:
|
|||||||
6. The root project selects the effective stdlib line for the whole build.
|
6. The root project selects the effective stdlib line for the whole build.
|
||||||
7. Dependency projects with a higher stdlib requirement than the root are rejected.
|
7. Dependency projects with a higher stdlib requirement than the root are rejected.
|
||||||
8. Stdlib modules are loaded as PBS-like interface modules, not as ordinary dependencies.
|
8. Stdlib modules are loaded as PBS-like interface modules, not as ordinary dependencies.
|
||||||
9. Interface modules are compile-time-only and do not emit executable bytecode.
|
9. Interface modules are compile-time-only in packaging terms: they may carry declarative service facades with method bodies for source-level API ergonomics, but they still do not emit executable bytecode as standalone modules.
|
||||||
10. `declare host` remains reserved to stdlib/toolchain interface modules.
|
10. `declare host` remains reserved to stdlib/toolchain interface modules.
|
||||||
11. Reserved attributes such as `[Host(...)]` are compile-time metadata, not runtime objects.
|
11. Reserved attributes such as `[Host(...)]` are compile-time metadata, not runtime objects.
|
||||||
12. The compiler consumes `Host` metadata to produce runtime-facing host-binding artifacts defined elsewhere.
|
12. The compiler consumes `Host` metadata to produce runtime-facing host-binding artifacts defined elsewhere.
|
||||||
|
|||||||
@ -63,7 +63,7 @@ The current manifest shape relevant to this document is:
|
|||||||
"title": "Example",
|
"title": "Example",
|
||||||
"app_version": "1.0.0",
|
"app_version": "1.0.0",
|
||||||
"app_mode": "game",
|
"app_mode": "game",
|
||||||
"entrypoint": "main",
|
"entrypoint": "frame",
|
||||||
"capabilities": ["gfx", "input"],
|
"capabilities": ["gfx", "input"],
|
||||||
"asset_table": [],
|
"asset_table": [],
|
||||||
"preload": []
|
"preload": []
|
||||||
@ -75,6 +75,7 @@ Rules:
|
|||||||
- `magic` identifies a Prometeu cartridge manifest,
|
- `magic` identifies a Prometeu cartridge manifest,
|
||||||
- `cartridge_version` identifies the cartridge manifest format line,
|
- `cartridge_version` identifies the cartridge manifest format line,
|
||||||
- `entrypoint` identifies the runtime entrypoint,
|
- `entrypoint` identifies the runtime entrypoint,
|
||||||
|
- for PBS v1, `entrypoint` should align with frontend-declared callable (`frame`),
|
||||||
- `capabilities` declares the cartridge's requested runtime capabilities,
|
- `capabilities` declares the cartridge's requested runtime capabilities,
|
||||||
- `asset_table` and `preload` remain separate concerns.
|
- `asset_table` and `preload` remain separate concerns.
|
||||||
|
|
||||||
|
|||||||
@ -9,5 +9,6 @@ public class PBSDefinitions {
|
|||||||
.languageId("pbs")
|
.languageId("pbs")
|
||||||
.allowedExtensions(ReadOnlySet.from("pbs", "barrel"))
|
.allowedExtensions(ReadOnlySet.from("pbs", "barrel"))
|
||||||
.sourceRoots(ReadOnlySet.from("src"))
|
.sourceRoots(ReadOnlySet.from("src"))
|
||||||
|
.entryPointCallableName("frame")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,10 +120,43 @@ public final class PbsFrontendCompiler {
|
|||||||
final String moduleKey,
|
final String moduleKey,
|
||||||
final HostAdmissionContext hostAdmissionContext,
|
final HostAdmissionContext hostAdmissionContext,
|
||||||
final NameTable nameTable) {
|
final NameTable nameTable) {
|
||||||
|
return compileParsedFile(
|
||||||
|
fileId,
|
||||||
|
ast,
|
||||||
|
diagnostics,
|
||||||
|
sourceKind,
|
||||||
|
moduleKey,
|
||||||
|
hostAdmissionContext,
|
||||||
|
nameTable,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
IRReservedMetadata.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRBackendFile compileParsedFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final DiagnosticSink diagnostics,
|
||||||
|
final SourceKind sourceKind,
|
||||||
|
final String moduleKey,
|
||||||
|
final HostAdmissionContext hostAdmissionContext,
|
||||||
|
final NameTable nameTable,
|
||||||
|
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||||
|
final ReadOnlyList<ImportedCallableSurface> importedCallables,
|
||||||
|
final IRReservedMetadata importedReservedMetadata) {
|
||||||
final var effectiveNameTable = nameTable == null ? new NameTable() : nameTable;
|
final var effectiveNameTable = nameTable == null ? new NameTable() : nameTable;
|
||||||
|
final var effectiveSupplementalTopDecls = supplementalTopDecls == null
|
||||||
|
? ReadOnlyList.<PbsAst.TopDecl>empty()
|
||||||
|
: supplementalTopDecls;
|
||||||
|
final var effectiveImportedCallables = importedCallables == null
|
||||||
|
? ReadOnlyList.<ImportedCallableSurface>empty()
|
||||||
|
: importedCallables;
|
||||||
|
final var effectiveImportedReservedMetadata = importedReservedMetadata == null
|
||||||
|
? IRReservedMetadata.empty()
|
||||||
|
: importedReservedMetadata;
|
||||||
final var semanticsErrorBaseline = diagnostics.errorCount();
|
final var semanticsErrorBaseline = diagnostics.errorCount();
|
||||||
new PbsDeclarationSemanticsValidator(effectiveNameTable).validate(ast, sourceKind, diagnostics);
|
new PbsDeclarationSemanticsValidator(effectiveNameTable).validate(ast, sourceKind, diagnostics);
|
||||||
flowSemanticsValidator.validate(ast, diagnostics);
|
flowSemanticsValidator.validate(ast, effectiveSupplementalTopDecls, diagnostics);
|
||||||
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
||||||
return IRBackendFile.empty(fileId);
|
return IRBackendFile.empty(fileId);
|
||||||
}
|
}
|
||||||
@ -142,14 +175,22 @@ public final class PbsFrontendCompiler {
|
|||||||
extractedReservedMetadata.builtinTypeSurfaces(),
|
extractedReservedMetadata.builtinTypeSurfaces(),
|
||||||
extractedReservedMetadata.builtinConstSurfaces(),
|
extractedReservedMetadata.builtinConstSurfaces(),
|
||||||
requiredCapabilities);
|
requiredCapabilities);
|
||||||
|
final var effectiveReservedMetadata = mergeReservedMetadata(
|
||||||
|
reservedMetadata,
|
||||||
|
effectiveImportedReservedMetadata);
|
||||||
|
|
||||||
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
|
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
|
||||||
? ReadOnlyList.empty()
|
? ReadOnlyList.empty()
|
||||||
: lowerFunctions(fileId, ast);
|
: lowerFunctions(fileId, ast);
|
||||||
final var loweringErrorBaseline = diagnostics.errorCount();
|
final var loweringErrorBaseline = diagnostics.errorCount();
|
||||||
final var executableLowering = sourceKind == SourceKind.SDK_INTERFACE
|
final var executableLowering = lowerExecutableFunctions(
|
||||||
? new ExecutableLoweringResult(ReadOnlyList.empty(), ReadOnlyList.empty(), ReadOnlyList.empty())
|
fileId,
|
||||||
: lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata, effectiveNameTable, diagnostics);
|
ast,
|
||||||
|
moduleKey,
|
||||||
|
effectiveReservedMetadata,
|
||||||
|
effectiveNameTable,
|
||||||
|
diagnostics,
|
||||||
|
effectiveImportedCallables);
|
||||||
if (diagnostics.errorCount() > loweringErrorBaseline) {
|
if (diagnostics.errorCount() > loweringErrorBaseline) {
|
||||||
return IRBackendFile.empty(fileId);
|
return IRBackendFile.empty(fileId);
|
||||||
}
|
}
|
||||||
@ -181,7 +222,8 @@ public final class PbsFrontendCompiler {
|
|||||||
final String moduleKey,
|
final String moduleKey,
|
||||||
final IRReservedMetadata reservedMetadata,
|
final IRReservedMetadata reservedMetadata,
|
||||||
final NameTable nameTable,
|
final NameTable nameTable,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics,
|
||||||
|
final ReadOnlyList<ImportedCallableSurface> importedCallables) {
|
||||||
final var normalizedModuleKey = moduleKey == null ? "" : moduleKey;
|
final var normalizedModuleKey = moduleKey == null ? "" : moduleKey;
|
||||||
final var hostByMethodName = new HashMap<NameId, List<IRReservedMetadata.HostMethodBinding>>();
|
final var hostByMethodName = new HashMap<NameId, List<IRReservedMetadata.HostMethodBinding>>();
|
||||||
for (final var hostBinding : reservedMetadata.hostMethodBindings()) {
|
for (final var hostBinding : reservedMetadata.hostMethodBindings()) {
|
||||||
@ -199,31 +241,49 @@ public final class PbsFrontendCompiler {
|
|||||||
}
|
}
|
||||||
final var callableIdTable = new CallableTable();
|
final var callableIdTable = new CallableTable();
|
||||||
final var callableIdsByNameAndArity = new HashMap<CallableResolutionKey, List<CallableId>>();
|
final var callableIdsByNameAndArity = new HashMap<CallableResolutionKey, List<CallableId>>();
|
||||||
final var callableIdByDeclaration = new HashMap<PbsAst.FunctionDecl, CallableId>();
|
final var callableIdByDeclaration = new HashMap<LowerableCallable, CallableId>();
|
||||||
final var returnSlotsByCallableId = new HashMap<CallableId, Integer>();
|
final var returnSlotsByCallableId = new HashMap<CallableId, Integer>();
|
||||||
final var intrinsicIdTable = new IntrinsicTable();
|
final var intrinsicIdTable = new IntrinsicTable();
|
||||||
final var typeSurfaceTable = new TypeSurfaceTable();
|
final var typeSurfaceTable = new TypeSurfaceTable();
|
||||||
final var callableShapeTable = new CallableShapeTable();
|
final var callableShapeTable = new CallableShapeTable();
|
||||||
for (final var declaredFn : ast.functions()) {
|
final var localCallables = collectLocalLowerableCallables(ast);
|
||||||
|
for (final var declaredCallable : localCallables) {
|
||||||
|
final var declaredFn = declaredCallable.functionDecl();
|
||||||
final var callableShapeId = callableShapeId(declaredFn, typeSurfaceTable, callableShapeTable);
|
final var callableShapeId = callableShapeId(declaredFn, typeSurfaceTable, callableShapeTable);
|
||||||
final var callableId = callableIdTable.register(
|
final var callableId = callableIdTable.register(
|
||||||
normalizedModuleKey,
|
normalizedModuleKey,
|
||||||
declaredFn.name(),
|
declaredCallable.callableName(),
|
||||||
declaredFn.parameters().size(),
|
declaredFn.parameters().size(),
|
||||||
callableShapeSurface(callableShapeId, typeSurfaceTable, callableShapeTable));
|
callableShapeSurface(callableShapeId, typeSurfaceTable, callableShapeTable));
|
||||||
callableIdByDeclaration.put(declaredFn, callableId);
|
callableIdByDeclaration.put(declaredCallable, callableId);
|
||||||
callableIdsByNameAndArity
|
callableIdsByNameAndArity
|
||||||
.computeIfAbsent(
|
.computeIfAbsent(
|
||||||
new CallableResolutionKey(nameTable.register(declaredFn.name()), declaredFn.parameters().size()),
|
new CallableResolutionKey(nameTable.register(declaredCallable.callableName()), declaredFn.parameters().size()),
|
||||||
ignored -> new ArrayList<>())
|
ignored -> new ArrayList<>())
|
||||||
.add(callableId);
|
.add(callableId);
|
||||||
final var retSlots = returnSlotsFor(declaredFn);
|
final var retSlots = returnSlotsFor(declaredFn);
|
||||||
returnSlotsByCallableId.put(callableId, retSlots);
|
returnSlotsByCallableId.put(callableId, retSlots);
|
||||||
}
|
}
|
||||||
|
for (final var importedCallable : importedCallables) {
|
||||||
|
final var callableId = callableIdTable.register(
|
||||||
|
importedCallable.moduleKey(),
|
||||||
|
importedCallable.callableName(),
|
||||||
|
importedCallable.arity(),
|
||||||
|
importedCallable.shapeSurface());
|
||||||
|
callableIdsByNameAndArity
|
||||||
|
.computeIfAbsent(
|
||||||
|
new CallableResolutionKey(
|
||||||
|
nameTable.register(importedCallable.callableName()),
|
||||||
|
importedCallable.arity()),
|
||||||
|
ignored -> new ArrayList<>())
|
||||||
|
.add(callableId);
|
||||||
|
returnSlotsByCallableId.put(callableId, importedCallable.returnSlots());
|
||||||
|
}
|
||||||
|
|
||||||
final var executableFunctions = new ArrayList<IRBackendExecutableFunction>(ast.functions().size());
|
final var executableFunctions = new ArrayList<IRBackendExecutableFunction>(localCallables.size());
|
||||||
for (final var fn : ast.functions()) {
|
for (final var callable : localCallables) {
|
||||||
final var functionCallableId = callableIdByDeclaration.get(fn);
|
final var fn = callable.functionDecl();
|
||||||
|
final var functionCallableId = callableIdByDeclaration.get(callable);
|
||||||
if (functionCallableId == null) {
|
if (functionCallableId == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -268,7 +328,7 @@ public final class PbsFrontendCompiler {
|
|||||||
executableFunctions.add(new IRBackendExecutableFunction(
|
executableFunctions.add(new IRBackendExecutableFunction(
|
||||||
fileId,
|
fileId,
|
||||||
normalizedModuleKey,
|
normalizedModuleKey,
|
||||||
fn.name(),
|
callable.callableName(),
|
||||||
functionCallableId,
|
functionCallableId,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
@ -300,6 +360,48 @@ public final class PbsFrontendCompiler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ReadOnlyList<LowerableCallable> collectLocalLowerableCallables(final PbsAst.File ast) {
|
||||||
|
final var callables = new ArrayList<LowerableCallable>();
|
||||||
|
for (final var functionDecl : ast.functions()) {
|
||||||
|
callables.add(new LowerableCallable(functionDecl.name(), functionDecl));
|
||||||
|
}
|
||||||
|
for (final var topDecl : ast.topDecls()) {
|
||||||
|
if (!(topDecl instanceof PbsAst.ServiceDecl serviceDecl)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (final var method : serviceDecl.methods()) {
|
||||||
|
callables.add(new LowerableCallable(serviceDecl.name() + "." + method.name(), method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ReadOnlyList.wrap(callables);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRReservedMetadata mergeReservedMetadata(
|
||||||
|
final IRReservedMetadata primary,
|
||||||
|
final IRReservedMetadata imported) {
|
||||||
|
final var hostBindings = new ArrayList<IRReservedMetadata.HostMethodBinding>();
|
||||||
|
hostBindings.addAll(primary.hostMethodBindings().asList());
|
||||||
|
hostBindings.addAll(imported.hostMethodBindings().asList());
|
||||||
|
|
||||||
|
final var builtinTypeSurfaces = new ArrayList<IRReservedMetadata.BuiltinTypeSurface>();
|
||||||
|
builtinTypeSurfaces.addAll(primary.builtinTypeSurfaces().asList());
|
||||||
|
builtinTypeSurfaces.addAll(imported.builtinTypeSurfaces().asList());
|
||||||
|
|
||||||
|
final var builtinConstSurfaces = new ArrayList<IRReservedMetadata.BuiltinConstSurface>();
|
||||||
|
builtinConstSurfaces.addAll(primary.builtinConstSurfaces().asList());
|
||||||
|
builtinConstSurfaces.addAll(imported.builtinConstSurfaces().asList());
|
||||||
|
|
||||||
|
final var requiredCapabilities = new HashSet<String>();
|
||||||
|
requiredCapabilities.addAll(primary.requiredCapabilities().asList());
|
||||||
|
requiredCapabilities.addAll(imported.requiredCapabilities().asList());
|
||||||
|
|
||||||
|
return new IRReservedMetadata(
|
||||||
|
ReadOnlyList.wrap(hostBindings),
|
||||||
|
ReadOnlyList.wrap(builtinTypeSurfaces),
|
||||||
|
ReadOnlyList.wrap(builtinConstSurfaces),
|
||||||
|
ReadOnlyList.wrap(requiredCapabilities.stream().toList()));
|
||||||
|
}
|
||||||
|
|
||||||
private CallableShapeId callableShapeId(
|
private CallableShapeId callableShapeId(
|
||||||
final PbsAst.FunctionDecl functionDecl,
|
final PbsAst.FunctionDecl functionDecl,
|
||||||
final TypeSurfaceTable typeSurfaceTable,
|
final TypeSurfaceTable typeSurfaceTable,
|
||||||
@ -315,6 +417,13 @@ public final class PbsFrontendCompiler {
|
|||||||
return callableShapeTable.register(ReadOnlyList.wrap(parameterSurfaceIds), outputSurfaceId);
|
return callableShapeTable.register(ReadOnlyList.wrap(parameterSurfaceIds), outputSurfaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String callableShapeSurfaceOf(final PbsAst.FunctionDecl functionDecl) {
|
||||||
|
final var typeSurfaceTable = new TypeSurfaceTable();
|
||||||
|
final var callableShapeTable = new CallableShapeTable();
|
||||||
|
final var callableShapeId = callableShapeId(functionDecl, typeSurfaceTable, callableShapeTable);
|
||||||
|
return callableShapeSurface(callableShapeId, typeSurfaceTable, callableShapeTable);
|
||||||
|
}
|
||||||
|
|
||||||
private String callableShapeSurface(
|
private String callableShapeSurface(
|
||||||
final CallableShapeId callableShapeId,
|
final CallableShapeId callableShapeId,
|
||||||
final TypeSurfaceTable typeSurfaceTable,
|
final TypeSurfaceTable typeSurfaceTable,
|
||||||
@ -600,10 +709,26 @@ public final class PbsFrontendCompiler {
|
|||||||
reportUnsupportedLowering("executable lowering requires resolvable callee identity", callExpr.span(), context);
|
reportUnsupportedLowering("executable lowering requires resolvable callee identity", callExpr.span(), context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final var callableCandidates = context.callableIdsByNameAndArity().get(
|
final var callableCandidates = new ArrayList<CallableId>();
|
||||||
new CallableResolutionKey(calleeIdentity.nameId(), callExpr.arguments().size()));
|
final var primaryCallableCandidates = context.callableIdsByNameAndArity().get(
|
||||||
final var hostCandidates = context.hostByMethodName().getOrDefault(calleeIdentity.nameId(), List.of());
|
new CallableResolutionKey(calleeIdentity.primaryCallableNameId(), callExpr.arguments().size()));
|
||||||
final var intrinsicCandidates = context.intrinsicByMethodName().getOrDefault(calleeIdentity.nameId(), List.of());
|
if (primaryCallableCandidates != null) {
|
||||||
|
callableCandidates.addAll(primaryCallableCandidates);
|
||||||
|
}
|
||||||
|
if (calleeIdentity.secondaryCallableNameId() != null) {
|
||||||
|
final var secondaryCallableCandidates = context.callableIdsByNameAndArity().get(
|
||||||
|
new CallableResolutionKey(calleeIdentity.secondaryCallableNameId(), callExpr.arguments().size()));
|
||||||
|
if (secondaryCallableCandidates != null) {
|
||||||
|
for (final var candidate : secondaryCallableCandidates) {
|
||||||
|
if (!callableCandidates.contains(candidate)) {
|
||||||
|
callableCandidates.add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final var hostCandidates = context.hostByMethodName().getOrDefault(calleeIdentity.memberNameId(), List.of());
|
||||||
|
final var intrinsicCandidates = context.intrinsicByMethodName().getOrDefault(calleeIdentity.memberNameId(), List.of());
|
||||||
|
|
||||||
var categoryCount = 0;
|
var categoryCount = 0;
|
||||||
if (callableCandidates != null && !callableCandidates.isEmpty()) {
|
if (callableCandidates != null && !callableCandidates.isEmpty()) {
|
||||||
@ -651,6 +776,7 @@ public final class PbsFrontendCompiler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final var intrinsic = intrinsicCandidates.getFirst();
|
final var intrinsic = intrinsicCandidates.getFirst();
|
||||||
|
final var effectiveArgSlots = intrinsic.argSlots() + implicitReceiverArgSlots(callExpr.callee());
|
||||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||||
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
|
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
|
||||||
"",
|
"",
|
||||||
@ -660,13 +786,13 @@ public final class PbsFrontendCompiler {
|
|||||||
intrinsic.canonicalName(),
|
intrinsic.canonicalName(),
|
||||||
intrinsic.canonicalVersion(),
|
intrinsic.canonicalVersion(),
|
||||||
context.intrinsicIdTable().register(intrinsic.canonicalName(), intrinsic.canonicalVersion())),
|
context.intrinsicIdTable().register(intrinsic.canonicalName(), intrinsic.canonicalVersion())),
|
||||||
intrinsic.argSlots(),
|
effectiveArgSlots,
|
||||||
intrinsic.retSlots(),
|
intrinsic.retSlots(),
|
||||||
callExpr.span()));
|
callExpr.span()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callableCandidates == null || callableCandidates.isEmpty()) {
|
if (callableCandidates.isEmpty()) {
|
||||||
reportUnsupportedLowering("executable lowering requires resolvable callable identity", callExpr.span(), context);
|
reportUnsupportedLowering("executable lowering requires resolvable callable identity", callExpr.span(), context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -678,7 +804,7 @@ public final class PbsFrontendCompiler {
|
|||||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||||
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
||||||
context.moduleKey(),
|
context.moduleKey(),
|
||||||
calleeIdentity.displayName(),
|
calleeIdentity.primaryCallableDisplayName(),
|
||||||
calleeCallableId,
|
calleeCallableId,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@ -692,16 +818,71 @@ public final class PbsFrontendCompiler {
|
|||||||
final NameTable nameTable) {
|
final NameTable nameTable) {
|
||||||
return switch (callee) {
|
return switch (callee) {
|
||||||
case PbsAst.IdentifierExpr identifierExpr ->
|
case PbsAst.IdentifierExpr identifierExpr ->
|
||||||
new CalleeIdentity(nameTable.register(identifierExpr.name()), identifierExpr.name());
|
new CalleeIdentity(
|
||||||
// Member access does not carry executable callsite identity in v1 lowering.
|
nameTable.register(identifierExpr.name()),
|
||||||
case PbsAst.MemberExpr ignored -> null;
|
identifierExpr.name(),
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
nameTable.register(identifierExpr.name()));
|
||||||
|
case PbsAst.MemberExpr memberExpr -> {
|
||||||
|
final var memberName = memberExpr.memberName();
|
||||||
|
final var rootName = memberRootName(memberExpr.receiver());
|
||||||
|
final var qualified = rootName == null || rootName.isBlank()
|
||||||
|
? memberName
|
||||||
|
: rootName + "." + memberName;
|
||||||
|
final NameId secondaryNameId;
|
||||||
|
final String secondaryDisplayName;
|
||||||
|
if (qualified.equals(memberName)) {
|
||||||
|
secondaryNameId = null;
|
||||||
|
secondaryDisplayName = "";
|
||||||
|
} else {
|
||||||
|
secondaryNameId = nameTable.register(memberName);
|
||||||
|
secondaryDisplayName = memberName;
|
||||||
|
}
|
||||||
|
yield new CalleeIdentity(
|
||||||
|
nameTable.register(qualified),
|
||||||
|
qualified,
|
||||||
|
secondaryNameId,
|
||||||
|
secondaryDisplayName,
|
||||||
|
nameTable.register(memberName));
|
||||||
|
}
|
||||||
case PbsAst.BindExpr bindExpr ->
|
case PbsAst.BindExpr bindExpr ->
|
||||||
new CalleeIdentity(nameTable.register(bindExpr.functionName()), bindExpr.functionName());
|
new CalleeIdentity(
|
||||||
|
nameTable.register(bindExpr.functionName()),
|
||||||
|
bindExpr.functionName(),
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
nameTable.register(bindExpr.functionName()));
|
||||||
case PbsAst.GroupExpr groupExpr -> resolveCalleeIdentity(groupExpr.expression(), nameTable);
|
case PbsAst.GroupExpr groupExpr -> resolveCalleeIdentity(groupExpr.expression(), nameTable);
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String memberRootName(final PbsAst.Expression expression) {
|
||||||
|
return switch (expression) {
|
||||||
|
case PbsAst.IdentifierExpr identifierExpr -> identifierExpr.name();
|
||||||
|
case PbsAst.MemberExpr memberExpr -> memberRootName(memberExpr.receiver());
|
||||||
|
case PbsAst.CallExpr callExpr -> memberRootName(callExpr.callee());
|
||||||
|
case PbsAst.GroupExpr groupExpr -> memberRootName(groupExpr.expression());
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int implicitReceiverArgSlots(final PbsAst.Expression callee) {
|
||||||
|
if (!(callee instanceof PbsAst.MemberExpr memberExpr)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return receiverProducesRuntimeValue(memberExpr.receiver()) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean receiverProducesRuntimeValue(final PbsAst.Expression receiver) {
|
||||||
|
return switch (receiver) {
|
||||||
|
case PbsAst.CallExpr ignored -> true;
|
||||||
|
case PbsAst.GroupExpr groupExpr -> receiverProducesRuntimeValue(groupExpr.expression());
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void emitJump(
|
private void emitJump(
|
||||||
final IRBackendExecutableFunction.InstructionKind jumpKind,
|
final IRBackendExecutableFunction.InstructionKind jumpKind,
|
||||||
final String targetLabel,
|
final String targetLabel,
|
||||||
@ -785,7 +966,8 @@ public final class PbsFrontendCompiler {
|
|||||||
switch (instruction.kind()) {
|
switch (instruction.kind()) {
|
||||||
case CALL_FUNC -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
|
case CALL_FUNC -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
|
||||||
case CALL_HOST -> outHeight += instruction.hostCall() == null ? 0 : instruction.hostCall().retSlots();
|
case CALL_HOST -> outHeight += instruction.hostCall() == null ? 0 : instruction.hostCall().retSlots();
|
||||||
case CALL_INTRINSIC, HALT, LABEL, JMP, RET -> {
|
case CALL_INTRINSIC -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
|
||||||
|
case HALT, LABEL, JMP, RET -> {
|
||||||
}
|
}
|
||||||
case JMP_IF_TRUE, JMP_IF_FALSE -> {
|
case JMP_IF_TRUE, JMP_IF_FALSE -> {
|
||||||
if (outHeight <= 0) {
|
if (outHeight <= 0) {
|
||||||
@ -947,14 +1129,30 @@ public final class PbsFrontendCompiler {
|
|||||||
ReadOnlyList<IntrinsicReference> intrinsicPool) {
|
ReadOnlyList<IntrinsicReference> intrinsicPool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record LowerableCallable(
|
||||||
|
String callableName,
|
||||||
|
PbsAst.FunctionDecl functionDecl) {
|
||||||
|
}
|
||||||
|
|
||||||
private record CallableResolutionKey(
|
private record CallableResolutionKey(
|
||||||
NameId callableNameId,
|
NameId callableNameId,
|
||||||
int arity) {
|
int arity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CalleeIdentity(
|
private record CalleeIdentity(
|
||||||
NameId nameId,
|
NameId primaryCallableNameId,
|
||||||
String displayName) {
|
String primaryCallableDisplayName,
|
||||||
|
NameId secondaryCallableNameId,
|
||||||
|
String secondaryCallableDisplayName,
|
||||||
|
NameId memberNameId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ImportedCallableSurface(
|
||||||
|
String moduleKey,
|
||||||
|
String callableName,
|
||||||
|
int arity,
|
||||||
|
int returnSlots,
|
||||||
|
String shapeSurface) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ExecutableLoweringAnalysisException extends RuntimeException {
|
private static final class ExecutableLoweringAnalysisException extends RuntimeException {
|
||||||
|
|||||||
@ -119,9 +119,10 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final var intrinsicMetadata = intrinsicAttribute.get();
|
final var intrinsicMetadata = intrinsicAttribute.get();
|
||||||
|
final var intrinsicName = stringArgument(intrinsicMetadata, "name").orElse(signature.name());
|
||||||
intrinsics.add(new IRReservedMetadata.IntrinsicSurface(
|
intrinsics.add(new IRReservedMetadata.IntrinsicSurface(
|
||||||
signature.name(),
|
signature.name(),
|
||||||
stringArgument(intrinsicMetadata, "name").orElse(signature.name()),
|
canonicalIntrinsicName(canonicalTypeName, intrinsicName),
|
||||||
longArgument(intrinsicMetadata, "version").orElse(canonicalVersion),
|
longArgument(intrinsicMetadata, "version").orElse(canonicalVersion),
|
||||||
signature.parameters().size(),
|
signature.parameters().size(),
|
||||||
switch (signature.returnKind()) {
|
switch (signature.returnKind()) {
|
||||||
@ -215,6 +216,23 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
return rawValue;
|
return rawValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String canonicalIntrinsicName(
|
||||||
|
final String canonicalTypeName,
|
||||||
|
final String intrinsicName) {
|
||||||
|
final var normalizedTypeName = canonicalTypeName == null ? "" : canonicalTypeName.trim();
|
||||||
|
final var normalizedIntrinsicName = intrinsicName == null ? "" : intrinsicName.trim();
|
||||||
|
if (normalizedIntrinsicName.isBlank()) {
|
||||||
|
return normalizedTypeName;
|
||||||
|
}
|
||||||
|
if (normalizedIntrinsicName.contains(".")) {
|
||||||
|
return normalizedIntrinsicName;
|
||||||
|
}
|
||||||
|
if (normalizedTypeName.isBlank()) {
|
||||||
|
return normalizedIntrinsicName;
|
||||||
|
}
|
||||||
|
return normalizedTypeName + "." + normalizedIntrinsicName;
|
||||||
|
}
|
||||||
|
|
||||||
private String typeSurfaceKey(final PbsAst.TypeRef typeRef) {
|
private String typeSurfaceKey(final PbsAst.TypeRef typeRef) {
|
||||||
if (typeRef == null) {
|
if (typeRef == null) {
|
||||||
return "unit";
|
return "unit";
|
||||||
|
|||||||
@ -285,7 +285,7 @@ final class PbsExprParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cursor.match(PbsTokenKind.DOT)) {
|
if (cursor.match(PbsTokenKind.DOT)) {
|
||||||
final var member = consume(PbsTokenKind.IDENTIFIER, "Expected member name after '.'");
|
final var member = consumeMemberName("Expected member name after '.'");
|
||||||
expression = new PbsAst.MemberExpr(
|
expression = new PbsAst.MemberExpr(
|
||||||
expression,
|
expression,
|
||||||
member.lexeme(),
|
member.lexeme(),
|
||||||
@ -631,6 +631,18 @@ final class PbsExprParser {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PbsToken consumeMemberName(final String message) {
|
||||||
|
if (cursor.check(PbsTokenKind.IDENTIFIER) || cursor.check(PbsTokenKind.ERROR)) {
|
||||||
|
return cursor.advance();
|
||||||
|
}
|
||||||
|
final var token = cursor.peek();
|
||||||
|
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, message + ", found " + token.kind());
|
||||||
|
if (!cursor.isAtEnd()) {
|
||||||
|
return cursor.advance();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
private Span span(final long start, final long end) {
|
private Span span(final long start, final long end) {
|
||||||
return new Span(fileId, start, end);
|
return new Span(fileId, start, end);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -730,7 +730,7 @@ public final class PbsParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.FunctionDecl parseFunctionLike(final PbsToken fnToken) {
|
private PbsAst.FunctionDecl parseFunctionLike(final PbsToken fnToken) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected function name");
|
final var name = consumeCallableName("Expected function name");
|
||||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
||||||
final var parameters = parseParametersUntilRightParen();
|
final var parameters = parseParametersUntilRightParen();
|
||||||
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list");
|
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list");
|
||||||
@ -752,7 +752,7 @@ public final class PbsParser {
|
|||||||
final PbsToken fnToken,
|
final PbsToken fnToken,
|
||||||
final boolean requireSemicolon,
|
final boolean requireSemicolon,
|
||||||
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected function name");
|
final var name = consumeCallableName("Expected function name");
|
||||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
||||||
final var parameters = parseParametersUntilRightParen();
|
final var parameters = parseParametersUntilRightParen();
|
||||||
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list");
|
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list");
|
||||||
@ -774,6 +774,18 @@ public final class PbsParser {
|
|||||||
span(fnToken.start(), end));
|
span(fnToken.start(), end));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PbsToken consumeCallableName(final String message) {
|
||||||
|
if (cursor.check(PbsTokenKind.IDENTIFIER) || cursor.check(PbsTokenKind.ERROR)) {
|
||||||
|
return cursor.advance();
|
||||||
|
}
|
||||||
|
final var token = cursor.peek();
|
||||||
|
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, message + ", found " + token.kind());
|
||||||
|
if (!cursor.isAtEnd()) {
|
||||||
|
return cursor.advance();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses `declare callback` declaration.
|
* Parses `declare callback` declaration.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -88,12 +88,6 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
||||||
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
||||||
if (interfaceModule) {
|
|
||||||
reportInterfaceNonDeclarativeDecl(
|
|
||||||
serviceDecl.span(),
|
|
||||||
"Interface modules must not declare executable service bodies",
|
|
||||||
diagnostics);
|
|
||||||
}
|
|
||||||
validateServiceDeclaration(serviceDecl, binder, rules);
|
validateServiceDeclaration(serviceDecl, binder, rules);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,14 @@ final class PbsFlowBodyAnalyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
||||||
final var model = Model.from(ast);
|
validate(ast, ReadOnlyList.empty(), diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var model = Model.from(ast, supplementalTopDecls);
|
||||||
|
|
||||||
for (final var topDecl : ast.topDecls()) {
|
for (final var topDecl : ast.topDecls()) {
|
||||||
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
|||||||
@ -169,109 +169,209 @@ final class PbsFlowSemanticSupport {
|
|||||||
final Map<String, Set<String>> errors = new HashMap<>();
|
final Map<String, Set<String>> errors = new HashMap<>();
|
||||||
final Map<String, TypeView> constTypes = new HashMap<>();
|
final Map<String, TypeView> constTypes = new HashMap<>();
|
||||||
final Map<String, TypeView> serviceSingletons = new HashMap<>();
|
final Map<String, TypeView> serviceSingletons = new HashMap<>();
|
||||||
|
final Set<String> knownStructNames = new HashSet<>();
|
||||||
|
final Set<String> knownServiceNames = new HashSet<>();
|
||||||
|
final Set<String> knownContractNames = new HashSet<>();
|
||||||
|
final Set<String> knownEnumNames = new HashSet<>();
|
||||||
|
final Set<String> knownErrorNames = new HashSet<>();
|
||||||
|
final Set<String> knownCallbackNames = new HashSet<>();
|
||||||
|
|
||||||
static Model from(final PbsAst.File ast) {
|
static Model from(final PbsAst.File ast) {
|
||||||
|
return from(ast, ReadOnlyList.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
static Model from(
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls) {
|
||||||
final var model = new Model();
|
final var model = new Model();
|
||||||
for (final var topDecl : ast.topDecls()) {
|
for (final var topDecl : ast.topDecls()) {
|
||||||
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
model.registerKnownTopDecl(topDecl);
|
||||||
final var fields = new HashMap<String, StructFieldInfo>();
|
}
|
||||||
for (final var field : structDecl.fields()) {
|
for (final var topDecl : supplementalTopDecls) {
|
||||||
fields.put(
|
model.registerKnownTopDecl(topDecl);
|
||||||
field.name(),
|
}
|
||||||
new StructFieldInfo(
|
for (final var topDecl : ast.topDecls()) {
|
||||||
model.typeFrom(field.typeRef()),
|
model.ingestTopDecl(topDecl);
|
||||||
field.isPublic(),
|
}
|
||||||
field.isMutable()));
|
for (final var topDecl : supplementalTopDecls) {
|
||||||
}
|
model.ingestTopDecl(topDecl);
|
||||||
final var methods = new HashMap<String, List<CallableSymbol>>();
|
|
||||||
for (final var method : structDecl.methods()) {
|
|
||||||
methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>())
|
|
||||||
.add(model.callableFrom(
|
|
||||||
method.name(),
|
|
||||||
method.parameters(),
|
|
||||||
method.returnKind(),
|
|
||||||
method.returnType(),
|
|
||||||
method.resultErrorType(),
|
|
||||||
method.span()));
|
|
||||||
}
|
|
||||||
model.structs.put(structDecl.name(), new StructInfo(fields, methods));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
|
||||||
final var methods = new HashMap<String, List<CallableSymbol>>();
|
|
||||||
for (final var method : serviceDecl.methods()) {
|
|
||||||
methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>())
|
|
||||||
.add(model.callableFrom(
|
|
||||||
method.name(),
|
|
||||||
method.parameters(),
|
|
||||||
method.returnKind(),
|
|
||||||
method.returnType(),
|
|
||||||
method.resultErrorType(),
|
|
||||||
method.span()));
|
|
||||||
}
|
|
||||||
model.services.put(serviceDecl.name(), new ServiceInfo(methods));
|
|
||||||
model.serviceSingletons.put(serviceDecl.name(), TypeView.service(serviceDecl.name()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
|
||||||
final var methods = new HashMap<String, List<CallableSymbol>>();
|
|
||||||
for (final var signature : contractDecl.signatures()) {
|
|
||||||
methods.computeIfAbsent(signature.name(), ignored -> new ArrayList<>())
|
|
||||||
.add(model.callableFrom(
|
|
||||||
signature.name(),
|
|
||||||
signature.parameters(),
|
|
||||||
signature.returnKind(),
|
|
||||||
signature.returnType(),
|
|
||||||
signature.resultErrorType(),
|
|
||||||
signature.span()));
|
|
||||||
}
|
|
||||||
model.contracts.put(contractDecl.name(), new ContractInfo(methods));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
|
||||||
model.topLevelCallables.computeIfAbsent(functionDecl.name(), ignored -> new ArrayList<>())
|
|
||||||
.add(model.callableFrom(
|
|
||||||
functionDecl.name(),
|
|
||||||
functionDecl.parameters(),
|
|
||||||
functionDecl.returnKind(),
|
|
||||||
functionDecl.returnType(),
|
|
||||||
functionDecl.resultErrorType(),
|
|
||||||
functionDecl.span()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.CallbackDecl(
|
|
||||||
String name, ReadOnlyList<PbsAst.Parameter> parameters, PbsAst.ReturnKind returnKind,
|
|
||||||
PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, Span span
|
|
||||||
)) {
|
|
||||||
final var symbol = model.callableFrom(
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returnKind,
|
|
||||||
returnType,
|
|
||||||
resultErrorType,
|
|
||||||
span);
|
|
||||||
model.callbacks.put(name, new CallbackSignature(symbol.inputTypes(), symbol.outputType()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
|
||||||
final var cases = new HashSet<String>();
|
|
||||||
for (final var enumCase : enumDecl.cases()) {
|
|
||||||
cases.add(enumCase.name());
|
|
||||||
}
|
|
||||||
model.enums.put(enumDecl.name(), cases);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
|
||||||
model.errors.put(errorDecl.name(), new HashSet<>(errorDecl.cases().asList()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
|
|
||||||
model.constTypes.put(constDecl.name(), model.typeFrom(constDecl.explicitType()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerKnownTopDecl(final PbsAst.TopDecl topDecl) {
|
||||||
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
|
knownStructNames.add(structDecl.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
|
knownStructNames.add(builtinTypeDecl.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
|
knownServiceNames.add(serviceDecl.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
|
knownServiceNames.add(hostDecl.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
|
knownContractNames.add(contractDecl.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||||
|
knownEnumNames.add(enumDecl.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
|
knownErrorNames.add(errorDecl.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
||||||
|
knownCallbackNames.add(callbackDecl.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ingestTopDecl(final PbsAst.TopDecl topDecl) {
|
||||||
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
|
final var fields = new HashMap<String, StructFieldInfo>();
|
||||||
|
for (final var field : structDecl.fields()) {
|
||||||
|
fields.put(
|
||||||
|
field.name(),
|
||||||
|
new StructFieldInfo(
|
||||||
|
typeFrom(field.typeRef()),
|
||||||
|
field.isPublic(),
|
||||||
|
field.isMutable()));
|
||||||
|
}
|
||||||
|
final var methods = new HashMap<String, List<CallableSymbol>>();
|
||||||
|
for (final var method : structDecl.methods()) {
|
||||||
|
methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>())
|
||||||
|
.add(callableFrom(
|
||||||
|
method.name(),
|
||||||
|
method.parameters(),
|
||||||
|
method.returnKind(),
|
||||||
|
method.returnType(),
|
||||||
|
method.resultErrorType(),
|
||||||
|
method.span()));
|
||||||
|
}
|
||||||
|
structs.put(structDecl.name(), new StructInfo(fields, methods));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
|
final var fields = new HashMap<String, StructFieldInfo>();
|
||||||
|
for (final var field : builtinTypeDecl.fields()) {
|
||||||
|
fields.put(
|
||||||
|
field.name(),
|
||||||
|
new StructFieldInfo(
|
||||||
|
typeFrom(field.typeRef()),
|
||||||
|
true,
|
||||||
|
false));
|
||||||
|
}
|
||||||
|
final var methods = new HashMap<String, List<CallableSymbol>>();
|
||||||
|
for (final var signature : builtinTypeDecl.signatures()) {
|
||||||
|
methods.computeIfAbsent(signature.name(), ignored -> new ArrayList<>())
|
||||||
|
.add(callableFrom(
|
||||||
|
signature.name(),
|
||||||
|
signature.parameters(),
|
||||||
|
signature.returnKind(),
|
||||||
|
signature.returnType(),
|
||||||
|
signature.resultErrorType(),
|
||||||
|
signature.span()));
|
||||||
|
}
|
||||||
|
structs.put(builtinTypeDecl.name(), new StructInfo(fields, methods));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
|
final var methods = new HashMap<String, List<CallableSymbol>>();
|
||||||
|
for (final var method : serviceDecl.methods()) {
|
||||||
|
methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>())
|
||||||
|
.add(callableFrom(
|
||||||
|
method.name(),
|
||||||
|
method.parameters(),
|
||||||
|
method.returnKind(),
|
||||||
|
method.returnType(),
|
||||||
|
method.resultErrorType(),
|
||||||
|
method.span()));
|
||||||
|
}
|
||||||
|
services.put(serviceDecl.name(), new ServiceInfo(methods));
|
||||||
|
serviceSingletons.put(serviceDecl.name(), TypeView.service(serviceDecl.name()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
|
final var methods = new HashMap<String, List<CallableSymbol>>();
|
||||||
|
for (final var signature : hostDecl.signatures()) {
|
||||||
|
methods.computeIfAbsent(signature.name(), ignored -> new ArrayList<>())
|
||||||
|
.add(callableFrom(
|
||||||
|
signature.name(),
|
||||||
|
signature.parameters(),
|
||||||
|
signature.returnKind(),
|
||||||
|
signature.returnType(),
|
||||||
|
signature.resultErrorType(),
|
||||||
|
signature.span()));
|
||||||
|
}
|
||||||
|
// Host owners are value singletons with callable members, same access shape as services.
|
||||||
|
services.put(hostDecl.name(), new ServiceInfo(methods));
|
||||||
|
serviceSingletons.put(hostDecl.name(), TypeView.service(hostDecl.name()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
|
final var methods = new HashMap<String, List<CallableSymbol>>();
|
||||||
|
for (final var signature : contractDecl.signatures()) {
|
||||||
|
methods.computeIfAbsent(signature.name(), ignored -> new ArrayList<>())
|
||||||
|
.add(callableFrom(
|
||||||
|
signature.name(),
|
||||||
|
signature.parameters(),
|
||||||
|
signature.returnKind(),
|
||||||
|
signature.returnType(),
|
||||||
|
signature.resultErrorType(),
|
||||||
|
signature.span()));
|
||||||
|
}
|
||||||
|
contracts.put(contractDecl.name(), new ContractInfo(methods));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
topLevelCallables.computeIfAbsent(functionDecl.name(), ignored -> new ArrayList<>())
|
||||||
|
.add(callableFrom(
|
||||||
|
functionDecl.name(),
|
||||||
|
functionDecl.parameters(),
|
||||||
|
functionDecl.returnKind(),
|
||||||
|
functionDecl.returnType(),
|
||||||
|
functionDecl.resultErrorType(),
|
||||||
|
functionDecl.span()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.CallbackDecl(
|
||||||
|
String name, ReadOnlyList<PbsAst.Parameter> parameters, PbsAst.ReturnKind returnKind,
|
||||||
|
PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, Span span
|
||||||
|
)) {
|
||||||
|
final var symbol = callableFrom(
|
||||||
|
name,
|
||||||
|
parameters,
|
||||||
|
returnKind,
|
||||||
|
returnType,
|
||||||
|
resultErrorType,
|
||||||
|
span);
|
||||||
|
callbacks.put(name, new CallbackSignature(symbol.inputTypes(), symbol.outputType()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||||
|
final var cases = new HashSet<String>();
|
||||||
|
for (final var enumCase : enumDecl.cases()) {
|
||||||
|
cases.add(enumCase.name());
|
||||||
|
}
|
||||||
|
enums.put(enumDecl.name(), cases);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
|
errors.put(errorDecl.name(), new HashSet<>(errorDecl.cases().asList()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
|
||||||
|
constTypes.put(constDecl.name(), typeFrom(constDecl.explicitType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private CallableSymbol callableFrom(
|
private CallableSymbol callableFrom(
|
||||||
final String name,
|
final String name,
|
||||||
final ReadOnlyList<PbsAst.Parameter> parameters,
|
final ReadOnlyList<PbsAst.Parameter> parameters,
|
||||||
@ -324,25 +424,28 @@ final class PbsFlowSemanticSupport {
|
|||||||
if ("str".equals(typeRef.name())) {
|
if ("str".equals(typeRef.name())) {
|
||||||
yield TypeView.str();
|
yield TypeView.str();
|
||||||
}
|
}
|
||||||
if (structs.containsKey(typeRef.name())) {
|
if (structs.containsKey(typeRef.name()) || knownStructNames.contains(typeRef.name())) {
|
||||||
yield TypeView.struct(typeRef.name());
|
yield TypeView.struct(typeRef.name());
|
||||||
}
|
}
|
||||||
if (services.containsKey(typeRef.name())) {
|
if (services.containsKey(typeRef.name()) || knownServiceNames.contains(typeRef.name())) {
|
||||||
yield TypeView.service(typeRef.name());
|
yield TypeView.service(typeRef.name());
|
||||||
}
|
}
|
||||||
if (contracts.containsKey(typeRef.name())) {
|
if (contracts.containsKey(typeRef.name()) || knownContractNames.contains(typeRef.name())) {
|
||||||
yield TypeView.contract(typeRef.name());
|
yield TypeView.contract(typeRef.name());
|
||||||
}
|
}
|
||||||
if (enums.containsKey(typeRef.name())) {
|
if (enums.containsKey(typeRef.name()) || knownEnumNames.contains(typeRef.name())) {
|
||||||
yield TypeView.enumType(typeRef.name());
|
yield TypeView.enumType(typeRef.name());
|
||||||
}
|
}
|
||||||
if (errors.containsKey(typeRef.name())) {
|
if (errors.containsKey(typeRef.name()) || knownErrorNames.contains(typeRef.name())) {
|
||||||
yield TypeView.error(typeRef.name());
|
yield TypeView.error(typeRef.name());
|
||||||
}
|
}
|
||||||
final var callbackSignature = callbacks.get(typeRef.name());
|
final var callbackSignature = callbacks.get(typeRef.name());
|
||||||
if (callbackSignature != null) {
|
if (callbackSignature != null) {
|
||||||
yield TypeView.callback(typeRef.name(), callbackSignature.inputTypes(), callbackSignature.outputType());
|
yield TypeView.callback(typeRef.name(), callbackSignature.inputTypes(), callbackSignature.outputType());
|
||||||
}
|
}
|
||||||
|
if (knownCallbackNames.contains(typeRef.name())) {
|
||||||
|
yield TypeView.callback(typeRef.name(), List.of(), TypeView.unknown());
|
||||||
|
}
|
||||||
yield TypeView.unknown();
|
yield TypeView.unknown();
|
||||||
}
|
}
|
||||||
case OPTIONAL -> TypeView.optional(typeFrom(typeRef.inner()));
|
case OPTIONAL -> TypeView.optional(typeFrom(typeRef.inner()));
|
||||||
|
|||||||
@ -2,11 +2,19 @@ package p.studio.compiler.pbs.semantics;
|
|||||||
|
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
public final class PbsFlowSemanticsValidator {
|
public final class PbsFlowSemanticsValidator {
|
||||||
private final PbsFlowBodyAnalyzer flowBodyAnalyzer = new PbsFlowBodyAnalyzer();
|
private final PbsFlowBodyAnalyzer flowBodyAnalyzer = new PbsFlowBodyAnalyzer();
|
||||||
|
|
||||||
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
||||||
flowBodyAnalyzer.validate(ast, diagnostics);
|
flowBodyAnalyzer.validate(ast, ReadOnlyList.empty(), diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
flowBodyAnalyzer.validate(ast, supplementalTopDecls, diagnostics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
package p.studio.compiler.services;
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import p.studio.compiler.PBSDefinitions;
|
||||||
import p.studio.compiler.messages.BuildingIssueSink;
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
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.IRReservedMetadata;
|
||||||
import p.studio.compiler.models.ProjectDescriptor;
|
import p.studio.compiler.models.ProjectDescriptor;
|
||||||
import p.studio.compiler.models.SourceHandle;
|
import p.studio.compiler.models.SourceHandle;
|
||||||
import p.studio.compiler.models.SourceKind;
|
import p.studio.compiler.models.SourceKind;
|
||||||
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.pbs.metadata.PbsReservedMetadataExtractor;
|
||||||
import p.studio.compiler.pbs.lexer.PbsLexer;
|
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||||
import p.studio.compiler.pbs.linking.PbsLinkErrors;
|
import p.studio.compiler.pbs.linking.PbsLinkErrors;
|
||||||
import p.studio.compiler.pbs.linking.PbsModuleVisibilityValidator;
|
import p.studio.compiler.pbs.linking.PbsModuleVisibilityValidator;
|
||||||
@ -42,6 +45,7 @@ import java.util.Set;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class PBSFrontendPhaseService implements FrontendPhaseService {
|
public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||||
private final PbsFrontendCompiler frontendCompiler = new PbsFrontendCompiler();
|
private final PbsFrontendCompiler frontendCompiler = new PbsFrontendCompiler();
|
||||||
|
private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor();
|
||||||
private final PbsModuleVisibilityValidator moduleVisibilityValidator = new PbsModuleVisibilityValidator();
|
private final PbsModuleVisibilityValidator moduleVisibilityValidator = new PbsModuleVisibilityValidator();
|
||||||
private final StdlibEnvironmentResolver stdlibEnvironmentResolver;
|
private final StdlibEnvironmentResolver stdlibEnvironmentResolver;
|
||||||
private final InterfaceModuleLoader interfaceModuleLoader;
|
private final InterfaceModuleLoader interfaceModuleLoader;
|
||||||
@ -154,6 +158,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), nameTable, diagnostics);
|
moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), nameTable, diagnostics);
|
||||||
markModulesWithLinkingErrors(diagnostics, moduleIdByFile, failedModuleIds);
|
markModulesWithLinkingErrors(diagnostics, moduleIdByFile, failedModuleIds);
|
||||||
final var moduleDependencyGraph = buildModuleDependencyGraph(parsedSourceFiles, moduleTable);
|
final var moduleDependencyGraph = buildModuleDependencyGraph(parsedSourceFiles, moduleTable);
|
||||||
|
final var importedSemanticContexts = buildImportedSemanticContexts(parsedSourceFiles, moduleTable);
|
||||||
|
|
||||||
final var compiledSourceFiles = new ArrayList<CompiledSourceFile>(parsedSourceFiles.size());
|
final var compiledSourceFiles = new ArrayList<CompiledSourceFile>(parsedSourceFiles.size());
|
||||||
for (final var parsedSource : parsedSourceFiles) {
|
for (final var parsedSource : parsedSourceFiles) {
|
||||||
@ -161,6 +166,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
if (blockedModuleIds.contains(parsedSource.moduleId())) {
|
if (blockedModuleIds.contains(parsedSource.moduleId())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
final var importedSemanticContext = importedSemanticContexts.getOrDefault(
|
||||||
|
parsedSource.fileId(),
|
||||||
|
ImportedSemanticContext.empty());
|
||||||
final var compileErrorBaseline = diagnostics.errorCount();
|
final var compileErrorBaseline = diagnostics.errorCount();
|
||||||
final var irBackendFile = frontendCompiler.compileParsedFile(
|
final var irBackendFile = frontendCompiler.compileParsedFile(
|
||||||
parsedSource.fileId(),
|
parsedSource.fileId(),
|
||||||
@ -169,7 +177,10 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
parsedSource.sourceKind(),
|
parsedSource.sourceKind(),
|
||||||
renderModuleKey(moduleTable, parsedSource.moduleId()),
|
renderModuleKey(moduleTable, parsedSource.moduleId()),
|
||||||
ctx.hostAdmissionContext(),
|
ctx.hostAdmissionContext(),
|
||||||
nameTable);
|
nameTable,
|
||||||
|
importedSemanticContext.supplementalTopDecls(),
|
||||||
|
importedSemanticContext.importedCallables(),
|
||||||
|
importedSemanticContext.importedReservedMetadata());
|
||||||
if (diagnostics.errorCount() > compileErrorBaseline) {
|
if (diagnostics.errorCount() > compileErrorBaseline) {
|
||||||
failedModuleIds.add(parsedSource.moduleId());
|
failedModuleIds.add(parsedSource.moduleId());
|
||||||
}
|
}
|
||||||
@ -183,6 +194,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
}
|
}
|
||||||
irBackendAggregator.merge(compiledSource.irBackendFile());
|
irBackendAggregator.merge(compiledSource.irBackendFile());
|
||||||
}
|
}
|
||||||
|
irBackendAggregator.entryPointCallableName(PBSDefinitions.PBS.getEntryPointCallableName());
|
||||||
|
|
||||||
final var irBackend = irBackendAggregator.emit();
|
final var irBackend = irBackendAggregator.emit();
|
||||||
logs.using(log).debug("PBS frontend lowered to IR BE:\n%s".formatted(irBackend));
|
logs.using(log).debug("PBS frontend lowered to IR BE:\n%s".formatted(irBackend));
|
||||||
@ -358,6 +370,383 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
return dependenciesByModule;
|
return dependenciesByModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<FileId, ImportedSemanticContext> buildImportedSemanticContexts(
|
||||||
|
final ArrayList<ParsedSourceFile> parsedSourceFiles,
|
||||||
|
final ModuleTable moduleTable) {
|
||||||
|
final Map<ModuleId, ArrayList<ParsedSourceFile>> sourcesByModule = new HashMap<>();
|
||||||
|
for (final var parsedSource : parsedSourceFiles) {
|
||||||
|
sourcesByModule
|
||||||
|
.computeIfAbsent(parsedSource.moduleId(), ignored -> new ArrayList<>())
|
||||||
|
.add(parsedSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<ModuleId, IRReservedMetadata> reservedMetadataByModule = new HashMap<>();
|
||||||
|
for (final var entry : sourcesByModule.entrySet()) {
|
||||||
|
var merged = IRReservedMetadata.empty();
|
||||||
|
for (final var source : entry.getValue()) {
|
||||||
|
final var extracted = reservedMetadataExtractor.extract(source.ast(), source.sourceKind());
|
||||||
|
merged = mergeReservedMetadata(merged, extracted);
|
||||||
|
}
|
||||||
|
reservedMetadataByModule.put(entry.getKey(), merged);
|
||||||
|
}
|
||||||
|
final Map<ModuleId, Map<String, ArrayList<PbsAst.TopDecl>>> topDeclsByNameByModule = new HashMap<>();
|
||||||
|
for (final var entry : sourcesByModule.entrySet()) {
|
||||||
|
topDeclsByNameByModule.put(entry.getKey(), indexTopDeclsByName(entry.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<FileId, ImportedSemanticContext> contexts = new HashMap<>();
|
||||||
|
for (final var parsedSource : parsedSourceFiles) {
|
||||||
|
final var supplementalTopDecls = new ArrayList<PbsAst.TopDecl>();
|
||||||
|
final var importedCallables = new ArrayList<PbsFrontendCompiler.ImportedCallableSurface>();
|
||||||
|
final var importedCallableKeys = new HashSet<String>();
|
||||||
|
final var supplementalKeys = new HashSet<String>();
|
||||||
|
var importedReservedMetadata = IRReservedMetadata.empty();
|
||||||
|
|
||||||
|
for (final var importDecl : parsedSource.ast().imports()) {
|
||||||
|
final var moduleRef = importDecl.moduleRef();
|
||||||
|
final var importedModuleId = moduleId(moduleTable, moduleRef.project(), moduleRef.pathSegments());
|
||||||
|
final var importedSources = sourcesByModule.get(importedModuleId);
|
||||||
|
if (importedSources == null || importedSources.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var importedTopDeclsByName = topDeclsByNameByModule.get(importedModuleId);
|
||||||
|
if (importedTopDeclsByName == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var importedModuleKey = renderModuleKey(moduleTable, importedModuleId);
|
||||||
|
importedReservedMetadata = mergeReservedMetadata(
|
||||||
|
importedReservedMetadata,
|
||||||
|
reservedMetadataByModule.getOrDefault(importedModuleId, IRReservedMetadata.empty()));
|
||||||
|
|
||||||
|
for (final var importItem : importDecl.items()) {
|
||||||
|
final var importName = importItem.name();
|
||||||
|
final var localName = importItemLocalName(importItem);
|
||||||
|
final var directDecls = importedTopDeclsByName.getOrDefault(importName, new ArrayList<>());
|
||||||
|
for (final var topDecl : directDecls) {
|
||||||
|
appendSupplementalTopDecl(topDecl, supplementalTopDecls, supplementalKeys);
|
||||||
|
appendTypeDependencyDecls(topDecl, importedTopDeclsByName, supplementalTopDecls, supplementalKeys);
|
||||||
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
|
for (final var method : serviceDecl.methods()) {
|
||||||
|
appendImportedCallable(
|
||||||
|
importedCallables,
|
||||||
|
importedCallableKeys,
|
||||||
|
new PbsFrontendCompiler.ImportedCallableSurface(
|
||||||
|
importedModuleKey,
|
||||||
|
localName + "." + method.name(),
|
||||||
|
method.parameters().size(),
|
||||||
|
returnSlotsFor(method),
|
||||||
|
frontendCompiler.callableShapeSurfaceOf(method)));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
appendImportedCallable(
|
||||||
|
importedCallables,
|
||||||
|
importedCallableKeys,
|
||||||
|
new PbsFrontendCompiler.ImportedCallableSurface(
|
||||||
|
importedModuleKey,
|
||||||
|
localName,
|
||||||
|
functionDecl.parameters().size(),
|
||||||
|
returnSlotsFor(functionDecl),
|
||||||
|
frontendCompiler.callableShapeSurfaceOf(functionDecl)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts.put(parsedSource.fileId(), new ImportedSemanticContext(
|
||||||
|
ReadOnlyList.wrap(supplementalTopDecls),
|
||||||
|
ReadOnlyList.wrap(importedCallables),
|
||||||
|
importedReservedMetadata));
|
||||||
|
}
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, ArrayList<PbsAst.TopDecl>> indexTopDeclsByName(
|
||||||
|
final ArrayList<ParsedSourceFile> moduleSources) {
|
||||||
|
final Map<String, ArrayList<PbsAst.TopDecl>> index = new HashMap<>();
|
||||||
|
for (final var source : moduleSources) {
|
||||||
|
for (final var topDecl : source.ast().topDecls()) {
|
||||||
|
final var declName = topDeclName(topDecl);
|
||||||
|
if (declName == null || declName.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
index.computeIfAbsent(declName, ignored -> new ArrayList<>()).add(topDecl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendTypeDependencyDecls(
|
||||||
|
final PbsAst.TopDecl rootDecl,
|
||||||
|
final Map<String, ArrayList<PbsAst.TopDecl>> topDeclsByName,
|
||||||
|
final ArrayList<PbsAst.TopDecl> supplementalTopDecls,
|
||||||
|
final Set<String> supplementalKeys) {
|
||||||
|
final var pendingTypeNames = new ArrayDeque<String>();
|
||||||
|
final var visitedTypeNames = new HashSet<String>();
|
||||||
|
collectReferencedTypeNames(rootDecl, pendingTypeNames);
|
||||||
|
while (!pendingTypeNames.isEmpty()) {
|
||||||
|
final var typeName = pendingTypeNames.removeFirst();
|
||||||
|
if (!visitedTypeNames.add(typeName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var candidates = topDeclsByName.get(typeName);
|
||||||
|
if (candidates == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (final var candidate : candidates) {
|
||||||
|
if (!isTypeDecl(candidate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
appendSupplementalTopDecl(candidate, supplementalTopDecls, supplementalKeys);
|
||||||
|
collectReferencedTypeNames(candidate, pendingTypeNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectReferencedTypeNames(
|
||||||
|
final PbsAst.TopDecl topDecl,
|
||||||
|
final ArrayDeque<String> sink) {
|
||||||
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
|
for (final var field : structDecl.fields()) {
|
||||||
|
collectReferencedTypeNames(field.typeRef(), sink);
|
||||||
|
}
|
||||||
|
for (final var method : structDecl.methods()) {
|
||||||
|
collectReferencedTypeNames(method, sink);
|
||||||
|
}
|
||||||
|
for (final var ctor : structDecl.ctors()) {
|
||||||
|
for (final var parameter : ctor.parameters()) {
|
||||||
|
collectReferencedTypeNames(parameter.typeRef(), sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
|
for (final var field : builtinTypeDecl.fields()) {
|
||||||
|
collectReferencedTypeNames(field.typeRef(), sink);
|
||||||
|
}
|
||||||
|
for (final var signature : builtinTypeDecl.signatures()) {
|
||||||
|
collectReferencedTypeNames(signature, sink);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
|
for (final var method : serviceDecl.methods()) {
|
||||||
|
collectReferencedTypeNames(method, sink);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
|
for (final var signature : hostDecl.signatures()) {
|
||||||
|
collectReferencedTypeNames(signature, sink);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
|
for (final var signature : contractDecl.signatures()) {
|
||||||
|
collectReferencedTypeNames(signature, sink);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
||||||
|
collectReferencedTypeNames(callbackDecl.parameters(), callbackDecl.returnType(), callbackDecl.resultErrorType(), sink);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
collectReferencedTypeNames(functionDecl, sink);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
|
||||||
|
collectReferencedTypeNames(constDecl.explicitType(), sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectReferencedTypeNames(
|
||||||
|
final PbsAst.FunctionDecl functionDecl,
|
||||||
|
final ArrayDeque<String> sink) {
|
||||||
|
collectReferencedTypeNames(
|
||||||
|
functionDecl.parameters(),
|
||||||
|
functionDecl.returnType(),
|
||||||
|
functionDecl.resultErrorType(),
|
||||||
|
sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectReferencedTypeNames(
|
||||||
|
final PbsAst.FunctionSignature functionSignature,
|
||||||
|
final ArrayDeque<String> sink) {
|
||||||
|
collectReferencedTypeNames(
|
||||||
|
functionSignature.parameters(),
|
||||||
|
functionSignature.returnType(),
|
||||||
|
functionSignature.resultErrorType(),
|
||||||
|
sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectReferencedTypeNames(
|
||||||
|
final ReadOnlyList<PbsAst.Parameter> parameters,
|
||||||
|
final PbsAst.TypeRef returnType,
|
||||||
|
final PbsAst.TypeRef resultErrorType,
|
||||||
|
final ArrayDeque<String> sink) {
|
||||||
|
for (final var parameter : parameters) {
|
||||||
|
collectReferencedTypeNames(parameter.typeRef(), sink);
|
||||||
|
}
|
||||||
|
collectReferencedTypeNames(returnType, sink);
|
||||||
|
collectReferencedTypeNames(resultErrorType, sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectReferencedTypeNames(
|
||||||
|
final PbsAst.TypeRef typeRef,
|
||||||
|
final ArrayDeque<String> sink) {
|
||||||
|
if (typeRef == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (typeRef.kind()) {
|
||||||
|
case SIMPLE -> {
|
||||||
|
final var simpleName = typeRef.name();
|
||||||
|
if (!isPrimitiveTypeName(simpleName)) {
|
||||||
|
sink.addLast(simpleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OPTIONAL, GROUP -> collectReferencedTypeNames(typeRef.inner(), sink);
|
||||||
|
case NAMED_TUPLE -> {
|
||||||
|
for (final var field : typeRef.fields()) {
|
||||||
|
collectReferencedTypeNames(field.typeRef(), sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case UNIT, SELF, ERROR -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPrimitiveTypeName(final String typeName) {
|
||||||
|
return "int".equals(typeName)
|
||||||
|
|| "float".equals(typeName)
|
||||||
|
|| "bool".equals(typeName)
|
||||||
|
|| "str".equals(typeName)
|
||||||
|
|| "unit".equals(typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTypeDecl(final PbsAst.TopDecl topDecl) {
|
||||||
|
return topDecl instanceof PbsAst.StructDecl
|
||||||
|
|| topDecl instanceof PbsAst.BuiltinTypeDecl
|
||||||
|
|| topDecl instanceof PbsAst.ServiceDecl
|
||||||
|
|| topDecl instanceof PbsAst.HostDecl
|
||||||
|
|| topDecl instanceof PbsAst.ContractDecl
|
||||||
|
|| topDecl instanceof PbsAst.CallbackDecl
|
||||||
|
|| topDecl instanceof PbsAst.EnumDecl
|
||||||
|
|| topDecl instanceof PbsAst.ErrorDecl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendSupplementalTopDecl(
|
||||||
|
final PbsAst.TopDecl topDecl,
|
||||||
|
final ArrayList<PbsAst.TopDecl> supplementalTopDecls,
|
||||||
|
final Set<String> supplementalKeys) {
|
||||||
|
final var key = topDeclKey(topDecl);
|
||||||
|
if (key == null || key.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (supplementalKeys.add(key)) {
|
||||||
|
supplementalTopDecls.add(topDecl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendImportedCallable(
|
||||||
|
final ArrayList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables,
|
||||||
|
final Set<String> importedCallableKeys,
|
||||||
|
final PbsFrontendCompiler.ImportedCallableSurface importedCallableSurface) {
|
||||||
|
final var callableKey = importedCallableSurface.moduleKey()
|
||||||
|
+ "#"
|
||||||
|
+ importedCallableSurface.callableName()
|
||||||
|
+ "#"
|
||||||
|
+ importedCallableSurface.arity()
|
||||||
|
+ "#"
|
||||||
|
+ importedCallableSurface.shapeSurface();
|
||||||
|
if (importedCallableKeys.add(callableKey)) {
|
||||||
|
importedCallables.add(importedCallableSurface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String topDeclKey(final PbsAst.TopDecl topDecl) {
|
||||||
|
final var declName = topDeclName(topDecl);
|
||||||
|
if (declName == null || declName.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return topDecl.getClass().getSimpleName() + ":" + declName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String topDeclName(final PbsAst.TopDecl topDecl) {
|
||||||
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
|
return structDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
|
return builtinTypeDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||||
|
return constDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
|
return serviceDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
|
return hostDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
|
return contractDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
return functionDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
||||||
|
return callbackDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||||
|
return enumDecl.name();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
|
return errorDecl.name();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRReservedMetadata mergeReservedMetadata(
|
||||||
|
final IRReservedMetadata left,
|
||||||
|
final IRReservedMetadata right) {
|
||||||
|
final var hostBindings = new ArrayList<IRReservedMetadata.HostMethodBinding>();
|
||||||
|
hostBindings.addAll(left.hostMethodBindings().asList());
|
||||||
|
hostBindings.addAll(right.hostMethodBindings().asList());
|
||||||
|
|
||||||
|
final var builtinTypeSurfaces = new ArrayList<IRReservedMetadata.BuiltinTypeSurface>();
|
||||||
|
builtinTypeSurfaces.addAll(left.builtinTypeSurfaces().asList());
|
||||||
|
builtinTypeSurfaces.addAll(right.builtinTypeSurfaces().asList());
|
||||||
|
|
||||||
|
final var builtinConstSurfaces = new ArrayList<IRReservedMetadata.BuiltinConstSurface>();
|
||||||
|
builtinConstSurfaces.addAll(left.builtinConstSurfaces().asList());
|
||||||
|
builtinConstSurfaces.addAll(right.builtinConstSurfaces().asList());
|
||||||
|
|
||||||
|
final var requiredCapabilities = new HashSet<String>();
|
||||||
|
requiredCapabilities.addAll(left.requiredCapabilities().asList());
|
||||||
|
requiredCapabilities.addAll(right.requiredCapabilities().asList());
|
||||||
|
|
||||||
|
return new IRReservedMetadata(
|
||||||
|
ReadOnlyList.wrap(hostBindings),
|
||||||
|
ReadOnlyList.wrap(builtinTypeSurfaces),
|
||||||
|
ReadOnlyList.wrap(builtinConstSurfaces),
|
||||||
|
ReadOnlyList.wrap(requiredCapabilities.stream().toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) {
|
||||||
|
return switch (functionDecl.returnKind()) {
|
||||||
|
case INFERRED_UNIT, EXPLICIT_UNIT -> 0;
|
||||||
|
case PLAIN, RESULT -> 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String importItemLocalName(final PbsAst.ImportItem importItem) {
|
||||||
|
if (importItem.alias() == null || importItem.alias().isBlank()) {
|
||||||
|
return importItem.name();
|
||||||
|
}
|
||||||
|
return importItem.alias();
|
||||||
|
}
|
||||||
|
|
||||||
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, Set<ModuleId>> dependenciesByModule) {
|
||||||
@ -420,4 +809,13 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
ModuleId moduleId,
|
ModuleId moduleId,
|
||||||
p.studio.compiler.models.IRBackendFile irBackendFile) {
|
p.studio.compiler.models.IRBackendFile irBackendFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record ImportedSemanticContext(
|
||||||
|
ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||||
|
ReadOnlyList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables,
|
||||||
|
IRReservedMetadata importedReservedMetadata) {
|
||||||
|
private static ImportedSemanticContext empty() {
|
||||||
|
return new ImportedSemanticContext(ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,62 @@
|
|||||||
|
declare host LowLog {
|
||||||
|
[Host(module = "log", name = "write", version = 1)]
|
||||||
|
[Capability(name = "log")]
|
||||||
|
fn write(level: int, message: str) -> void;
|
||||||
|
|
||||||
|
[Host(module = "log", name = "write_tag", version = 1)]
|
||||||
|
[Capability(name = "log")]
|
||||||
|
fn write_tag(level: int, tag: int, message: str) -> void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare service Log
|
||||||
|
{
|
||||||
|
fn trace(msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write(0, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug(msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write(1, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info(msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write(2, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warn(msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write(3, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error(msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write(4, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trace(tag: int, msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write_tag(0, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug(tag: int, msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write_tag(1, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info(tag: int, msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write_tag(2, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warn(tag: int, msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write_tag(3, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error(tag: int, msg: str) -> void
|
||||||
|
{
|
||||||
|
LowLog.write_tag(4, tag, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
mod host LowLog;
|
||||||
|
pub service Log;
|
||||||
@ -95,9 +95,10 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
import { Color } from @core:color;
|
import { Color } from @core:color;
|
||||||
import { Gfx } from @sdk:gfx;
|
import { Gfx } from @sdk:gfx;
|
||||||
import { Input, InputPad, InputButton } from @sdk:input;
|
import { Input, InputPad, InputButton } from @sdk:input;
|
||||||
|
import { Log } from @sdk:log;
|
||||||
|
|
||||||
declare contract Renderer {
|
declare contract Renderer {
|
||||||
fn render(gfx: Gfx, color: Color, input: Input, pad: InputPad, button: InputButton) -> void;
|
fn render(gfx: Gfx, color: Color, input: Input, pad: InputPad, button: InputButton, log: Log) -> void;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
"pub contract Renderer;",
|
"pub contract Renderer;",
|
||||||
@ -113,9 +114,19 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
.anyMatch(t -> t.sourceTypeName().equals("Input") && t.canonicalTypeName().equals("input")));
|
.anyMatch(t -> t.sourceTypeName().equals("Input") && t.canonicalTypeName().equals("input")));
|
||||||
assertTrue(positive.irBackend().getReservedMetadata().builtinTypeSurfaces().stream()
|
assertTrue(positive.irBackend().getReservedMetadata().builtinTypeSurfaces().stream()
|
||||||
.anyMatch(t -> t.sourceTypeName().equals("InputButton")
|
.anyMatch(t -> t.sourceTypeName().equals("InputButton")
|
||||||
&& t.intrinsics().stream().anyMatch(i -> i.canonicalName().equals("hold"))));
|
&& t.intrinsics().stream().anyMatch(i -> i.canonicalName().equals("input.button.hold"))));
|
||||||
assertTrue(positive.irBackend().getReservedMetadata().hostMethodBindings().stream()
|
assertTrue(positive.irBackend().getReservedMetadata().hostMethodBindings().stream()
|
||||||
.anyMatch(h -> h.ownerName().equals("Gfx")));
|
.anyMatch(h -> h.ownerName().equals("Gfx")));
|
||||||
|
assertTrue(positive.irBackend().getReservedMetadata().hostMethodBindings().stream()
|
||||||
|
.anyMatch(h -> h.ownerName().equals("LowLog")
|
||||||
|
&& h.abiModule().equals("log")
|
||||||
|
&& h.abiMethod().equals("write")
|
||||||
|
&& h.abiVersion() == 1));
|
||||||
|
assertTrue(positive.irBackend().getReservedMetadata().hostMethodBindings().stream()
|
||||||
|
.anyMatch(h -> h.ownerName().equals("LowLog")
|
||||||
|
&& h.abiModule().equals("log")
|
||||||
|
&& h.abiMethod().equals("write_tag")
|
||||||
|
&& h.abiVersion() == 1));
|
||||||
|
|
||||||
final var negative = compileWorkspaceModule(
|
final var negative = compileWorkspaceModule(
|
||||||
tempDir.resolve("gate-u-reserved-import-negative"),
|
tempDir.resolve("gate-u-reserved-import-negative"),
|
||||||
@ -129,6 +140,19 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
final var missingModule = firstDiagnostic(negative.diagnostics(),
|
final var missingModule = firstDiagnostic(negative.diagnostics(),
|
||||||
d -> d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name()));
|
d -> d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name()));
|
||||||
assertStableDiagnosticIdentity(missingModule, PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name(), DiagnosticPhase.LINKING);
|
assertStableDiagnosticIdentity(missingModule, PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name(), DiagnosticPhase.LINKING);
|
||||||
|
|
||||||
|
final var lowLogNotPublic = compileWorkspaceModule(
|
||||||
|
tempDir.resolve("gate-u-reserved-import-lowlog-hidden"),
|
||||||
|
"""
|
||||||
|
import { LowLog } from @sdk:log;
|
||||||
|
fn run() -> int { return 1; }
|
||||||
|
""",
|
||||||
|
"pub fn run() -> int;",
|
||||||
|
1,
|
||||||
|
d -> d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name()));
|
||||||
|
final var notPublic = firstDiagnostic(lowLogNotPublic.diagnostics(),
|
||||||
|
d -> d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name()));
|
||||||
|
assertStableDiagnosticIdentity(notPublic, PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name(), DiagnosticPhase.LINKING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -142,6 +142,31 @@ class PbsParserTest {
|
|||||||
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name())));
|
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldParseServiceAndMemberNamesUsingErrorKeyword() {
|
||||||
|
final var source = """
|
||||||
|
declare service Log {
|
||||||
|
fn error(msg: str) -> void { return; }
|
||||||
|
fn error(tag: int, msg: str) -> void { return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> void {
|
||||||
|
Log.error("oops");
|
||||||
|
Log.error(7, "oops");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var fileId = new FileId(0);
|
||||||
|
|
||||||
|
final PbsAst.File ast = PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.isEmpty(), "Parser should accept 'error' as callable/member name");
|
||||||
|
final var service = assertInstanceOf(PbsAst.ServiceDecl.class, ast.topDecls().getFirst());
|
||||||
|
assertEquals(2, service.methods().size());
|
||||||
|
assertEquals("error", service.methods().getFirst().name());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldAcceptStructFieldTrailingComma() {
|
void shouldAcceptStructFieldTrailingComma() {
|
||||||
final var source = "declare struct S(a: int,);";
|
final var source = "declare struct S(a: int,);";
|
||||||
|
|||||||
@ -81,10 +81,6 @@ class PbsInterfaceModuleSemanticsTest {
|
|||||||
final var source = """
|
final var source = """
|
||||||
fn run() -> int { return 1; }
|
fn run() -> int { return 1; }
|
||||||
|
|
||||||
declare service Game {
|
|
||||||
fn tick() -> int { return 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
declare contract C { fn run() -> int; }
|
declare contract C { fn run() -> int; }
|
||||||
declare struct S(v: int);
|
declare struct S(v: int);
|
||||||
implements C for S using s {
|
implements C for S using s {
|
||||||
@ -98,7 +94,30 @@ class PbsInterfaceModuleSemanticsTest {
|
|||||||
final var nonDeclarativeCount = diagnostics.stream()
|
final var nonDeclarativeCount = diagnostics.stream()
|
||||||
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name()))
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name()))
|
||||||
.count();
|
.count();
|
||||||
assertTrue(nonDeclarativeCount >= 3);
|
assertTrue(nonDeclarativeCount >= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAllowDeclarativeServiceFacadesInInterfaceModule() {
|
||||||
|
final var source = """
|
||||||
|
declare host LowLog {
|
||||||
|
[Host(module = "log", name = "write", version = 1)]
|
||||||
|
[Capability(name = "log")]
|
||||||
|
fn write(level: int, message: str) -> void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare service Log {
|
||||||
|
fn trace(message: str) -> void {
|
||||||
|
LowLog.write(0, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(3), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -181,6 +181,7 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
BuildingIssueSink.empty());
|
BuildingIssueSink.empty());
|
||||||
|
|
||||||
assertTrue(diagnostics.isEmpty());
|
assertTrue(diagnostics.isEmpty());
|
||||||
|
assertEquals("frame", irBackend.getEntryPointCallableName());
|
||||||
assertEquals(2, irBackend.getFunctions().size());
|
assertEquals(2, irBackend.getFunctions().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,6 +784,89 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
assertEquals("independent", irBackend.getFunctions().getFirst().name());
|
assertEquals("independent", irBackend.getFunctions().getFirst().name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveImportedBuiltinTypeChainsInExpressionFlowSemantics() throws IOException {
|
||||||
|
final var projectRoot = tempDir.resolve("project-builtin-chain");
|
||||||
|
final var sourceRoot = projectRoot.resolve("src");
|
||||||
|
Files.createDirectories(sourceRoot);
|
||||||
|
|
||||||
|
final var sourceFile = sourceRoot.resolve("main.pbs");
|
||||||
|
final var modBarrel = sourceRoot.resolve("mod.barrel");
|
||||||
|
Files.writeString(sourceFile, """
|
||||||
|
import { Log } from @sdk:log;
|
||||||
|
import { Input } from @sdk:input;
|
||||||
|
|
||||||
|
fn frame() -> void
|
||||||
|
{
|
||||||
|
if (Input.pad().a().pressed())
|
||||||
|
{
|
||||||
|
Log.debug("Hello World!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
Files.writeString(modBarrel, "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("main")
|
||||||
|
.version("1.0.0")
|
||||||
|
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registerFile(projectId, projectRoot, sourceFile, fileTable);
|
||||||
|
registerFile(projectId, projectRoot, modBarrel, fileTable);
|
||||||
|
|
||||||
|
final var ctx = new FrontendPhaseContext(
|
||||||
|
projectTable,
|
||||||
|
fileTable,
|
||||||
|
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
|
||||||
|
1);
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
final var irBackend = new PBSFrontendPhaseService().compile(
|
||||||
|
ctx,
|
||||||
|
diagnostics,
|
||||||
|
LogAggregator.empty(),
|
||||||
|
BuildingIssueSink.empty());
|
||||||
|
|
||||||
|
assertTrue(diagnostics.stream().noneMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name())));
|
||||||
|
assertTrue(diagnostics.stream().noneMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_APPLY_NON_CALLABLE_TARGET.name())));
|
||||||
|
final var frameExecutableOptional = irBackend.getExecutableFunctions().stream()
|
||||||
|
.filter(function -> "frame".equals(function.callableName()))
|
||||||
|
.findFirst();
|
||||||
|
final var executableNames = irBackend.getExecutableFunctions().stream()
|
||||||
|
.map(function -> function.callableName() + "@" + function.moduleKey())
|
||||||
|
.toList();
|
||||||
|
assertTrue(
|
||||||
|
frameExecutableOptional.isPresent(),
|
||||||
|
"diagnostics="
|
||||||
|
+ diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList()
|
||||||
|
+ " executableNames="
|
||||||
|
+ executableNames);
|
||||||
|
final var frameExecutable = frameExecutableOptional.orElseThrow();
|
||||||
|
final var intrinsicCalls = frameExecutable.instructions().stream()
|
||||||
|
.filter(instruction ->
|
||||||
|
instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC)
|
||||||
|
.toList();
|
||||||
|
assertTrue(intrinsicCalls.stream().anyMatch(instruction -> Integer.valueOf(1).equals(instruction.expectedArgSlots())));
|
||||||
|
assertTrue(intrinsicCalls.stream().anyMatch(instruction ->
|
||||||
|
instruction.intrinsicCall() != null && "input.pad".equals(instruction.intrinsicCall().canonicalName())));
|
||||||
|
assertTrue(intrinsicCalls.stream().anyMatch(instruction ->
|
||||||
|
instruction.intrinsicCall() != null && "input.pad.a".equals(instruction.intrinsicCall().canonicalName())));
|
||||||
|
assertTrue(intrinsicCalls.stream().anyMatch(instruction ->
|
||||||
|
instruction.intrinsicCall() != null && "input.button.pressed".equals(instruction.intrinsicCall().canonicalName())));
|
||||||
|
assertTrue(
|
||||||
|
irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName())),
|
||||||
|
"diagnostics="
|
||||||
|
+ diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList()
|
||||||
|
+ " executableNames="
|
||||||
|
+ executableNames);
|
||||||
|
}
|
||||||
|
|
||||||
private void registerFile(
|
private void registerFile(
|
||||||
final ProjectId projectId,
|
final ProjectId projectId,
|
||||||
final Path projectRoot,
|
final Path projectRoot,
|
||||||
|
|||||||
@ -0,0 +1,73 @@
|
|||||||
|
package p.studio.compiler.backend.irvm;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
|
final class IRVMIntrinsicRegistry {
|
||||||
|
private static final Map<IntrinsicKey, Integer> FINAL_ID_BY_INTRINSIC = createRegistry();
|
||||||
|
|
||||||
|
private IRVMIntrinsicRegistry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static OptionalInt resolveFinalId(
|
||||||
|
final String canonicalName,
|
||||||
|
final long canonicalVersion) {
|
||||||
|
if (canonicalName == null || canonicalName.isBlank()) {
|
||||||
|
return OptionalInt.empty();
|
||||||
|
}
|
||||||
|
final var resolved = FINAL_ID_BY_INTRINSIC.get(new IntrinsicKey(canonicalName, canonicalVersion));
|
||||||
|
if (resolved == null) {
|
||||||
|
return OptionalInt.empty();
|
||||||
|
}
|
||||||
|
return OptionalInt.of(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<IntrinsicKey, Integer> createRegistry() {
|
||||||
|
final var registry = new HashMap<IntrinsicKey, Integer>();
|
||||||
|
register(registry, "vec2.dot", 1, 0x1000);
|
||||||
|
register(registry, "vec2.length", 1, 0x1001);
|
||||||
|
|
||||||
|
register(registry, "input.pad", 1, 0x2000);
|
||||||
|
register(registry, "input.touch", 1, 0x2001);
|
||||||
|
|
||||||
|
register(registry, "input.pad.up", 1, 0x2010);
|
||||||
|
register(registry, "input.pad.down", 1, 0x2011);
|
||||||
|
register(registry, "input.pad.left", 1, 0x2012);
|
||||||
|
register(registry, "input.pad.right", 1, 0x2013);
|
||||||
|
register(registry, "input.pad.a", 1, 0x2014);
|
||||||
|
register(registry, "input.pad.b", 1, 0x2015);
|
||||||
|
register(registry, "input.pad.x", 1, 0x2016);
|
||||||
|
register(registry, "input.pad.y", 1, 0x2017);
|
||||||
|
register(registry, "input.pad.l", 1, 0x2018);
|
||||||
|
register(registry, "input.pad.r", 1, 0x2019);
|
||||||
|
register(registry, "input.pad.start", 1, 0x201A);
|
||||||
|
register(registry, "input.pad.select", 1, 0x201B);
|
||||||
|
|
||||||
|
register(registry, "input.touch.button", 1, 0x2020);
|
||||||
|
register(registry, "input.touch.x", 1, 0x2021);
|
||||||
|
register(registry, "input.touch.y", 1, 0x2022);
|
||||||
|
|
||||||
|
register(registry, "input.button.pressed", 1, 0x2030);
|
||||||
|
register(registry, "input.button.released", 1, 0x2031);
|
||||||
|
register(registry, "input.button.down", 1, 0x2032);
|
||||||
|
register(registry, "input.button.hold", 1, 0x2033);
|
||||||
|
return Map.copyOf(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void register(
|
||||||
|
final Map<IntrinsicKey, Integer> registry,
|
||||||
|
final String canonicalName,
|
||||||
|
final long canonicalVersion,
|
||||||
|
final int finalId) {
|
||||||
|
final var previous = registry.put(new IntrinsicKey(canonicalName, canonicalVersion), finalId);
|
||||||
|
if (previous != null && previous != finalId) {
|
||||||
|
throw new IllegalStateException("duplicate intrinsic registry identity: " + canonicalName + "@" + canonicalVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record IntrinsicKey(
|
||||||
|
String canonicalName,
|
||||||
|
long canonicalVersion) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package p.studio.compiler.backend.irvm;
|
|||||||
|
|
||||||
public enum IRVMLoweringErrorCode {
|
public enum IRVMLoweringErrorCode {
|
||||||
LOWER_IRVM_EMPTY_EXECUTABLE_INPUT,
|
LOWER_IRVM_EMPTY_EXECUTABLE_INPUT,
|
||||||
|
LOWER_IRVM_ENTRYPOINT_DECLARATION_MISSING,
|
||||||
LOWER_IRVM_ENTRYPOINT_MISSING,
|
LOWER_IRVM_ENTRYPOINT_MISSING,
|
||||||
LOWER_IRVM_ENTRYPOINT_AMBIGUOUS,
|
LOWER_IRVM_ENTRYPOINT_AMBIGUOUS,
|
||||||
LOWER_IRVM_MISSING_CALLEE,
|
LOWER_IRVM_MISSING_CALLEE,
|
||||||
|
|||||||
@ -173,7 +173,7 @@ public class IRVMValidator {
|
|||||||
if (inHeight < pops) {
|
if (inHeight < pops) {
|
||||||
throw new IRVMValidationException(
|
throw new IRVMValidationException(
|
||||||
IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_STACK_UNDERFLOW,
|
IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_STACK_UNDERFLOW,
|
||||||
"stack underflow",
|
"stack underflow. need=%d have=%d".formatted(pops, inHeight),
|
||||||
functionIndex,
|
functionIndex,
|
||||||
pc);
|
pc);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ public class LowerToIRVMService {
|
|||||||
"IRBackend has no executable functions");
|
"IRBackend has no executable functions");
|
||||||
}
|
}
|
||||||
|
|
||||||
final var ordered = orderFunctions(backend.getExecutableFunctions());
|
final var ordered = orderFunctions(backend);
|
||||||
final var funcIdByCallableId = new HashMap<CallableId, Integer>();
|
final var funcIdByCallableId = new HashMap<CallableId, Integer>();
|
||||||
for (var i = 0; i < ordered.size(); i++) {
|
for (var i = 0; i < ordered.size(); i++) {
|
||||||
final var fn = ordered.get(i);
|
final var fn = ordered.get(i);
|
||||||
@ -145,9 +145,31 @@ public class LowerToIRVMService {
|
|||||||
IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID,
|
IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID,
|
||||||
"invalid intrinsic id: " + intrinsic.intrinsicId());
|
"invalid intrinsic id: " + intrinsic.intrinsicId());
|
||||||
}
|
}
|
||||||
instructions.add(new IRVMInstruction(IRVMOp.INTRINSIC, intrinsicIndex));
|
final var canonicalIntrinsic = backend.getIntrinsicPool().get(intrinsicIndex);
|
||||||
|
if (!canonicalIntrinsic.canonicalName().equals(intrinsic.canonicalName())
|
||||||
|
|| canonicalIntrinsic.canonicalVersion() != intrinsic.canonicalVersion()) {
|
||||||
|
throw loweringError(
|
||||||
|
fn,
|
||||||
|
instr,
|
||||||
|
IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID,
|
||||||
|
"intrinsic identity mismatch between callsite and pool: call=%s@%d pool=%s@%d".formatted(
|
||||||
|
intrinsic.canonicalName(),
|
||||||
|
intrinsic.canonicalVersion(),
|
||||||
|
canonicalIntrinsic.canonicalName(),
|
||||||
|
canonicalIntrinsic.canonicalVersion()));
|
||||||
|
}
|
||||||
|
final var finalIntrinsicId = IRVMIntrinsicRegistry
|
||||||
|
.resolveFinalId(canonicalIntrinsic.canonicalName(), canonicalIntrinsic.canonicalVersion())
|
||||||
|
.orElseThrow(() -> loweringError(
|
||||||
|
fn,
|
||||||
|
instr,
|
||||||
|
IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID,
|
||||||
|
"unknown VM-owned intrinsic identity: %s@%d".formatted(
|
||||||
|
canonicalIntrinsic.canonicalName(),
|
||||||
|
canonicalIntrinsic.canonicalVersion())));
|
||||||
|
instructions.add(new IRVMInstruction(IRVMOp.INTRINSIC, finalIntrinsicId));
|
||||||
operations.add(BytecodeEmitter.Operation.intrinsic(
|
operations.add(BytecodeEmitter.Operation.intrinsic(
|
||||||
intrinsicIndex,
|
finalIntrinsicId,
|
||||||
instr.expectedArgSlots(),
|
instr.expectedArgSlots(),
|
||||||
instr.expectedRetSlots(),
|
instr.expectedRetSlots(),
|
||||||
sourceSpan));
|
sourceSpan));
|
||||||
@ -231,22 +253,29 @@ public class LowerToIRVMService {
|
|||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadOnlyList<IRBackendExecutableFunction> orderFunctions(
|
private ReadOnlyList<IRBackendExecutableFunction> orderFunctions(final IRBackend backend) {
|
||||||
final ReadOnlyList<IRBackendExecutableFunction> functions) {
|
final var entryPointCallableName = backend.getEntryPointCallableName();
|
||||||
final var sorted = new ArrayList<>(functions.asList());
|
if (entryPointCallableName == null || entryPointCallableName.isBlank()) {
|
||||||
|
throw new IRVMLoweringException(
|
||||||
|
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_DECLARATION_MISSING,
|
||||||
|
"frontend IRBackend entrypoint declaration is missing");
|
||||||
|
}
|
||||||
|
final var sorted = new ArrayList<>(backend.getExecutableFunctions().asList());
|
||||||
sorted.sort(FUNCTION_ORDER);
|
sorted.sort(FUNCTION_ORDER);
|
||||||
final var entrypoints = sorted.stream()
|
final var entrypoints = sorted.stream()
|
||||||
.filter(candidate -> "main".equals(candidate.callableName()))
|
.filter(candidate -> entryPointCallableName.equals(candidate.callableName()))
|
||||||
.toList();
|
.toList();
|
||||||
if (entrypoints.isEmpty()) {
|
if (entrypoints.isEmpty()) {
|
||||||
throw new IRVMLoweringException(
|
throw new IRVMLoweringException(
|
||||||
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING,
|
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING,
|
||||||
"missing entrypoint callable 'main'");
|
"missing entrypoint callable '%s'".formatted(entryPointCallableName));
|
||||||
}
|
}
|
||||||
if (entrypoints.size() > 1) {
|
if (entrypoints.size() > 1) {
|
||||||
throw new IRVMLoweringException(
|
throw new IRVMLoweringException(
|
||||||
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS,
|
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS,
|
||||||
"ambiguous entrypoint: found %d callables named 'main'".formatted(entrypoints.size()));
|
"ambiguous entrypoint: found %d callables named '%s'".formatted(
|
||||||
|
entrypoints.size(),
|
||||||
|
entryPointCallableName));
|
||||||
}
|
}
|
||||||
final var entrypoint = entrypoints.getFirst();
|
final var entrypoint = entrypoints.getFirst();
|
||||||
final var ordered = new ArrayList<IRBackendExecutableFunction>(sorted.size());
|
final var ordered = new ArrayList<IRBackendExecutableFunction>(sorted.size());
|
||||||
|
|||||||
@ -21,6 +21,7 @@ public class BuilderPipelineContext {
|
|||||||
public IRVMProgram optimizedIrvm;
|
public IRVMProgram optimizedIrvm;
|
||||||
public BytecodeModule bytecodeModule;
|
public BytecodeModule bytecodeModule;
|
||||||
public byte[] bytecodeBytes;
|
public byte[] bytecodeBytes;
|
||||||
|
public Path bytecodeArtifactPath;
|
||||||
|
|
||||||
private BuilderPipelineContext(
|
private BuilderPipelineContext(
|
||||||
final BuilderPipelineConfig config,
|
final BuilderPipelineConfig config,
|
||||||
|
|||||||
@ -23,7 +23,8 @@ public class BuilderPipelineService {
|
|||||||
new OptimizeIRVMPipelineStage(),
|
new OptimizeIRVMPipelineStage(),
|
||||||
new EmitBytecodePipelineStage(),
|
new EmitBytecodePipelineStage(),
|
||||||
new LinkBytecodePipelineStage(),
|
new LinkBytecodePipelineStage(),
|
||||||
new VerifyBytecodePipelineStage()
|
new VerifyBytecodePipelineStage(),
|
||||||
|
new WriteBytecodeArtifactPipelineStage()
|
||||||
);
|
);
|
||||||
INSTANCE = new BuilderPipelineService(stages);
|
INSTANCE = new BuilderPipelineService(stages);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
package p.studio.compiler.workspaces.stages;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.models.BuilderPipelineContext;
|
||||||
|
import p.studio.compiler.workspaces.PipelineStage;
|
||||||
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class WriteBytecodeArtifactPipelineStage implements PipelineStage {
|
||||||
|
private static final String OUTPUT_DIR = "build";
|
||||||
|
private static final String OUTPUT_FILE = "program.pbx";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
|
||||||
|
if (ctx.rootProjectPathCanon == null) {
|
||||||
|
return BuildingIssueSink.empty()
|
||||||
|
.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.phase("BACKEND_WRITE_ARTIFACT")
|
||||||
|
.code("MARSHAL_FORMAT_STAGE_INPUT_MISSING")
|
||||||
|
.message("[BUILD]: root project path is missing before WriteBytecodeArtifact stage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var bytes = resolveBytes(ctx);
|
||||||
|
if (bytes == null || bytes.length == 0) {
|
||||||
|
return BuildingIssueSink.empty()
|
||||||
|
.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.phase("BACKEND_WRITE_ARTIFACT")
|
||||||
|
.code("MARSHAL_FORMAT_STAGE_INPUT_MISSING")
|
||||||
|
.message("[BUILD]: bytecode bytes are missing before WriteBytecodeArtifact stage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var outputDir = ctx.rootProjectPathCanon.resolve(OUTPUT_DIR);
|
||||||
|
final var outputPath = outputDir.resolve(OUTPUT_FILE);
|
||||||
|
try {
|
||||||
|
Files.createDirectories(outputDir);
|
||||||
|
Files.write(
|
||||||
|
outputPath,
|
||||||
|
bytes,
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.TRUNCATE_EXISTING,
|
||||||
|
StandardOpenOption.WRITE);
|
||||||
|
ctx.bytecodeArtifactPath = outputPath;
|
||||||
|
logs.using(log).info("[BUILD]: wrote bytecode artifact to " + outputPath);
|
||||||
|
return BuildingIssueSink.empty();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return BuildingIssueSink.empty()
|
||||||
|
.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.phase("BACKEND_WRITE_ARTIFACT")
|
||||||
|
.code("MARSHAL_ARTIFACT_WRITE_FAILED")
|
||||||
|
.message("[BUILD]: failed to write bytecode artifact: " + outputPath)
|
||||||
|
.exception(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] resolveBytes(final BuilderPipelineContext ctx) {
|
||||||
|
if (ctx.bytecodeBytes != null && ctx.bytecodeBytes.length > 0) {
|
||||||
|
return ctx.bytecodeBytes;
|
||||||
|
}
|
||||||
|
if (ctx.bytecodeModule != null) {
|
||||||
|
return ctx.bytecodeModule.serialize();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -68,7 +68,7 @@ class GoldenArtifactsTest {
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
null,
|
null,
|
||||||
new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, new IntrinsicId(0)),
|
new IRBackendExecutableFunction.IntrinsicCallMetadata("input.pad", 1, new IntrinsicId(0)),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
Span.none()),
|
Span.none()),
|
||||||
@ -80,7 +80,7 @@ class GoldenArtifactsTest {
|
|||||||
null,
|
null,
|
||||||
Span.none())),
|
Span.none())),
|
||||||
Span.none())))
|
Span.none())))
|
||||||
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))
|
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("input.pad", 1)))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -204,7 +204,7 @@ class IRVMValidatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validateProgramMustApplyHostcallStackEffectsFromMetadata() {
|
void validateProgramMustRejectHostcallWhenDeclaredArgsAreMissingOnStack() {
|
||||||
final var module = new IRVMModule(
|
final var module = new IRVMModule(
|
||||||
"core-v1",
|
"core-v1",
|
||||||
ReadOnlyList.from(new IRVMFunction(
|
ReadOnlyList.from(new IRVMFunction(
|
||||||
|
|||||||
@ -36,15 +36,34 @@ class LowerToIRVMServiceTest {
|
|||||||
assertEquals(0, lowered.module().functions().get(1).instructions().get(0).immediate());
|
assertEquals(0, lowered.module().functions().get(1).instructions().get(0).immediate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lowerMustUseFrontendDeclaredEntrypointCallable() {
|
||||||
|
final var backend = IRBackend.builder()
|
||||||
|
.entryPointCallableName("frame")
|
||||||
|
.executableFunctions(ReadOnlyList.from(
|
||||||
|
fn("aux", "app", 11, ReadOnlyList.from(
|
||||||
|
callFunc("app", "frame", 10),
|
||||||
|
ret())),
|
||||||
|
fn("frame", "app", 10, ReadOnlyList.from(
|
||||||
|
ret()))))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final var lowered = new LowerToIRVMService().lower(backend);
|
||||||
|
|
||||||
|
assertEquals("frame", lowered.module().functions().get(0).name());
|
||||||
|
assertEquals("aux", lowered.module().functions().get(1).name());
|
||||||
|
assertEquals(0, lowered.module().functions().get(1).instructions().get(0).immediate());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void lowerMustMapHostAndIntrinsicCallsites() {
|
void lowerMustMapHostAndIntrinsicCallsites() {
|
||||||
final var backend = IRBackend.builder()
|
final var backend = IRBackend.builder()
|
||||||
.executableFunctions(ReadOnlyList.from(
|
.executableFunctions(ReadOnlyList.from(
|
||||||
fn("main", "app", 10, ReadOnlyList.from(
|
fn("main", "app", 10, ReadOnlyList.from(
|
||||||
callHost("gfx", "draw_pixel", 1, 0, 0),
|
callHost("gfx", "draw_pixel", 1, 0, 0),
|
||||||
callIntrinsic("core.color.pack", 1, 0),
|
callIntrinsic("input.pad", 1, 0),
|
||||||
ret()))))
|
ret()))))
|
||||||
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))
|
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("input.pad", 1)))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final var lowered = new LowerToIRVMService().lower(backend);
|
final var lowered = new LowerToIRVMService().lower(backend);
|
||||||
@ -52,7 +71,7 @@ class LowerToIRVMServiceTest {
|
|||||||
final var instructions = lowered.module().functions().getFirst().instructions();
|
final var instructions = lowered.module().functions().getFirst().instructions();
|
||||||
assertEquals(IRVMOp.HOSTCALL, instructions.get(0).op());
|
assertEquals(IRVMOp.HOSTCALL, instructions.get(0).op());
|
||||||
assertEquals(IRVMOp.INTRINSIC, instructions.get(1).op());
|
assertEquals(IRVMOp.INTRINSIC, instructions.get(1).op());
|
||||||
assertEquals(0, instructions.get(1).immediate());
|
assertEquals(0x2000, instructions.get(1).immediate());
|
||||||
final var emissionOps = lowered.emissionPlan().functions().getFirst().operations();
|
final var emissionOps = lowered.emissionPlan().functions().getFirst().operations();
|
||||||
assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.HOSTCALL, emissionOps.get(0).kind());
|
assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.HOSTCALL, emissionOps.get(0).kind());
|
||||||
assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.INTRINSIC, emissionOps.get(1).kind());
|
assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.INTRINSIC, emissionOps.get(1).kind());
|
||||||
@ -84,6 +103,21 @@ class LowerToIRVMServiceTest {
|
|||||||
assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE, thrown.code());
|
assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE, thrown.code());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lowerMustRejectUnknownCanonicalIntrinsicIdentity() {
|
||||||
|
final var backend = IRBackend.builder()
|
||||||
|
.executableFunctions(ReadOnlyList.from(
|
||||||
|
fn("main", "app", 10, ReadOnlyList.from(
|
||||||
|
callIntrinsic("core.color.pack", 1, 0),
|
||||||
|
ret()))))
|
||||||
|
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
|
||||||
|
assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID, thrown.code());
|
||||||
|
assertTrue(thrown.getMessage().contains("unknown VM-owned intrinsic identity"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void lowerMustRejectCallArgSlotMismatch() {
|
void lowerMustRejectCallArgSlotMismatch() {
|
||||||
final var backend = IRBackend.builder()
|
final var backend = IRBackend.builder()
|
||||||
@ -141,6 +175,18 @@ class LowerToIRVMServiceTest {
|
|||||||
assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING, thrown.code());
|
assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING, thrown.code());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lowerMustRejectWhenEntrypointDeclarationIsMissing() {
|
||||||
|
final var backend = IRBackend.builder()
|
||||||
|
.entryPointCallableName(" ")
|
||||||
|
.executableFunctions(ReadOnlyList.from(
|
||||||
|
fn("main", "app", 10, ReadOnlyList.from(ret()))))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
|
||||||
|
assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_DECLARATION_MISSING, thrown.code());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void lowerMustRejectWhenEntrypointIsAmbiguous() {
|
void lowerMustRejectWhenEntrypointIsAmbiguous() {
|
||||||
final var backend = IRBackend.builder()
|
final var backend = IRBackend.builder()
|
||||||
|
|||||||
@ -31,9 +31,9 @@ class OptimizeIRVMEquivalenceHarnessTest {
|
|||||||
.executableFunctions(ReadOnlyList.from(
|
.executableFunctions(ReadOnlyList.from(
|
||||||
fn("main", 1, ReadOnlyList.from(
|
fn("main", 1, ReadOnlyList.from(
|
||||||
callHost("gfx", "draw_pixel", 1, 0, 0),
|
callHost("gfx", "draw_pixel", 1, 0, 0),
|
||||||
callIntrinsic("core.color.pack", 1, 0, 0, 0),
|
callIntrinsic("input.pad", 1, 0, 0, 0),
|
||||||
ret()))))
|
ret()))))
|
||||||
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))
|
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("input.pad", 1)))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final var lowered = new LowerToIRVMService().lower(backend);
|
final var lowered = new LowerToIRVMService().lower(backend);
|
||||||
|
|||||||
@ -145,9 +145,9 @@ class BackendGateIIntegrationTest {
|
|||||||
void gateI_validIntrinsicPath() {
|
void gateI_validIntrinsicPath() {
|
||||||
final var module = emitFromBackend(backendWithSingleFunction(
|
final var module = emitFromBackend(backendWithSingleFunction(
|
||||||
fn("main", ReadOnlyList.from(
|
fn("main", ReadOnlyList.from(
|
||||||
callIntrinsic("core.color.pack", 1, 0),
|
callIntrinsic("input.pad", 1, 0),
|
||||||
ret())),
|
ret())),
|
||||||
ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1))));
|
ReadOnlyList.from(new IntrinsicReference("input.pad", 1))));
|
||||||
|
|
||||||
final var check = compatibilityAdapter.check(module);
|
final var check = compatibilityAdapter.check(module);
|
||||||
assertEquals(CompatibilityError.NONE, check.error());
|
assertEquals(CompatibilityError.NONE, check.error());
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import p.studio.compiler.workspaces.stages.LowerToIRVMPipelineStage;
|
|||||||
import p.studio.compiler.workspaces.stages.OptimizeIRVMPipelineStage;
|
import p.studio.compiler.workspaces.stages.OptimizeIRVMPipelineStage;
|
||||||
import p.studio.compiler.workspaces.stages.ResolveDepsPipelineStage;
|
import p.studio.compiler.workspaces.stages.ResolveDepsPipelineStage;
|
||||||
import p.studio.compiler.workspaces.stages.VerifyBytecodePipelineStage;
|
import p.studio.compiler.workspaces.stages.VerifyBytecodePipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.WriteBytecodeArtifactPipelineStage;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -35,7 +36,8 @@ class BuilderPipelineServiceOrderTest {
|
|||||||
OptimizeIRVMPipelineStage.class,
|
OptimizeIRVMPipelineStage.class,
|
||||||
EmitBytecodePipelineStage.class,
|
EmitBytecodePipelineStage.class,
|
||||||
LinkBytecodePipelineStage.class,
|
LinkBytecodePipelineStage.class,
|
||||||
VerifyBytecodePipelineStage.class),
|
VerifyBytecodePipelineStage.class,
|
||||||
|
WriteBytecodeArtifactPipelineStage.class),
|
||||||
stageTypes);
|
stageTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,4 +141,67 @@ class LowerToIRVMPipelineStageTest {
|
|||||||
assertEquals(12, firstIssue.getStart());
|
assertEquals(12, firstIssue.getStart());
|
||||||
assertEquals(24, firstIssue.getEnd());
|
assertEquals(24, firstIssue.getEnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustRejectCallWhenStackDoesNotProvideDeclaredArgs() {
|
||||||
|
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.irBackend = IRBackend.builder()
|
||||||
|
.executableFunctions(ReadOnlyList.from(
|
||||||
|
new IRBackendExecutableFunction(
|
||||||
|
new FileId(0),
|
||||||
|
"app",
|
||||||
|
"callee",
|
||||||
|
new CallableId(2),
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Span.none())),
|
||||||
|
Span.none()),
|
||||||
|
new IRBackendExecutableFunction(
|
||||||
|
new FileId(0),
|
||||||
|
"app",
|
||||||
|
"main",
|
||||||
|
new CallableId(1),
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(
|
||||||
|
new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
||||||
|
"app",
|
||||||
|
"callee",
|
||||||
|
new CallableId(2),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
Span.none()),
|
||||||
|
new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Span.none())),
|
||||||
|
Span.none())))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final var issues = new LowerToIRVMPipelineStage().run(ctx, LogAggregator.empty());
|
||||||
|
final var firstIssue = issues.asCollection().iterator().next();
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertEquals("MARSHAL_VERIFY_PRECHECK_STACK_UNDERFLOW", firstIssue.getCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,86 @@
|
|||||||
|
package p.studio.compiler.workspaces.stages;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodeModule;
|
||||||
|
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||||
|
import p.studio.compiler.models.BuilderPipelineContext;
|
||||||
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class WriteBytecodeArtifactPipelineStageTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustFailWhenRootProjectPathIsMissing() {
|
||||||
|
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.bytecodeBytes = new byte[] { 0x01 };
|
||||||
|
|
||||||
|
final var issues = new WriteBytecodeArtifactPipelineStage().run(ctx, LogAggregator.empty());
|
||||||
|
final var firstIssue = issues.asCollection().iterator().next();
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertEquals("BACKEND_WRITE_ARTIFACT", firstIssue.getPhase());
|
||||||
|
assertEquals("MARSHAL_FORMAT_STAGE_INPUT_MISSING", firstIssue.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustFailWhenBytecodeInputIsMissing() {
|
||||||
|
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.rootProjectPathCanon = tempDir;
|
||||||
|
|
||||||
|
final var issues = new WriteBytecodeArtifactPipelineStage().run(ctx, LogAggregator.empty());
|
||||||
|
final var firstIssue = issues.asCollection().iterator().next();
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertEquals("BACKEND_WRITE_ARTIFACT", firstIssue.getPhase());
|
||||||
|
assertEquals("MARSHAL_FORMAT_STAGE_INPUT_MISSING", firstIssue.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustWriteBuildProgramPbxFromByteArray() throws Exception {
|
||||||
|
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.rootProjectPathCanon = tempDir;
|
||||||
|
ctx.bytecodeBytes = new byte[] { 0x50, 0x42, 0x58, 0x00 };
|
||||||
|
|
||||||
|
final var issues = new WriteBytecodeArtifactPipelineStage().run(ctx, LogAggregator.empty());
|
||||||
|
final var outputPath = tempDir.resolve("build").resolve("program.pbx");
|
||||||
|
|
||||||
|
assertFalse(issues.hasErrors());
|
||||||
|
assertTrue(Files.exists(outputPath));
|
||||||
|
assertArrayEquals(ctx.bytecodeBytes, Files.readAllBytes(outputPath));
|
||||||
|
assertEquals(outputPath, ctx.bytecodeArtifactPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustWriteBuildProgramPbxFromModuleWhenByteArrayIsMissing() throws Exception {
|
||||||
|
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.rootProjectPathCanon = tempDir;
|
||||||
|
ctx.bytecodeModule = new BytecodeModule(
|
||||||
|
0,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
new byte[] { (byte) 0x51, 0x00 },
|
||||||
|
null,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty());
|
||||||
|
|
||||||
|
final var issues = new WriteBytecodeArtifactPipelineStage().run(ctx, LogAggregator.empty());
|
||||||
|
final var outputPath = tempDir.resolve("build").resolve("program.pbx");
|
||||||
|
|
||||||
|
assertFalse(issues.hasErrors());
|
||||||
|
assertTrue(Files.exists(outputPath));
|
||||||
|
assertArrayEquals(ctx.bytecodeModule.serialize(), Files.readAllBytes(outputPath));
|
||||||
|
assertEquals(outputPath, ctx.bytecodeArtifactPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
504253000000000004000000000000000000000000000000000000000000000001000000500000001400000002000000640000000e00000003000000720000004400000005000000b60000001b00000001000000000000000e000000000000000000020071000000000072000000000051000300000000000000010000000000000000000000060000000100000000000000000000000c0000000100000000000000000000000100000000000000040000006d61696e0100000003006766780a00647261775f706978656c010000000000
|
504253000000000004000000000000000000000000000000000000000000000001000000500000001400000002000000640000000e00000003000000720000004400000005000000b60000001b00000001000000000000000e000000000000000000020071000000000072000020000051000300000000000000010000000000000000000000060000000100000000000000000000000c0000000100000000000000000000000100000000000000040000006d61696e0100000003006766780a00647261775f706978656c010000000000
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
profile=core-v1
|
profile=core-v1
|
||||||
fn#0:main param=0 local=0 ret=0 max=2
|
fn#0:main param=0 local=0 ret=0 max=2
|
||||||
HOSTCALL imm=0
|
HOSTCALL imm=0
|
||||||
INTRINSIC imm=0
|
INTRINSIC imm=8192
|
||||||
RET imm=-
|
RET imm=-
|
||||||
|
|||||||
@ -11,8 +11,10 @@ public class FrontendSpec {
|
|||||||
private final ReadOnlySet<String> allowedExtensions;
|
private final ReadOnlySet<String> allowedExtensions;
|
||||||
private final ReadOnlySet<String> sourceRoots;
|
private final ReadOnlySet<String> sourceRoots;
|
||||||
private final boolean caseSensitive;
|
private final boolean caseSensitive;
|
||||||
|
@Builder.Default
|
||||||
|
private final String entryPointCallableName = "main";
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("FrontendSpec(language=%s)", languageId);
|
return String.format("FrontendSpec(language=%s, entryPoint=%s)", languageId, entryPointCallableName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,8 @@ import java.util.LinkedHashSet;
|
|||||||
@Builder
|
@Builder
|
||||||
@Getter
|
@Getter
|
||||||
public class IRBackend {
|
public class IRBackend {
|
||||||
|
@Builder.Default
|
||||||
|
private final String entryPointCallableName = "main";
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private final ReadOnlyList<IRFunction> functions = ReadOnlyList.empty();
|
private final ReadOnlyList<IRFunction> functions = ReadOnlyList.empty();
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
@ -32,6 +34,7 @@ public class IRBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final class IRBackendAggregator {
|
public static final class IRBackendAggregator {
|
||||||
|
private String entryPointCallableName;
|
||||||
private final ArrayList<IRFunction> functions = new ArrayList<>();
|
private final ArrayList<IRFunction> functions = new ArrayList<>();
|
||||||
private final ArrayList<IRBackendExecutableFunction> executableFunctions = new ArrayList<>();
|
private final ArrayList<IRBackendExecutableFunction> executableFunctions = new ArrayList<>();
|
||||||
private final CallableTable callableTable = new CallableTable();
|
private final CallableTable callableTable = new CallableTable();
|
||||||
@ -58,6 +61,13 @@ public class IRBackend {
|
|||||||
requiredCapabilities.addAll(metadata.requiredCapabilities().asList());
|
requiredCapabilities.addAll(metadata.requiredCapabilities().asList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IRBackendAggregator entryPointCallableName(final String callableName) {
|
||||||
|
if (callableName != null && !callableName.isBlank()) {
|
||||||
|
entryPointCallableName = callableName;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private CallableId[] reindexCallables(final ReadOnlyList<CallableSignatureRef> localCallableSignatures) {
|
private CallableId[] reindexCallables(final ReadOnlyList<CallableSignatureRef> localCallableSignatures) {
|
||||||
if (localCallableSignatures == null || localCallableSignatures.isEmpty()) {
|
if (localCallableSignatures == null || localCallableSignatures.isEmpty()) {
|
||||||
return new CallableId[0];
|
return new CallableId[0];
|
||||||
@ -170,6 +180,7 @@ public class IRBackend {
|
|||||||
public IRBackend emit() {
|
public IRBackend emit() {
|
||||||
return IRBackend
|
return IRBackend
|
||||||
.builder()
|
.builder()
|
||||||
|
.entryPointCallableName(resolveEntryPointCallableName())
|
||||||
.functions(ReadOnlyList.wrap(functions))
|
.functions(ReadOnlyList.wrap(functions))
|
||||||
.executableFunctions(ReadOnlyList.wrap(executableFunctions))
|
.executableFunctions(ReadOnlyList.wrap(executableFunctions))
|
||||||
.callableSignatures(emitCallableSignatures())
|
.callableSignatures(emitCallableSignatures())
|
||||||
@ -181,12 +192,19 @@ public class IRBackend {
|
|||||||
ReadOnlyList.wrap(requiredCapabilities.stream().toList())))
|
ReadOnlyList.wrap(requiredCapabilities.stream().toList())))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveEntryPointCallableName() {
|
||||||
|
return entryPointCallableName == null || entryPointCallableName.isBlank()
|
||||||
|
? "main"
|
||||||
|
: entryPointCallableName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final var sb = new StringBuilder();
|
final var sb = new StringBuilder();
|
||||||
sb.append("IRBackend{functions=").append(functions.size())
|
sb.append("IRBackend{entrypoint=").append(entryPointCallableName)
|
||||||
|
.append(", functions=").append(functions.size())
|
||||||
.append(", executableFunctions=").append(executableFunctions.size())
|
.append(", executableFunctions=").append(executableFunctions.size())
|
||||||
.append(", callableSignatures=").append(callableSignatures.size())
|
.append(", callableSignatures=").append(callableSignatures.size())
|
||||||
.append(", intrinsicPool=").append(intrinsicPool.size())
|
.append(", intrinsicPool=").append(intrinsicPool.size())
|
||||||
|
|||||||
@ -177,11 +177,21 @@ class IRBackendExecutableContractTest {
|
|||||||
final var backend = aggregator.emit();
|
final var backend = aggregator.emit();
|
||||||
|
|
||||||
assertEquals(2, backend.getExecutableFunctions().size());
|
assertEquals(2, backend.getExecutableFunctions().size());
|
||||||
|
assertEquals("main", backend.getEntryPointCallableName());
|
||||||
assertEquals("entry", backend.getExecutableFunctions().get(0).callableName());
|
assertEquals("entry", backend.getExecutableFunctions().get(0).callableName());
|
||||||
assertEquals("aux", backend.getExecutableFunctions().get(1).callableName());
|
assertEquals("aux", backend.getExecutableFunctions().get(1).callableName());
|
||||||
assertEquals(2, backend.getCallableSignatures().size());
|
assertEquals(2, backend.getCallableSignatures().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void aggregatorMustEmitConfiguredEntrypointCallableName() {
|
||||||
|
final var aggregator = IRBackend.aggregator();
|
||||||
|
aggregator.entryPointCallableName("frame");
|
||||||
|
|
||||||
|
final var backend = aggregator.emit();
|
||||||
|
assertEquals("frame", backend.getEntryPointCallableName());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void aggregatorMustReindexIntrinsicsToBuildGlobalPool() {
|
void aggregatorMustReindexIntrinsicsToBuildGlobalPool() {
|
||||||
final var fileA = new IRBackendFile(
|
final var fileA = new IRBackendFile(
|
||||||
|
|||||||
10
test-projects/main/cartridge/manifest.json
Normal file
10
test-projects/main/cartridge/manifest.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"magic": "PMTU",
|
||||||
|
"cartridge_version": 1,
|
||||||
|
"app_id": 1,
|
||||||
|
"title": "Main Test",
|
||||||
|
"app_version": "0.1.0",
|
||||||
|
"app_mode": "Game",
|
||||||
|
"entrypoint": "0",
|
||||||
|
"capabilities": ["log"]
|
||||||
|
}
|
||||||
BIN
test-projects/main/cartridge/program.pbx
Normal file
BIN
test-projects/main/cartridge/program.pbx
Normal file
Binary file not shown.
@ -4,9 +4,5 @@
|
|||||||
"language": "pbs",
|
"language": "pbs",
|
||||||
"stdlib": "1",
|
"stdlib": "1",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
|
||||||
"name": "dep1",
|
|
||||||
"version": "1.0.0"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
6
test-projects/main/run.sh
Executable file
6
test-projects/main/run.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cp build/program.pbx cartridge
|
||||||
|
./runtime/prometeu run cartridge
|
||||||
1
test-projects/main/runtime
Symbolic link
1
test-projects/main/runtime
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../runtime/dist-staging/stable/prometeu-cli-aarch64-apple-darwin/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { Log } from @sdk:log;
|
||||||
|
import { Input } from @sdk:input;
|
||||||
|
|
||||||
|
fn frame() -> void
|
||||||
|
{
|
||||||
|
if (Input.pad().a().pressed())
|
||||||
|
{
|
||||||
|
Log.debug("Hello World!");
|
||||||
|
}
|
||||||
|
}
|
||||||
1
test-projects/main/src/mod.barrel
Normal file
1
test-projects/main/src/mod.barrel
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub fn frame() -> void;
|
||||||
@ -1 +0,0 @@
|
|||||||
module-a.barrel: content
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
fn a1()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
fn a2(v1: int, v2: bounded): int
|
|
||||||
{
|
|
||||||
return v1 + v2;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
fn a3(): int
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user