implements PLN-0008

This commit is contained in:
bQUARKz 2026-03-27 18:23:20 +00:00
parent d28916578b
commit b75b2a5825
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
19 changed files with 444 additions and 18 deletions

View File

@ -1,9 +1,9 @@
{"type":"meta","next_id":{"DSC":9,"AGD":9,"DEC":7,"PLN":8,"LSN":24,"CLSN":1}}
{"type":"meta","next_id":{"DSC":9,"AGD":9,"DEC":7,"PLN":9,"LSN":24,"CLSN":1}}
{"type":"discussion","id":"DSC-0001","status":"done","ticket":"studio-docs-import","title":"Import docs/studio into discussion-framework artifacts","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["studio","migration","discussion-framework","docs-import"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0001-assets-workspace-execution-wave-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0002","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0002-bank-composition-editor-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0003","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0003-mental-model-asset-mutations-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0004","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0004-mental-model-assets-workspace-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0005","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0005-mental-model-studio-events-and-components-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0006","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0006-mental-model-studio-shell-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0007","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0007-pack-wizard-shell-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0008","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0008-project-scoped-state-and-activity-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0016","file":"discussion/lessons/DSC-0001-studio-docs-import/LSN-0016-studio-docs-import-pattern.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"}]}
{"type":"discussion","id":"DSC-0002","status":"open","ticket":"palette-management-in-studio","title":"Palette Management in Studio","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["studio","legacy-import","palette-management","tile-bank","packer-boundary"],"agendas":[{"id":"AGD-0002","file":"AGD-0002-palette-management-in-studio.md","status":"open","created_at":"2026-03-26","updated_at":"2026-03-26"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0003","status":"done","ticket":"packer-docs-import","title":"Import docs/packer into discussion-framework artifacts","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","migration","discussion-framework","docs-import"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0009","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0009-mental-model-packer-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0010","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0010-asset-identity-and-runtime-contract-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0011","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0011-foundations-workspace-runtime-and-build-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0012","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0012-runtime-ownership-and-studio-boundary-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0013","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0013-metadata-convergence-and-runtime-sink-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0014","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0014-pack-wizard-summary-validation-and-pack-execution-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0015","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0015-tile-bank-packing-contract-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0017","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0017-packer-docs-import-pattern.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"}]}
{"type":"discussion","id":"DSC-0004","status":"open","ticket":"tilemap-and-metatile-runtime-binary-layout","title":"Tilemap and Metatile Runtime Binary Layout","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","legacy-import","tilemap","metatile","runtime-layout"],"agendas":[{"id":"AGD-0004","file":"AGD-0004-tilemap-and-metatile-runtime-binary-layout.md","status":"open","created_at":"2026-03-26","updated_at":"2026-03-26"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0005","status":"open","ticket":"variable-tile-bank-palette-serialization","title":"Variable Tile Bank Palette Serialization","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","legacy-import","tile-bank","palette-serialization","versioning"],"agendas":[{"id":"AGD-0005","file":"AGD-0005-variable-tile-bank-palette-serialization.md","status":"open","created_at":"2026-03-26","updated_at":"2026-03-26"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"pbs-game-facing-asset-refs-and-call-result-discard","title":"PBS Game-Facing Asset References and Ignored Call Result Lowering","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","ergonomics","lowering","runtime","asset-identity","expression-statements"],"agendas":[{"id":"AGD-0006","file":"AGD-0006-pbs-game-facing-asset-refs-and-call-result-discard.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[{"id":"DEC-0005","file":"DEC-0005-pbs-asset-address-surface-and-be-lowering.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"},{"id":"DEC-0006","file":"DEC-0006-pbs-ignored-values-lowering-and-warning.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"}],"plans":[{"id":"PLN-0005","file":"PLN-0005-pbs-asset-address-surface-spec-propagation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0006","file":"PLN-0006-pbs-asset-address-surface-implementation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0007","file":"PLN-0007-pbs-ignored-values-warning-implementation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0006"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"pbs-game-facing-asset-refs-and-call-result-discard","title":"PBS Game-Facing Asset References and Ignored Call Result Lowering","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","ergonomics","lowering","runtime","asset-identity","expression-statements"],"agendas":[{"id":"AGD-0006","file":"AGD-0006-pbs-game-facing-asset-refs-and-call-result-discard.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[{"id":"DEC-0005","file":"DEC-0005-pbs-asset-address-surface-and-be-lowering.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"},{"id":"DEC-0006","file":"DEC-0006-pbs-ignored-values-lowering-and-warning.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"}],"plans":[{"id":"PLN-0005","file":"PLN-0005-pbs-asset-address-surface-spec-propagation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0006","file":"PLN-0006-pbs-asset-address-surface-implementation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0007","file":"PLN-0007-pbs-ignored-values-warning-implementation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0006"]},{"id":"PLN-0008","file":"PLN-0008-pbs-assetlowering-host-metadata-and-callsite-rewrite.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0007","status":"done","ticket":"pbs-learn-to-discussion-lessons-migration","title":"Migrate PBS Learn Documents into Discussion Lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","migration","discussion-framework","lessons","learn-prune"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0018","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0018-pbs-ast-and-parser-contract-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0019","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0019-pbs-name-resolution-and-linking-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0020","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0020-pbs-runtime-values-identity-memory-boundaries-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0021","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0021-pbs-diagnostics-and-conformance-governance-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0022","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0022-pbs-globals-lifecycle-and-published-entrypoint-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"pbs-low-level-asset-manager-surface","title":"PBS Low-Level Asset Manager Surface for Runtime AssetManager","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","runtime","asset-manager","host-abi","stdlib","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0023","file":"discussion/lessons/DSC-0008-pbs-low-level-asset-manager-surface/LSN-0023-lowassets-runtime-aligned-sdk-surface.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}

View File

@ -0,0 +1,196 @@
---
id: PLN-0008
ticket: pbs-game-facing-asset-refs-and-call-result-discard
title: Implement Mandatory AssetLowering Host Metadata and Backend Callsite Rewrite
status: done
created: 2026-03-27
completed: 2026-03-27
tags: [compiler, pbs, implementation, assetlowering, host-metadata, backend-lowering, correction]
---
## Objective
Complete the missing normative part of `DEC-0005` by implementing mandatory host-backed `AssetLowering` metadata and using it to rewrite `Addressable` arguments into runtime-facing `asset_id` at backend callsite lowering time.
## Background
`DEC-0005` did not only require:
- `FESurfaceContext = List<Addressable(address, asset_id)>`,
- symbolic `assets...` semantics,
- and backend ownership of final validation.
It also normatively locked a second requirement:
- asset-aware lowering metadata must live on the host-backed declaration surface;
- the backend must use that metadata to identify which host parameter is asset-facing;
- and host-backed lowering must rewrite the `Addressable` argument to operational `asset_id` before the final `LowAssets` path.
`PLN-0006` implemented the symbolic surface and direct symbolic-to-`asset_id` expression lowering, but it did not implement:
- reserved attribute `[AssetLowering(param = N)]`,
- reserved-metadata propagation for host bindings,
- host-signature validation for asset-lowered parameters,
- or host-callsite rewriting driven by parameter metadata.
This plan exists to correct that gap without reinterpreting `DEC-0005`.
## Scope
### Included
- Add `AssetLowering(param = N)` as a PBS reserved attribute on host-backed signatures.
- Validate the attribute shape and its admissible target surface.
- Extend backend host-binding metadata to carry asset-lowering parameter information.
- Rewrite host call lowering so asset-facing parameters consume symbolic `Addressable` values and lower them to `asset_id` according to host metadata.
- Align tests and specs so the normative path is explicit and covered.
- Correct any now-misleading tests or implementation shortcuts introduced by `PLN-0006`.
### Excluded
- Redesign of `DEC-0005`.
- Changes to the `LowAssets` ABI already fixed by `DEC-0004`.
- Generalization to non-asset metadata families beyond the exact `AssetLowering` rule.
- Runtime-dynamic asset lookup exceptions.
## Execution Steps
### Step 1 - Add Reserved Attribute Surface For AssetLowering
**What:**
Introduce `[AssetLowering(param = N)]` as a normative reserved attribute on host-backed callable surfaces.
**How:**
Extend the PBS reserved attribute extractor and declaration validation so:
- `AssetLowering` is recognized as reserved metadata;
- it is admitted only on supported host-backed callable declarations;
- `param` is mandatory and must be a valid zero-based parameter index;
- the targeted parameter must match the `Addressable` author-facing contract required by `DEC-0005`;
- duplicate or malformed declarations fail deterministically.
**File(s):**
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsReservedMetadataExtractor.java`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java`
- tests under `prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/`
### Step 2 - Propagate AssetLowering Through Reserved Metadata
**What:**
Make host binding metadata carry the asset-lowering contract instead of relying on implicit expression-level lowering.
**How:**
Extend `IRReservedMetadata.HostMethodBinding` or the equivalent host-binding metadata shape so each binding can expose:
- whether asset lowering is required;
- which parameter index is asset-facing.
Update metadata construction, merging, indexing, and any downstream readers so the information survives all backend-facing frontend phases.
**File(s):**
- `prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsReservedMetadataExtractor.java`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java`
- metadata conformance tests under `prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/`
### Step 3 - Rewrite Host Calls Using AssetLowering Metadata
**What:**
Move final `Addressable -> asset_id` host-argument rewriting into the backend-owned host call path mandated by `DEC-0005`.
**How:**
Update executable lowering so:
- host callsites inspect `AssetLowering(param = N)` metadata from the resolved host binding;
- the backend validates that the selected argument is a terminal backend-known `Addressable`;
- the selected argument is lowered to operational `asset_id`;
- non-asset-facing arguments are lowered normally;
- the final emitted host call matches the already accepted `LowAssets` runtime path.
This step must remove reliance on the current shortcut where symbolic asset references are lowered generically without host-parameter ownership.
**File(s):**
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallsiteEmitter.java`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java`
- related lowering metadata/index files
- targeted lowering tests under `prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/`
### Step 4 - Realign Symbolic Expression Lowering With The Host-Metadata Contract
**What:**
Correct the partial implementation from `PLN-0006` so symbolic asset expressions support the normative host-metadata path instead of bypassing it.
**How:**
Review the current direct lowering of `assets...` expressions and keep only the behavior that remains valid under `DEC-0005`.
At minimum:
- standalone symbolic `Addressable` semantics must remain correct;
- host-backed `AssetLowering` callsites must no longer depend on an implicit generic shortcut;
- tests that previously “passed” through direct expression lowering must be updated to assert the host-metadata path explicitly.
If any `PLN-0006` behavior contradicts `DEC-0005`, correct the code and the tests rather than preserving the shortcut.
**File(s):**
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java`
### Step 5 - Propagate And Pin The Contract In Specs And Tests
**What:**
Ensure the normative wording and regression coverage now match the corrected implementation.
**How:**
Update specs and tests so they explicitly cover:
- `AssetLowering(param = N)` as mandatory host-backed metadata for asset-aware lowering;
- the backend-owned rewrite from symbolic `Addressable` to operational `asset_id`;
- rejection of malformed attribute usage and invalid parameter targeting;
- a representative `LowAssets`-aligned host call path using the metadata-bearing surface.
**File(s):**
- `docs/specs/compiler-languages/pbs/4. Static Semantics Specification.md`
- `docs/specs/compiler-languages/pbs/12. Diagnostics Specification.md`
- `docs/specs/compiler-languages/pbs/13. Lowering IRBackend Specification.md`
- `docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md`
- targeted PBS frontend/compiler tests
## Test Requirements
### Unit Tests
- Reserved-attribute tests for valid and invalid `AssetLowering(param = N)` usage.
- Metadata tests proving host bindings preserve the asset-lowering parameter index.
- Negative tests for duplicate, malformed, or out-of-range `AssetLowering` declarations.
### Integration Tests
- End-to-end PBS frontend compilation tests where a host-backed asset API uses `Addressable` plus `[AssetLowering(param = N)]`.
- Lowering tests proving the backend rewrites the marked argument to `asset_id` before the final host call.
- Regression tests proving `LowAssets`-aligned callsites still lower correctly after removing the shortcut path.
### Manual Verification
- Inspect extracted reserved metadata and verify `AssetLowering` is carried on the host binding.
- Inspect lowered instruction streams for an asset-aware host call and verify the asset-facing argument becomes `asset_id`.
- Confirm malformed host metadata fails with deterministic source-facing diagnostics.
## Acceptance Criteria
- [ ] PBS supports `[AssetLowering(param = N)]` as a validated reserved attribute on the correct host-backed surface.
- [ ] Host binding metadata preserves the asset-lowering parameter contract through backend lowering preparation.
- [ ] Backend host-call lowering uses the metadata to rewrite the selected `Addressable` argument into runtime-facing `asset_id`.
- [ ] The final asset-aware host path remains aligned with `LowAssets` and no longer depends on a generic shortcut that bypasses host metadata.
- [ ] Tests and specs explicitly cover the normative `AssetLowering` path from `DEC-0005`.
## Dependencies
- `DEC-0005-pbs-asset-address-surface-and-be-lowering`
- `DEC-0004` low-level `LowAssets` contract
- existing `PLN-0006` implementation as the correction baseline
## Risks
- The current direct symbolic lowering path may overlap with the normative host-metadata path and require careful removal or restriction.
- Extending host metadata may require synchronized updates across multiple compiler layers, increasing the chance of partial propagation if done carelessly.
- If tests keep asserting the shortcut path instead of the normative path, the repository may regress into the same discrepancy again.

View File

@ -207,6 +207,13 @@ At minimum, the PBS diagnostics baseline must cover:
- a stable warning when a materialized value is produced and then ignored,
- and no warning for unit-like expression statements.
Reserved-attribute diagnostics for host-backed asset lowering must also cover:
- duplicate `AssetLowering` declarations on the same host signature,
- missing or malformed `AssetLowering(param = N)` arguments,
- out-of-range `AssetLowering.param` indexes,
- and `AssetLowering` targets whose selected parameter is not statically typed as `Addressable`.
At minimum, host-admission diagnostics must cover missing or malformed host capability metadata and unknown or undeclared capability names.
Only backend-originated failures that remain source-attributable and user-actionable belong to the PBS-facing diagnostics contract.

View File

@ -185,6 +185,7 @@ When available at this boundary, declared host ABI shape (`arg_slots`, `ret_slot
When a host-backed callsite also depends on backend-owned symbolic asset lowering:
- the frontend boundary MUST preserve which callsite argument is asset-facing;
- for PBS v1 this asset-facing designation is carried by reserved host metadata `[AssetLowering(param = N)]`;
- the preserved symbolic operand MUST remain attributable to a backend-provided `Addressable` identity;
- backend stages MUST be able to rewrite that symbolic operand into runtime-facing `asset_id` before the final low-level host path is emitted.

View File

@ -80,14 +80,19 @@ Rules:
- Attributes are not first-class values and are not reflectable in v1 core.
- Attributes do not automatically survive into runtime or bytecode artifacts.
- An attribute affects runtime artifacts only when another specification defines an explicit lowering for its semantic effect.
- In v1 core, the normative reserved attributes are `Host`, `Capability`, `BuiltinType`, `BuiltinConst`, `IntrinsicCall`, `Init`, `Frame`, and `InitAllowed`.
- In v1 core, the normative reserved attributes are `Host`, `Capability`, `AssetLowering`, `BuiltinType`, `BuiltinConst`, `IntrinsicCall`, `Init`, `Frame`, and `InitAllowed`.
- `Host` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body.
- `Capability` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body.
- `AssetLowering` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body.
- `Init` is valid only on a top-level userland `fn` declaration with lifecycle-admissible signature.
- `Frame` is valid only on a top-level userland `fn` declaration with lifecycle-admissible signature.
- `InitAllowed` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body.
- `Host` is invalid on ordinary user-authored modules, top-level `fn`, struct methods, service methods, callbacks, contracts, and constants.
- `Host` metadata is consumed by the compiler during host-binding lowering.
- `AssetLowering` MUST declare exactly the named argument `param`.
- `AssetLowering.param` MUST be a zero-based integer literal that names a valid host parameter position.
- The parameter targeted by `AssetLowering.param` MUST be statically typed as `Addressable`.
- `AssetLowering` metadata is consumed by backend-owned host-call lowering to rewrite the targeted symbolic `Addressable` operand into runtime-facing `asset_id`.
- The `Host` attribute syntax itself is not exported as runtime metadata; instead, its canonical identity participates in PBX host-binding emission as defined by the Host ABI Binding specification.
- `BuiltinType` is valid only on a reserved top-level `declare builtin type` declaration.
- `BuiltinConst` is valid only on a reserved top-level `declare const` declaration that omits an initializer.

View File

@ -143,7 +143,8 @@ For example, the PBS-facing declaration:
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
fn load(asset_id: int, slot: int) -> (status: int, loading_handle: int);
[AssetLowering(param = 0)]
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
}
```
@ -155,6 +156,8 @@ maps to the canonical runtime identity:
The loader MUST NOT derive runtime identity from the source owner spelling `LowAssets`.
At the PBS-facing declaration layer, `AssetLowering(param = 0)` marks the author-facing `Addressable` parameter as the backend-owned symbolic operand that must be rewritten to operational `asset_id` before the final low-level host path is emitted.
## 7. PBX Host Binding Section
### 7.1 Temporary section contract

View File

@ -214,7 +214,8 @@ or:
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
fn load(asset_id: int, slot: int) -> (status: int, loading_handle: int);
[AssetLowering(param = 0)]
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
}
```
@ -228,8 +229,8 @@ Rules:
- stdlib line `1` MUST expose the low-level asset interface module at `@sdk:asset`,
- that module MUST declare `LowAssets`,
- that module MUST use runtime module `asset` and capability `asset`,
- that module MUST use the runtime-aligned signatures:
- `load(asset_id: int, slot: int) -> (status: int, loading_handle: int)`
- that module MUST use the runtime-aligned signatures and reserved lowering metadata:
- `load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int)` together with `[AssetLowering(param = 0)]`
- `status(loading_handle: int) -> int`
- `commit(loading_handle: int) -> int`
- `cancel(loading_handle: int) -> int`

View File

@ -150,6 +150,8 @@ Before bytecode emission, backend must run structural pre-verification on lowere
7. and structural validity of host/intrinsic call forms.
8. and structural validity of backend-owned symbolic-to-operational rewrites such as `Addressable -> asset_id`.
For PBS host-backed asset calls in v1, this validation includes the rewrite selected by preserved host metadata such as `[AssetLowering(param = N)]`.
This pre-verification does not replace runtime verifier authority.
## 11. Deterministic Rejection Policy

View File

@ -15,6 +15,7 @@ public final class PbsReservedMetadataExtractor {
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
private static final String ATTR_ASSET_LOWERING = "AssetLowering";
public IRReservedMetadata extract(
final PbsAst.File ast,
@ -68,6 +69,7 @@ public final class PbsReservedMetadataExtractor {
final var capability = capabilityAttribute
.flatMap(attr -> stringArgument(attr, "name"))
.orElse("");
final var assetLoweringAttribute = firstAttributeNamed(signature.attributes(), ATTR_ASSET_LOWERING);
hostMethodBindings.add(new IRReservedMetadata.HostMethodBinding(
hostDecl.name(),
signature.name(),
@ -78,6 +80,9 @@ public final class PbsReservedMetadataExtractor {
case INFERRED_UNIT, EXPLICIT_UNIT -> 0;
case PLAIN, RESULT -> 1;
},
assetLoweringAttribute.isPresent()
? safeToInteger(longArgument(assetLoweringAttribute.get(), "param").orElse(-1L))
: null,
capabilityAttribute.isPresent(),
capability,
signature.span()));
@ -261,4 +266,11 @@ public final class PbsReservedMetadataExtractor {
}
return ReadOnlyList.wrap(required.stream().toList());
}
private Integer safeToInteger(final long value) {
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
return null;
}
return (int) value;
}
}

View File

@ -415,10 +415,28 @@ final class PbsExecutableBodyLowerer {
final PbsAst.CallExpr callExpr,
final PbsExecutableLoweringContext context) {
lowerCallsiteReceiver(callExpr.callee(), context);
lowerExpressionList(callExpr.arguments(), context);
lowerCallArguments(callExpr, context);
callsiteEmitter.emitCallsite(callExpr, context);
}
private void lowerCallArguments(
final PbsAst.CallExpr callExpr,
final PbsExecutableLoweringContext context) {
final var hostBinding = callsiteEmitter.resolveUniqueHostBinding(callExpr, context);
if (hostBinding == null || hostBinding.assetLoweringParam() == null) {
lowerExpressionList(callExpr.arguments(), context);
return;
}
for (int i = 0; i < callExpr.arguments().size(); i++) {
final var argument = callExpr.arguments().get(i);
if (i == hostBinding.assetLoweringParam()) {
lowerAssetFacingArgument(argument, hostBinding, context);
continue;
}
lowerExpression(argument, context);
}
}
private void lowerExpressionList(
final ReadOnlyList<PbsAst.Expression> expressions,
final PbsExecutableLoweringContext context) {
@ -461,6 +479,26 @@ final class PbsExecutableBodyLowerer {
lowerExpression(memberExpr.receiver(), context);
}
private void lowerAssetFacingArgument(
final PbsAst.Expression expression,
final p.studio.compiler.models.IRReservedMetadata.HostMethodBinding hostBinding,
final PbsExecutableLoweringContext context) {
final var assetReference = flattenAssetReference(expression);
if (assetReference == null) {
lowerExpression(expression, context);
return;
}
final var assetId = context.resolveAssetId(assetReference);
if (assetId == null) {
reportUnsupportedLowering(
"asset-facing host parameter does not resolve in backend-owned surface: " + assetReference,
expression.span(),
context);
return;
}
emitPushI32(assetId, expression.span(), context);
}
private void lowerIfExpression(
final PbsAst.IfExpr ifExpr,
final PbsExecutableLoweringContext context) {

View File

@ -16,6 +16,23 @@ import java.util.Map;
import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;
final class PbsExecutableCallsiteEmitter {
IRReservedMetadata.HostMethodBinding resolveUniqueHostBinding(
final PbsAst.CallExpr callExpr,
final PbsExecutableLoweringContext context) {
final var calleeIdentity = resolveCalleeIdentity(callExpr.callee(), context.nameTable());
if (calleeIdentity == null) {
return null;
}
final var candidates = resolveCallsiteCandidates(callExpr, calleeIdentity, context);
if (!candidates.callableCandidates().isEmpty() || !candidates.intrinsicCandidates().isEmpty()) {
return null;
}
if (candidates.hostCandidates().size() != 1) {
return null;
}
return candidates.hostCandidates().getFirst();
}
int returnSlotsOf(
final PbsAst.CallExpr callExpr,
final PbsExecutableLoweringContext context) {

View File

@ -18,6 +18,7 @@ public final class PbsDeclarationSemanticsValidator {
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
private static final String ATTR_INIT_ALLOWED = "InitAllowed";
private static final String ATTR_ASSET_LOWERING = "AssetLowering";
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
ATTR_HOST,
@ -25,7 +26,8 @@ public final class PbsDeclarationSemanticsValidator {
ATTR_BUILTIN_TYPE,
ATTR_BUILTIN_CONST,
ATTR_INTRINSIC_CALL,
ATTR_INIT_ALLOWED);
ATTR_INIT_ALLOWED,
ATTR_ASSET_LOWERING);
private final NameTable nameTable;
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
@ -403,13 +405,15 @@ public final class PbsDeclarationSemanticsValidator {
final DiagnosticSink diagnostics) {
final var hostAttributes = attributesNamed(signature.attributes(), ATTR_HOST);
final var initAllowedAttributes = attributesNamed(signature.attributes(), ATTR_INIT_ALLOWED);
final var assetLoweringAttributes = attributesNamed(signature.attributes(), ATTR_ASSET_LOWERING);
for (final var attribute : signature.attributes()) {
if (!isReservedAttribute(attribute.name())) {
continue;
}
if (ATTR_HOST.equals(attribute.name())
|| ATTR_CAPABILITY.equals(attribute.name())
|| ATTR_INIT_ALLOWED.equals(attribute.name())) {
|| ATTR_INIT_ALLOWED.equals(attribute.name())
|| ATTR_ASSET_LOWERING.equals(attribute.name())) {
continue;
}
reportInvalidReservedAttributeTarget(
@ -438,10 +442,19 @@ public final class PbsDeclarationSemanticsValidator {
}
}
if (assetLoweringAttributes.size() > 1) {
for (int i = 1; i < assetLoweringAttributes.size(); i++) {
reportDuplicateReservedAttribute(assetLoweringAttributes.get(i), ATTR_ASSET_LOWERING, diagnostics);
}
}
validateHostAttributeShape(hostAttributes.getFirst(), diagnostics);
if (!initAllowedAttributes.isEmpty()) {
validateInitAllowedAttributeShape(initAllowedAttributes.getFirst(), diagnostics);
}
if (!assetLoweringAttributes.isEmpty()) {
validateAssetLoweringAttributeShape(assetLoweringAttributes.getFirst(), signature, diagnostics);
}
}
private void validateBuiltinTypeAttribute(
@ -587,6 +600,39 @@ public final class PbsDeclarationSemanticsValidator {
}
}
private void validateAssetLoweringAttributeShape(
final PbsAst.Attribute attribute,
final PbsAst.FunctionSignature signature,
final DiagnosticSink diagnostics) {
final var args = validateNamedArguments(attribute, Set.of("param"), Set.of(), diagnostics);
if (args == null) {
return;
}
validateRequiredIntArgument(attribute, args, "param", false, diagnostics);
final var value = args.get("param");
if (!(value instanceof PbsAst.AttributeIntValue intValue)) {
return;
}
final var paramIndex = intValue.value();
if (paramIndex < 0 || paramIndex >= signature.parameters().size()) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
"AssetLowering param index %d is out of range for host signature '%s'".formatted(paramIndex, signature.name()),
attribute.span());
return;
}
final var parameter = signature.parameters().get((int) paramIndex);
final var parameterType = unwrapGroup(parameter.typeRef());
if (parameterType == null
|| parameterType.kind() != PbsAst.TypeRefKind.SIMPLE
|| !"Addressable".equals(parameterType.name())) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
"AssetLowering target parameter '%s' must have Addressable type".formatted(parameter.name()),
attribute.span());
}
}
private Map<String, PbsAst.AttributeValue> validateNamedArguments(
final PbsAst.Attribute attribute,
final Set<String> requiredNames,
@ -690,6 +736,13 @@ public final class PbsDeclarationSemanticsValidator {
return trimmed.isEmpty();
}
private PbsAst.TypeRef unwrapGroup(final PbsAst.TypeRef typeRef) {
if (typeRef == null || typeRef.kind() != PbsAst.TypeRefKind.GROUP) {
return typeRef;
}
return unwrapGroup(typeRef.inner());
}
private List<PbsAst.Attribute> attributesNamed(
final ReadOnlyList<PbsAst.Attribute> attributes,
final String name) {

View File

@ -1,7 +1,8 @@
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
fn load(asset_id: int, slot: int) -> (status: int, loading_handle: int);
[AssetLowering(param = 0)]
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
[Host(module = "asset", name = "status", version = 1)]
[Capability(name = "asset")]

View File

@ -9,12 +9,18 @@ import p.studio.compiler.models.IRGlobalVisibility;
import p.studio.compiler.models.IRSyntheticCallableKind;
import p.studio.compiler.models.SourceKind;
import p.studio.compiler.pbs.lexer.LexErrors;
import p.studio.compiler.pbs.lexer.PbsLexer;
import p.studio.compiler.pbs.parser.PbsParser;
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ModuleId;
import p.studio.compiler.source.tables.NameTable;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -433,7 +439,8 @@ class PbsFrontendCompilerTest {
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
fn load(asset_id: int, slot: int) -> (status: int, loading_handle: int);
[AssetLowering(param = 0)]
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
[Host(module = "asset", name = "status", version = 1)]
[Capability(name = "asset")]
@ -462,7 +469,8 @@ class PbsFrontendCompilerTest {
.anyMatch(h -> h.ownerName().equals("LowAssets")
&& h.abiModule().equals("asset")
&& h.abiMethod().equals("load")
&& h.abiVersion() == 1));
&& h.abiVersion() == 1
&& java.util.Objects.equals(h.assetLoweringParam(), 0)));
}
@Test
@ -493,6 +501,61 @@ class PbsFrontendCompilerTest {
&& "37".equals(i.label())));
}
@Test
void shouldRewriteAssetLoweringHostParameterToAssetId() {
final var interfaceSource = """
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
[AssetLowering(param = 0)]
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
}
""";
final var source = """
fn boot() -> (status: int, loading_handle: int) {
return LowAssets.load(assets.ui.panel, 2);
}
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
final var interfaceBackend = compiler.compileFile(new FileId(16), interfaceSource, diagnostics, SourceKind.SDK_INTERFACE);
final var sourceFileId = new FileId(17);
final var sourceTokens = PbsLexer.lex(source, sourceFileId, diagnostics);
final var sourceAst = PbsParser.parse(sourceTokens, sourceFileId, diagnostics);
final var interfaceTokens = PbsLexer.lex(interfaceSource, new FileId(18), diagnostics);
final var interfaceAst = PbsParser.parse(interfaceTokens, new FileId(18), diagnostics, PbsParser.ParseMode.INTERFACE_MODULE);
final var fileBackend = compiler.compileParsedFile(
sourceFileId,
sourceAst,
diagnostics,
SourceKind.PROJECT,
ModuleId.none(),
ReadOnlyList.empty(),
HostAdmissionContext.permissiveDefault(),
new FESurfaceContext(ReadOnlyList.from(new Addressable("assets.ui.panel", 55))),
new NameTable(),
interfaceAst.topDecls(),
ReadOnlyList.empty(),
ReadOnlyList.empty(),
interfaceBackend.reservedMetadata(),
Map.of());
assertEquals(0, diagnostics.errorCount(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
final var executableBoot = fileBackend.executableFunctions().stream()
.filter(fn -> fn.callableName().equals("boot"))
.findFirst()
.orElseThrow();
assertTrue(executableBoot.instructions().stream().anyMatch(i ->
i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_I32
&& "55".equals(i.label())));
assertTrue(executableBoot.instructions().stream().anyMatch(i ->
i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_HOST
&& i.hostCall() != null
&& "asset".equals(i.hostCall().module())
&& "load".equals(i.hostCall().name())));
}
@Test
void shouldRejectUnresolvedBackendOwnedAssetReference() {
final var source = """

View File

@ -332,7 +332,8 @@ class PbsGateUSdkInterfaceConformanceTest {
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
fn load(asset_id: int, slot: int) -> (status: int, loading_handle: int);
[AssetLowering(param = 0)]
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
[Host(module = "asset", name = "status", version = 1)]
[Capability(name = "asset")]
@ -361,8 +362,9 @@ class PbsGateUSdkInterfaceConformanceTest {
assertEquals("asset", backend.reservedMetadata().requiredCapabilities().getFirst());
assertTrue(backend.reservedMetadata().hostMethodBindings().stream()
.anyMatch(h -> h.ownerName().equals("LowAssets")
&& java.util.Objects.equals(h.assetLoweringParam(), 0)
&& h.abiModule().equals("asset")
&& h.abiMethod().equals("cancel")
&& h.abiMethod().equals("load")
&& h.abiVersion() == 1));
}

View File

@ -126,7 +126,8 @@ class PbsInterfaceModuleSemanticsTest {
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
fn load(asset_id: int, slot: int) -> (status: int, loading_handle: int);
[AssetLowering(param = 0)]
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
[Host(module = "asset", name = "status", version = 1)]
[Capability(name = "asset")]
@ -140,6 +141,24 @@ class PbsInterfaceModuleSemanticsTest {
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
}
@Test
void shouldRejectMalformedAssetLoweringOnHostSurface() {
final var source = """
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
[AssetLowering(param = 1)]
fn load(addressable: Addressable) -> int;
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(5), source, diagnostics, SourceKind.SDK_INTERFACE);
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
}
@Test
void shouldRejectBuiltinFieldWithNonAdmissibleLayoutType() {
final var source = """

View File

@ -69,7 +69,8 @@ class InterfaceModuleLoaderTest {
declare host LowAssets {
[Host(module = "asset", name = "load", version = 1)]
[Capability(name = "asset")]
fn load(asset_id: int, slot: int) -> (status: int, loading_handle: int);
[AssetLowering(param = 0)]
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
}
"""))),
"pub host LowAssets;");

View File

@ -950,7 +950,8 @@ class PBSFrontendPhaseServiceTest {
assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream()
.anyMatch(h -> h.ownerName().equals("LowAssets")
&& h.sourceMethodName().equals("load")
&& h.abiModule().equals("asset")));
&& h.abiModule().equals("asset")
&& java.util.Objects.equals(h.assetLoweringParam(), 0)));
}
@Test

View File

@ -33,6 +33,7 @@ public record IRReservedMetadata(
String abiMethod,
long abiVersion,
int retSlots,
Integer assetLoweringParam,
boolean capabilityDeclared,
String requiredCapability,
Span span) {
@ -44,6 +45,9 @@ public record IRReservedMetadata(
if (retSlots < 0) {
throw new IllegalArgumentException("host retSlots must be non-negative");
}
if (assetLoweringParam != null && assetLoweringParam < 0) {
throw new IllegalArgumentException("assetLoweringParam must be non-negative");
}
requiredCapability = requiredCapability == null ? "" : requiredCapability;
span = Objects.requireNonNull(span, "span");
}