new test main with code directly (fixes)

This commit is contained in:
bQUARKz 2026-03-09 12:14:08 +00:00
parent 14f9b5dd26
commit 5630f8f119
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
63 changed files with 1677 additions and 198 deletions

View File

@ -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.

View File

@ -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`.

View File

@ -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

View File

@ -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`

View File

@ -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?

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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();
} }

View File

@ -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 {

View File

@ -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";

View File

@ -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);
} }

View File

@ -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.
*/ */

View File

@ -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;
} }

View File

@ -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) {

View File

@ -169,24 +169,85 @@ 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()) {
model.registerKnownTopDecl(topDecl);
}
for (final var topDecl : supplementalTopDecls) {
model.registerKnownTopDecl(topDecl);
}
for (final var topDecl : ast.topDecls()) {
model.ingestTopDecl(topDecl);
}
for (final var topDecl : supplementalTopDecls) {
model.ingestTopDecl(topDecl);
}
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) { if (topDecl instanceof PbsAst.StructDecl structDecl) {
final var fields = new HashMap<String, StructFieldInfo>(); final var fields = new HashMap<String, StructFieldInfo>();
for (final var field : structDecl.fields()) { for (final var field : structDecl.fields()) {
fields.put( fields.put(
field.name(), field.name(),
new StructFieldInfo( new StructFieldInfo(
model.typeFrom(field.typeRef()), typeFrom(field.typeRef()),
field.isPublic(), field.isPublic(),
field.isMutable())); field.isMutable()));
} }
final var methods = new HashMap<String, List<CallableSymbol>>(); final var methods = new HashMap<String, List<CallableSymbol>>();
for (final var method : structDecl.methods()) { for (final var method : structDecl.methods()) {
methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>()) methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>())
.add(model.callableFrom( .add(callableFrom(
method.name(), method.name(),
method.parameters(), method.parameters(),
method.returnKind(), method.returnKind(),
@ -194,30 +255,23 @@ final class PbsFlowSemanticSupport {
method.resultErrorType(), method.resultErrorType(),
method.span())); method.span()));
} }
model.structs.put(structDecl.name(), new StructInfo(fields, methods)); structs.put(structDecl.name(), new StructInfo(fields, methods));
continue; 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));
} }
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
final var methods = new HashMap<String, List<CallableSymbol>>(); final var methods = new HashMap<String, List<CallableSymbol>>();
for (final var method : serviceDecl.methods()) { for (final var signature : builtinTypeDecl.signatures()) {
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<>()) methods.computeIfAbsent(signature.name(), ignored -> new ArrayList<>())
.add(model.callableFrom( .add(callableFrom(
signature.name(), signature.name(),
signature.parameters(), signature.parameters(),
signature.returnKind(), signature.returnKind(),
@ -225,52 +279,98 @@ final class PbsFlowSemanticSupport {
signature.resultErrorType(), signature.resultErrorType(),
signature.span())); signature.span()));
} }
model.contracts.put(contractDecl.name(), new ContractInfo(methods)); structs.put(builtinTypeDecl.name(), new StructInfo(fields, methods));
continue; 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) { if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
model.topLevelCallables.computeIfAbsent(functionDecl.name(), ignored -> new ArrayList<>()) topLevelCallables.computeIfAbsent(functionDecl.name(), ignored -> new ArrayList<>())
.add(model.callableFrom( .add(callableFrom(
functionDecl.name(), functionDecl.name(),
functionDecl.parameters(), functionDecl.parameters(),
functionDecl.returnKind(), functionDecl.returnKind(),
functionDecl.returnType(), functionDecl.returnType(),
functionDecl.resultErrorType(), functionDecl.resultErrorType(),
functionDecl.span())); functionDecl.span()));
continue; return;
} }
if (topDecl instanceof PbsAst.CallbackDecl( if (topDecl instanceof PbsAst.CallbackDecl(
String name, ReadOnlyList<PbsAst.Parameter> parameters, PbsAst.ReturnKind returnKind, String name, ReadOnlyList<PbsAst.Parameter> parameters, PbsAst.ReturnKind returnKind,
PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, Span span PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, Span span
)) { )) {
final var symbol = model.callableFrom( final var symbol = callableFrom(
name, name,
parameters, parameters,
returnKind, returnKind,
returnType, returnType,
resultErrorType, resultErrorType,
span); span);
model.callbacks.put(name, new CallbackSignature(symbol.inputTypes(), symbol.outputType())); callbacks.put(name, new CallbackSignature(symbol.inputTypes(), symbol.outputType()));
continue; return;
} }
if (topDecl instanceof PbsAst.EnumDecl enumDecl) { if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
final var cases = new HashSet<String>(); final var cases = new HashSet<String>();
for (final var enumCase : enumDecl.cases()) { for (final var enumCase : enumDecl.cases()) {
cases.add(enumCase.name()); cases.add(enumCase.name());
} }
model.enums.put(enumDecl.name(), cases); enums.put(enumDecl.name(), cases);
continue; return;
} }
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) { if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
model.errors.put(errorDecl.name(), new HashSet<>(errorDecl.cases().asList())); errors.put(errorDecl.name(), new HashSet<>(errorDecl.cases().asList()));
continue; return;
} }
if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) { if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) {
model.constTypes.put(constDecl.name(), model.typeFrom(constDecl.explicitType())); constTypes.put(constDecl.name(), typeFrom(constDecl.explicitType()));
} }
} }
return model;
}
private CallableSymbol callableFrom( private CallableSymbol callableFrom(
final String name, final String name,
@ -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()));

View File

@ -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);
} }
} }

View File

@ -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());
}
}
} }

View File

@ -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);
}
}

View File

@ -0,0 +1,2 @@
mod host LowLog;
pub service Log;

View File

@ -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

View File

@ -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,);";

View File

@ -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

View File

@ -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,

View File

@ -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) {
}
}

View File

@ -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,

View File

@ -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);
} }

View File

@ -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());

View File

@ -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,

View File

@ -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);
} }

View File

@ -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;
}
}

View File

@ -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();
} }

View File

@ -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(

View File

@ -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()

View File

@ -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);

View File

@ -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());

View File

@ -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);
} }
} }

View File

@ -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());
}
} }

View File

@ -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);
}
}

View File

@ -1 +1 @@
504253000000000004000000000000000000000000000000000000000000000001000000500000001400000002000000640000000e00000003000000720000004400000005000000b60000001b00000001000000000000000e000000000000000000020071000000000072000000000051000300000000000000010000000000000000000000060000000100000000000000000000000c0000000100000000000000000000000100000000000000040000006d61696e0100000003006766780a00647261775f706978656c010000000000 504253000000000004000000000000000000000000000000000000000000000001000000500000001400000002000000640000000e00000003000000720000004400000005000000b60000001b00000001000000000000000e000000000000000000020071000000000072000020000051000300000000000000010000000000000000000000060000000100000000000000000000000c0000000100000000000000000000000100000000000000040000006d61696e0100000003006766780a00647261775f706978656c010000000000

View File

@ -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=-

View File

@ -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);
} }
} }

View File

@ -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())

View File

@ -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(

View 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"]
}

Binary file not shown.

View File

@ -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
View 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
View File

@ -0,0 +1 @@
../../../runtime/dist-staging/stable/prometeu-cli-aarch64-apple-darwin/

View File

@ -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!");
}
}

View File

@ -0,0 +1 @@
pub fn frame() -> void;

View File

@ -1 +0,0 @@
module-a.barrel: content

View File

@ -1,3 +0,0 @@
fn a1()
{
}

View File

@ -1,4 +0,0 @@
fn a2(v1: int, v2: bounded): int
{
return v1 + v2;
}

View File

@ -1,4 +0,0 @@
fn a3(): int
{
return 1;
}