diff --git a/.gitignore b/.gitignore index 7e470f1e6..e95084b61 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,6 @@ __pycache__/ /docs/site/node_modules/ /docs/site/.docusaurus/ /docs/site/build/ +/docs/site/evidence/ /docs/site/test-results/ /docs/site/playwright-report/ diff --git a/DESIGN.md b/DESIGN.md index 4feffb35c..ffaeb111f 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -3,7 +3,7 @@ ## Source of truth - Status: Active -- Last refreshed: 2026-08-25 +- Last refreshed: 2026-08-27 - Primary product surface: PTO Formal Specification Portal at `https://pto-isa.github.io/` - Product owner: `PTO-ISA/pto-spec` @@ -26,6 +26,11 @@ assets, SSG, versioning, internationalization, and GitHub Pages deployment - Plotly.js documentation for WebGL traces, animations, React integration, partial bundles, updates, and WebGL context limits + - Arm A64 instruction-reference composition, using `ADD (immediate)` as the + concrete page study: short purpose, encoding diagram, per-variant assembly, + shared decode pseudocode, assembler symbols, operation pseudocode, then + operational information + - `.codex/skills/pto-asl/references/arm-style.md` ## Normative ownership @@ -48,8 +53,11 @@ asl/**/*.asl owners. They must not be rewritten by hand in Markdown, React components, or visualization data. - Hand-written documentation may demonstrate behavior through examples, - animation, diagrams, implementation guidance, and common pitfalls. It must be - visibly labeled non-normative and must not redefine instruction semantics. + animation, diagrams, implementation guidance, and common pitfalls. The UI + presents these as reader aids without repeated legalistic status banners; + semantic authority is established once, through the directly embedded ASL + and the source ledger at the end of the page. Reader aids must not redefine + instruction semantics. - If an ASL/NDF explanation is insufficient, improve the owning ASL/NDF source, regenerate every projection, add or update evidence, and run the required validation workflow. Do not repair the gap only in the website. @@ -64,7 +72,10 @@ asl/**/*.asl - Trust signals: exact release identity, commit SHA, source path, line range, content hash, validation state, and direct links to evidence. - Visual posture: a restrained specification manual combined with a professional - engineering workbench. + engineering workbench. The dark-first palette uses obsidian neutrals, + oxidized red for primary/current-route emphasis, and brushed gold for focus, + source identity, and selected-data highlights. It is Huawei/Ascend-inspired + in tone without claiming or reproducing an official brand system. - Avoid: - cyberpunk neon and decorative particle effects; - excessive glass effects, gradients, shadows, or marketing cards; @@ -155,21 +166,53 @@ asl/**/*.asl ### Primary navigation -- PTO Architecture +- Architecture +- All instructions - Scalar - Block - Tile -- ADR / NDF +- NDF +- NDF graph +- Decisions / ADR - Search Scalar, Block, and Tile navigation uses an exact released-surface facet. It must not be implemented as a free-text search whose substring matches can cross surface boundaries. +Every primary reading route uses a stable left navigation rail on desktop, +including the homepage, Architecture landing page, instruction workbenches, +NDF indexes and exploration, and ADR/Decision entry points. The rail is ordered +as Home, Architecture, All instructions, Scalar, Block, Tile, NDF, NDF graph, +Decisions, and Search. On the instruction index, the `surface` URL query +parameter is the canonical selected facet; rail links, filter buttons, browser +history, and current-route state must remain synchronized with it. The rail +shows hierarchy and the current route, may collapse individual groups, and is +never fully hidden by default on desktop. The top navbar remains a compact set +of direct entries rather than the only navigation surface. + +The native Docusaurus Reference sidebar remains the stable left navigation for +governance, status, development, and release documents that are not canonical +unit workbenches. Architecture, Scalar, Block, and Tile unit documents do not +remain in that sidebar: their legacy `/reference/...` URLs redirect to the one +canonical unit page. The Reference sidebar marks the exact current project +record, while the global top navbar retains Home, Architecture, instructions, +NDF, and Search access without duplicating two rails. Custom routes use the +shared `PortalShell` and `SpecNavigation` components instead of duplicating +route-specific navigation. At tablet and phone widths the persistent rail +becomes a clearly labelled navigation button and in-flow drawer; it does not +overlay or obscure the document. + +English uses `Architecture`. Simplified Chinese uses the concise label `架构`. +Navigation must not use `PTO Architecture`, `PTO架构`, `PTO ISA`, or `PTO 手册` +as sibling labels for the same destination. Formal release identity and the +normative product name remain unchanged where those names are actual source or +version identities. + ### Default flow ```text -PTO Architecture +Architecture -> Scalar -> Block -> Tile @@ -191,14 +234,42 @@ Homepage prose is an orientation layer, not a second semantic manual. It may summarize released inventory and repository structure, but every semantic claim must resolve to the embedded ASL/NDF owner. +### Architecture landing-page reading order + +The `/architecture/` route is a reader-first map over current architecture +owners, not a replacement architecture manual. It presents: + +1. a compact mental model connecting program, Core/PE execution, Scalar and + Block/bundle control, Tile operations, state, and memory; +2. a relationship table and a typical implementation reading path before any + source inventory; +3. source-backed topics for execution/programming model, architecture state, + register classes, memory model, type/shape model, fault/diagnostic model, + and version/compatibility; +4. short reader-guide excerpts projected from the exact owning units, with + direct links to their workbenches and original ASL; +5. explicit source gaps when the current owners distribute or omit a requested + contract, rather than a generic explanation that invents one; and +6. a collapsed provenance ledger after the human reading path. + +The site build owns the topic-to-owner map and validates every referenced unit, +source path, source hash, reviewed reader-guide hash, and exact block identity. +Typical scenarios and source boundaries are exact bound reader-guide blocks, +not hand-written architectural claims. React renders the projection and may +supply localized navigation labels, but it must not carry a second copy of +architectural semantics. + ### Core routes - `/` — latest release landing page and global search. - `/search` — implementation-oriented search results and filters. +- `/instructions/` — complete released instruction index with Scalar, Block, + and Tile facets; surface subroutes retain the active facet. - `/architecture/` — architecture, state, memory, profile, and instruction classification entry points. - `/instructions////` — instruction workbench. +- `/ndf/` — complete source-backed NDF index. - `/ndf//` — readable clause, owner, references, evidence index, and local relationship graph. - `/explore/ndf/` — repository-wide Plotly/WebGL relationship explorer. @@ -208,22 +279,80 @@ must resolve to the embedded ASL/NDF owner. - `/project/` — governance, contribution, repository layout, ADRs, and open questions. +### One canonical unit page + +Generated Markdown under `docs/{arch,scalar,block,tile}/` remains the checked-in +ASL projection and reader-guide source, but it is not published as a second +instruction/reference page. Every released unit has one canonical workbench +route that renders the Markdown reader blocks, exact embedded ASL, NDF, +Decision/AVS evidence, and provenance together. Legacy `/reference/...` unit +URLs redirect to that canonical route. The Docusaurus Reference plugin retains +only governance, status, development, and release documents that are not unit +workbenches. + +The instruction and NDF indexes are generated from the same release +traceability graph and route manifest. They never maintain a second handwritten +catalog. Every NDF detail route resolves the exact canonical clause body and +links every owning/affected unit back to its canonical workbench. + ### Content hierarchy on an instruction route -- Identity, release, commit, source path, and hash. -- Canonical assembly and generated encoding. -- Embedded ASL decode and operation regions. -- Generated legality, defaults, operands, state, memory, ordering, and fault - contracts. -- Interactive demonstration labeled non-normative. -- NDF clauses and dependency neighborhood. -- Expandable, searchable evidence index. -- Decision history and exact source links. +1. Mnemonic, one-sentence purpose, and stable instruction identity. +2. For bundle-defined Tile operations, the complete bundle/block syntax comes + first: ordered BSTART/B.xxx/BSTOP rail, legal variants, minimum and complete + examples, occurrence constraints, parameter sources, defaults, mutual + exclusions, and links to every command owner. +3. The BSTART encoding follows under the explicit title `Entry instruction + encoding`. It must never imply that the entry command is the whole Tile + operation. +4. Exact decode ASL that binds encoded fields into small typed locals and + rejects reserved combinations before effects. +5. An assembler-symbol table generated from the same ASL/catalog owner, + explaining each visible operand, option, default, and assigned value. +6. A semantic execution path ordered as bundle inputs, decode/bind, derived + state, legality, complete-footprint preflight, operation, and publish/commit. + Every stage embeds its exact mnemonic-owner DOC region. When a stage executes + a shared helper, it embeds the exact referenced ASL fragment with module, + lines, source hash, fragment hash, and source link. +7. Human-readable operational information: effects, ordering, fault boundary, + aliases, constraints, and bounded walkthroughs. +8. NDF and ADR bodies expanded inline from their canonical sources. Stable ID + chips and human-readable content are primary; provenance is collapsed below. +9. A final source ledger containing release, commit, ASL path and hash, + generated-document path and hash, and exact ASL/NDF owner links. + +Release and source provenance stays available but does not interrupt the +reader-first explanation at the top of the page. + +### Arm-style embedding contract + +The Arm `ADD (immediate)` page is a structural reference, not a semantic source. +Its important property is that encoding, decode, symbols, and operation are +separate projections of one instruction definition: + +- the register diagram appears once, before variant-specific assembly forms; +- each assembly placeholder links conceptually to one symbol definition; +- decode pseudocode is embedded in place and only performs field binding and + legality preparation; +- operation pseudocode is embedded separately and performs the architectural + computation and writeback; +- shared helpers remain links/references rather than copied implementations; +- operational notes follow the exact pseudocode instead of interrupting it. + +PTO must preserve the same separation. A `DOC-BEGIN: decode` region that merely +returns an operation or handler enum is instruction-selection linkage, not a +reader-visible decode. The portal must label it as such and must not present it +as Encoding/Decode ASL. Promoting it to a true Decode section requires enhancing +the owning ASL to expose field binding and legality, regenerating projections, +and rerunning the normative verification flow. ## Design principles -- Source before interpretation: place exact owner identity and embedded source - ahead of hand-written explanation. +- Human reading before provenance chrome: begin with what the instruction is, + its assembly syntax, and its behavior; place commit and source identity in the + final source ledger. +- Source inside the explanation: the working-mechanism section embeds the exact + ASL operation region instead of paraphrasing it into a second semantic source. - One context per implementation task: instruction, NDF, source, and evidence views remain in the same workbench route. - Progressive disclosure: readable contract first, deeper source and graph views @@ -239,9 +368,12 @@ must resolve to the embedded ASL/NDF owner. ## Visual language -- Color: neutral light reading surfaces; dark engineering workbench surfaces; - blue and cyan for selection and data focus; restrained green, amber, and red - only for explicit validated, pending, and failing states. +- Color: dark-first obsidian reading and workbench surfaces; vivid signal red + for brand, primary actions, and current-route emphasis; high-contrast gold/ + yellow for focus, + source identity, and selected-data highlights. Light mode remains available + as a warm neutral fallback. Green, amber, and danger colors retain distinct + status meanings; brand red never means failure by itself. - Typography: highly readable sans-serif UI text, stable monospace source and identifiers, and a restrained heading scale suitable for dense reference pages. @@ -263,12 +395,23 @@ must resolve to the embedded ASL/NDF owner. ### Shared components - `GlobalSpecSearch` +- `PortalShell` +- `SpecNavigation` +- `ArchitectureOverview` - `ReleaseIdentity` - `SourceIdentity` +- `SourceLedger` +- `AssemblySyntax` - `ASLViewer` - `GeneratedContract` - `EncodingBitfield` - `NdfClause` +- `InstructionIndex` +- `NdfIndex` +- `NdfDetail` +- `InstructionComposition` +- `SemanticExecution` +- `SemanticIdPath` - `NdfNeighborhood` - `NdfGlobalExplorer` - `EvidenceIndex` @@ -278,9 +421,74 @@ must resolve to the embedded ASL/NDF owner. ### Component contracts +- `PortalShell` supplies the shared desktop rail and mobile navigation drawer + to custom routes without duplicating the native Reference sidebar. It keeps + the document in normal flow and never narrows the content below its readable + responsive width. +- `SpecNavigation` owns one locale-aware route tree. It exposes the current + location through visible styling and `aria-current`, keeps desktop navigation + visible by default, and provides an explicit mobile open/close control with + keyboard and screen-reader semantics. +- `ArchitectureOverview` consumes only build-generated architecture topic data + resolved from released ASL units and their reviewed reader-guide projections. + It renders a mental model, topic relationships, scenarios, exact owner links, + source boundaries, and a collapsed provenance ledger. Missing or distributed + contracts remain visible as source gaps. - `ASLViewer` receives build-generated source text, line information, content - hash, and exact release permalink. It never accepts hand-written semantic - replacement text. + hash, and exact release permalink. It shows the operation region first, + decode second, and the complete owner on demand. It never accepts hand-written + semantic replacement text. +- `InstructionComposition` consumes only structured owner metadata parsed by + the build plugin. It is generic across bundle instruction families and + renders ordered rails, variants, constraints, defaults, examples, command + detail routes, and exact command-owner links. React contains no TLOAD + semantic strings beyond presentation labels. +- Every Tile composition rail and generated Markdown block-composition section + consumes the same ASL-owned `contract.block_composition` projection. A second + `metadata.block` form may provide additional structural detail only when the + build validates it against the same owner; it must not silently omit a legal + variant declared by the contract. +- `InstructionIndex`, `NdfIndex`, and `NdfDetail` consume only build-generated + release data. Index entries link to canonical workbench/detail routes and + preserve stable identity, source path, and hash. +- `SemanticExecution` consumes owner-declared stage bindings. Owner DOC regions + must surround executable ASL. Shared-module fragments are extracted from + explicit source markers at build time; missing, duplicate, reversed, non-ASL, + or hash-inconsistent regions fail the build. +- `SemanticIdPath` never guesses owner boundaries by splitting a stable ID. + Build-time schema data supplies surface, owner, category/kind, and case or + decision number. The default view shows the most useful two to four facets; + the complete ID remains in the copy action, search index, deep-link anchor, + tooltip, provenance, and accessible name. +- `NdfClause` renders canonical body text expanded by default. Mouse drag and + keyboard/touch move buttons may temporarily reorder whole cards in the + current page session. Reordering never changes URL, source order, identity, + hash, relationship data, or canonical source; reload restores source order + and a reset action restores it immediately. +- ADR/NDF content assets are parsed once from canonical sources and reused by + every referring route. ADR cards render the actual Context/Decision/Rationale/ + Consequences structure present in the source, including lists, tables, and + code. NDF cards render the exact owning clause body. A link-only card is a + product defect. +- ASL code uses one repository-owned Prism grammar for workbench sources, + generated reference fences, and expandable AVS sources. Keywords, functions, + built-ins, literals, comments, and operators are distinguishable in light and + dark themes; line numbers retain WCAG contrast, and source meaning never + depends on color alone. +- `AssemblySyntax` reads canonical forms from the ASL-owned instruction metadata + already present in route data. It never maintains a separate syntax table. +- Every Tile page begins its Assembly section with one uniform high-level call + grammar: `Mnemonic inputs -> outputs`. + Build-time projection derives parameter labels from ASL-owned BSTART/B.DATR/ + B.DIM composition and derives input/output order from `contract.operands`. + The page exposes a binding table from every high-level placeholder back to + its exact source field/declaration. LinxISA v0.58 Tile syntax is a structural reading + reference; current PTO owning ASL wins on explicit destinations, operand + order, and the removal of legacy `.reuse` spellings. +- `AssemblerSymbols` joins catalog fields with ASL-owned operand roles, defaults, + assigned values, and reserved-value behavior. It never invents descriptions. +- `SourceLedger` is the last major section on a workbench route and consolidates + release, commit, ASL/NDF owners, generated projection paths, and hashes. - `GeneratedContract` consumes catalog or ASL-derived data and marks its source artifact. - `EvidenceIndex` supports filtering, per-group expand/collapse, expand all, @@ -288,8 +496,8 @@ must resolve to the embedded ASL/NDF owner. - `NdfGlobalExplorer` dynamically loads the smallest reviewed Plotly/WebGL bundle, uses deterministic build-generated positions, and opens a readable clause detail panel on selection. -- `StateTransitionDemo` is explicitly non-normative and cites the ASL functions - and evidence it demonstrates. +- `StateTransitionDemo` is presented as an interactive walkthrough and cites the + ASL functions and evidence it demonstrates in the final source ledger. - A large WebGL component is mounted only after an explicit user action and is purged when hidden or unmounted. @@ -310,15 +518,29 @@ must resolve to the embedded ASL/NDF owner. actions are keyboard accessible; - focus order follows reading order; - focus remains visible in light and dark themes. + - the mobile specification-navigation control is reachable before document + content, announces expanded state, and returns focus predictably when + closed; + - the current left-navigation item uses `aria-current="page"`; group labels + and disclosure controls have explicit accessible names; + - NDF reordering provides Move up, Move down, and Restore default order + controls with polite announcements; the drag handle is separate from body + selection and links. - Contrast/readability: - code, identifiers, metadata, and status text meet contrast requirements; - status never depends on color alone. + - ordinary text meets at least 4.5:1 contrast and meaningful UI boundaries + meet at least 3:1; dark red is not used for small text on dark surfaces; + - focus uses the gold focus-ring token and remains visible in forced-colors + mode; red/gold categories always retain text, shape, or role labels. - Screen-reader semantics: - every interactive graph has a concise text summary and a structured list or table fallback; - dynamic selection and search results use polite announcements; - source line numbers are not read as part of every code line unless explicitly requested. + - Semantic ID chips expose their role labels and the complete stable ID in + the group and copy-button accessible names. - Reduced motion and sensory considerations: - animations become step changes when reduced motion is requested; - no flashing, looping, or background motion; @@ -326,14 +548,19 @@ must resolve to the embedded ASL/NDF owner. ## Responsive behavior -- Desktop: full workbench, Plotly/WebGL exploration, animation, filtering, - zooming, and side-by-side source/evidence views. -- Tablet: retain full capability with smaller local graph limits and stacked - detail panes. +- Desktop: persistent left navigation plus the full workbench, Plotly/WebGL + exploration, animation, filtering, zooming, and side-by-side source/evidence + views. The rail participates in layout and never overlays the document. +- Tablet: the left rail becomes a visible in-flow navigation drawer/button; + retain full capability with smaller local graph limits and stacked detail + panes. - Phone: preserve complete readable source, NDF, generated contracts, and - evidence; default large graphs to a local neighborhood or indexed list. + evidence; expose the same navigation hierarchy through the labelled drawer; + default large graphs to a local neighborhood or indexed list. - WebGL unavailable: use static SVG, tables, and relationship lists. - Touch devices: do not depend on hover; selection opens explicit details. +- Touch devices use the same NDF move buttons when native drag is unsuitable; + no persistence or account state is introduced. ## Interaction states @@ -345,6 +572,8 @@ must resolve to the embedded ASL/NDF owner. source/evidence fallback. - Success: show the exact validated release and commit, not a generic green dashboard state. +- Reordered NDF view: announce the temporary move; Restore default order returns + to canonical source order; reload always discards session order. - Disabled: explain missing browser capability or unavailable relationship and offer the fallback. - Offline/slow network: documentation routes remain usable after their static @@ -356,15 +585,34 @@ must resolve to the embedded ASL/NDF owner. boundaries. - Default language: English. - Supported locales: `en` and `zh-CN` from the first framework release. +- Use `Architecture` in English navigation and `架构` in Simplified Chinese. + Do not mix `PTO Architecture`, `PTO架构`, `PTO ISA`, or `PTO 手册` as labels + for the same navigation level. - Chinese coverage may grow incrementally across landing pages, navigation, guides, and demonstrations. - ASL/NDF source, stable IDs, paths, and evidence identities are never translated. - Missing Chinese content falls back visibly to English. Machine translation is not presented as reviewed content. -- Use `normative`, `generated projection`, `non-normative demonstration`, - `decision history`, `executable evidence`, and `commit-scoped evidence` - consistently. +- Prefer direct section titles such as `Assembly syntax`, `Encoding`, + `Assembler symbols`, `Decode ASL`, `Operation ASL`, `Behavior`, `NDF clauses`, + `Evidence`, and `Sources and release identity`. Put the one-sentence + instruction definition directly beneath the mnemonic title. +- Instruction pages do not explain their own projection machinery in eyebrow + labels. Remove labels such as `At a glance`, `Generated from released + catalog`, `Embedded directly`, and similar source-status microcopy; establish + provenance through the final source ledger instead. +- NDF clause bodies are visible in place under their stable IDs. Their source + links remain available, but the reader does not have to open a disclosure to + read current clause text. +- NDF and ADR card headers prioritize a human title/status plus two to four + semantic ID facets. Commit, path, hashes, version, and other provenance live + in a collapsed `Sources and references` section without weakening build-time + validation. +- Do not show `Unpublished`, `preview`, or repetitive `non-normative` banners in + page chrome. An unreleased build uses the neutral label `Release candidate`; + release eligibility and exact provenance remain machine-readable and visible + in the final source ledger. ## Repository layout @@ -407,6 +655,12 @@ build/site-data/ # Disposable per-route site data files, not injected as global page data; - raw source inclusion follows an explicit generated allowlist; - every entry carries release, commit, path, line, and content-hash identity; + - NDF/ADR records are parsed once from allowlisted canonical paths and reused + across routes; missing IDs, duplicate IDs, source-hash mismatches, reversed + regions, and unallowlisted paths fail closed; + - bundle composition and semantic-stage metadata remain ASCII-compatible + owner data; localized JSON strings may use Unicode escapes so ASLRef input + remains ASCII while the site renders reviewed `zh-CN` text; - deterministic graph layout is computed during build. Plotly renders and interacts with the result but does not own the dependency graph. - Plotly/WebGL: @@ -506,13 +760,20 @@ following are true for the same immutable release candidate commit: - Docusaurus application under `docs/site/`. - English and `zh-CN` locale framework. - Latest-release landing page and global specification search. -- One source-backed workbench for each of the 852 released ASL units, in both - locale trees. +- One source-backed workbench for every released ASL unit, in both locale + trees; the exact count is derived from the release catalog rather than + duplicated in this design contract. - Generated per-locale unit route ledgers with exact ASL and Markdown projection hashes. - Exact ASL source embedding and generated NDF clause views. +- TLOAD bundle-first prototype with Ordinary Local, Local CUBE, and Ordinary + Shared variants, entry-encoding separation, executable owner stages, and + exact shared-helper fragments. +- Inline canonical NDF and ADR content, source-derived semantic ID paths, and + session-only accessible NDF card reordering. - Searchable and collapsible evidence index. -- One TLOAD-only state-transition demonstration, explicitly non-normative. +- One TLOAD-only interactive state-transition walkthrough whose source citations + are consolidated with the page provenance. - One repository-wide Plotly/WebGL NDF relationship explorer. - Static and non-WebGL fallbacks. - Immutable release preview and release-blocking site validation. diff --git a/asl/tile/layout-and-rearrangement/layout/TCONCAT.asl b/asl/tile/layout-and-rearrangement/layout/TCONCAT.asl index 10b6bb660..546e0b763 100644 --- a/asl/tile/layout-and-rearrangement/layout/TCONCAT.asl +++ b/asl/tile/layout-and-rearrangement/layout/TCONCAT.asl @@ -1,4 +1,4 @@ -// PTO-INSTRUCTION: {"assembly":["TCONCAT "],"block":["BSTART.SFU TCONCAT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM (LB1/LB2 for 2D)","B.IOT","BSTOP"],"catalog_indices":[68],"catalog_records":[{"arguments":[{"operand":"destination0"},{"operand":"source0"},{"operand":"source1"}],"command_mnemonic":"BSTART.TEPL","contract_status":"reviewed-complete","datr_contract":{"allowed_nonzero_fields":["Layout"],"pad_union":"must-zero"},"disposition":"accepted-direct-operation","effect_contract":"TCONCAT","family":"TEPL","fault_contract":"ExecuteTileInstruction","function":0,"legality_handler":"TileOperandsLegal_TCONCAT","mode":3,"name":"TCONCAT","operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source-left"},{"field":"source1","role":"source-right"}],"restart_contract":"CompleteBundleAtWithAcceptedApplicabilityRules","selector":"0x060","semantic_handler":"TCONCAT","state_effects":["operand:destination0:destination","operand:source0:source-left","operand:source1:source-right"]}],"classification":["layout-and-rearrangement","layout"],"contract":{"block_composition":["BSTART.SFU TCONCAT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM (LB1/LB2 for 2D)","B.IOT","BSTOP"],"canonical_assembly":["TCONCAT "],"defaults":["At BSTART the bundle descriptor begins with zero-valued B.DATR and B.DIM state; omitted optional commands retain those reset values, and an encoded zero is a value rather than absence.","The TileOperandsLegal_TCONCAT schema determines which B.IOR, B.IOT, B.IOS, B.DATR, and B.DIM bindings are required or optional for TCONCAT."],"encoding_class":"selector-encoded-block-operation","examples":["BSTART.SFU TCONCAT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; BSTOP"],"exceptions":["ExecuteTileInstruction supplies the operation fault contract; illegal bundles and reserved selector combinations reject before architectural effects.","CompleteBundleAtWithAcceptedApplicabilityRules supplies restart and completion behavior after an accepted operation."],"field_contracts":{},"field_zero_meanings":{},"legality":["TCONCAT is selected only by its BSTART carrier and selector/function assignment; it has no standalone opcode.","Before effects, TileOperandsLegal_TCONCAT validates the complete assembled bundle, operand roles, dimensions, data attributes, and applicability.","B.DATR applicability is exactly [{\"allowed_nonzero_fields\":[\"Layout\"],\"pad_union\":\"must-zero\"}]."],"memory_effects":["none"],"operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source-left"},{"field":"source1","role":"source-right"}],"ordering":["none"],"standalone_opcode":false,"state_effects":["Concatenate two source Tiles along columns.","After complete preflight, execute TCONCAT with the operand bindings listed above; destination definedness changes only as specified by that handler."]},"depends_on":["PTO-TILE-MODEL-EXECUTION-REARRANGEMENT","PTO-TILE-MODEL-LEGALITY-LAYOUT-REARRANGEMENT"],"engine":"SFU","id":"PTO-TILE-TCONCAT","mnemonic":"TCONCAT","summary":"Concatenate two source Tiles along columns.","surface":"tile"} +// PTO-INSTRUCTION: {"assembly":["TCONCAT "],"block":["BSTART.SFU TCONCAT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM LB1 (optional)","B.DIM LB2 (optional)","B.IOT","BSTOP"],"catalog_indices":[68],"catalog_records":[{"arguments":[{"operand":"destination0"},{"operand":"source0"},{"operand":"source1"}],"command_mnemonic":"BSTART.TEPL","contract_status":"reviewed-complete","datr_contract":{"allowed_nonzero_fields":["Layout"],"pad_union":"must-zero"},"disposition":"accepted-direct-operation","effect_contract":"TCONCAT","family":"TEPL","fault_contract":"ExecuteTileInstruction","function":0,"legality_handler":"TileOperandsLegal_TCONCAT","mode":3,"name":"TCONCAT","operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source-left"},{"field":"source1","role":"source-right"}],"restart_contract":"CompleteBundleAtWithAcceptedApplicabilityRules","selector":"0x060","semantic_handler":"TCONCAT","state_effects":["operand:destination0:destination","operand:source0:source-left","operand:source1:source-right"]}],"classification":["layout-and-rearrangement","layout"],"contract":{"block_composition":["BSTART.SFU TCONCAT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM LB1 (optional)","B.DIM LB2 (optional)","B.IOT","BSTOP"],"canonical_assembly":["TCONCAT "],"defaults":["At BSTART the bundle descriptor begins with zero-valued B.DATR and B.DIM state; omitted optional commands retain those reset values, and an encoded zero is a value rather than absence.","The TileOperandsLegal_TCONCAT schema determines which B.IOR, B.IOT, B.IOS, B.DATR, and B.DIM bindings are required or optional for TCONCAT."],"encoding_class":"selector-encoded-block-operation","examples":["BSTART.SFU TCONCAT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; BSTOP"],"exceptions":["ExecuteTileInstruction supplies the operation fault contract; illegal bundles and reserved selector combinations reject before architectural effects.","CompleteBundleAtWithAcceptedApplicabilityRules supplies restart and completion behavior after an accepted operation."],"field_contracts":{},"field_zero_meanings":{},"legality":["TCONCAT is selected only by its BSTART carrier and selector/function assignment; it has no standalone opcode.","Before effects, TileOperandsLegal_TCONCAT validates the complete assembled bundle, operand roles, dimensions, data attributes, and applicability.","B.DATR applicability is exactly [{\"allowed_nonzero_fields\":[\"Layout\"],\"pad_union\":\"must-zero\"}]."],"memory_effects":["none"],"operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source-left"},{"field":"source1","role":"source-right"}],"ordering":["none"],"standalone_opcode":false,"state_effects":["Concatenate two source Tiles along columns.","After complete preflight, execute TCONCAT with the operand bindings listed above; destination definedness changes only as specified by that handler."]},"depends_on":["PTO-TILE-MODEL-EXECUTION-REARRANGEMENT","PTO-TILE-MODEL-LEGALITY-LAYOUT-REARRANGEMENT"],"engine":"SFU","id":"PTO-TILE-TCONCAT","mnemonic":"TCONCAT","summary":"Concatenate two source Tiles along columns.","surface":"tile"} // PTO-REVIEW: {"review_method":"formal-definition-read","outcome":"FORMAL-COMPLETE","reviewed_fields":["assembly","encoding","defaults","operation","state","memory","ordering","faults","reserved"]} // NDF-BEGIN: PTO-TCONCAT-CONTRACT-001 // ndf: kind=contract level=L1 layer=tile status=accepted diff --git a/asl/tile/layout-and-rearrangement/layout/TEXTRACT.asl b/asl/tile/layout-and-rearrangement/layout/TEXTRACT.asl index 7c4c62f51..f0ecb0d2a 100644 --- a/asl/tile/layout-and-rearrangement/layout/TEXTRACT.asl +++ b/asl/tile/layout-and-rearrangement/layout/TEXTRACT.asl @@ -1,4 +1,4 @@ -// PTO-INSTRUCTION: {"assembly":["TEXTRACT "],"block":["BSTART.SFU TEXTRACT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM (LB1/LB2 for 2D)","B.IOT","B.IOR","BSTOP"],"catalog_indices":[69],"catalog_records":[{"arguments":[{"operand":"destination0"},{"operand":"source0"},{"operand":"natural0"},{"operand":"natural1"}],"command_mnemonic":"BSTART.TEPL","contract_status":"reviewed-complete","datr_contract":{"allowed_nonzero_fields":["Layout"],"pad_union":"must-zero"},"disposition":"accepted-direct-operation","effect_contract":"TEXTRACT","family":"TEPL","fault_contract":"ExecuteTileInstruction","function":2,"legality_handler":"TileOperandsLegal_TEXTRACT","mode":3,"name":"TEXTRACT","operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source"},{"field":"natural0","role":"row-offset"},{"field":"natural1","role":"column-offset"}],"restart_contract":"CompleteBundleAtWithAcceptedApplicabilityRules","selector":"0x062","semantic_handler":"TEXTRACT","state_effects":["operand:destination0:destination","operand:source0:source","operand:natural0:row-offset","operand:natural1:column-offset"]}],"classification":["layout-and-rearrangement","layout"],"contract":{"block_composition":["BSTART.SFU TEXTRACT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM (LB1/LB2 for 2D)","B.IOT","B.IOR","BSTOP"],"canonical_assembly":["TEXTRACT "],"defaults":["At BSTART the bundle descriptor begins with zero-valued B.DATR and B.DIM state; omitted optional commands retain those reset values, and an encoded zero is a value rather than absence.","The TileOperandsLegal_TEXTRACT schema determines which B.IOR, B.IOT, B.IOS, B.DATR, and B.DIM bindings are required or optional for TEXTRACT."],"encoding_class":"selector-encoded-block-operation","examples":["BSTART.SFU TEXTRACT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; B.IOR; BSTOP"],"exceptions":["ExecuteTileInstruction supplies the operation fault contract; illegal bundles and reserved selector combinations reject before architectural effects.","CompleteBundleAtWithAcceptedApplicabilityRules supplies restart and completion behavior after an accepted operation."],"field_contracts":{},"field_zero_meanings":{},"legality":["TEXTRACT is selected only by its BSTART carrier and selector/function assignment; it has no standalone opcode.","Before effects, TileOperandsLegal_TEXTRACT validates the complete assembled bundle, operand roles, dimensions, data attributes, and applicability.","B.DATR applicability is exactly [{\"allowed_nonzero_fields\":[\"Layout\"],\"pad_union\":\"must-zero\"}]."],"memory_effects":["none"],"operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source"},{"field":"natural0","role":"row-offset"},{"field":"natural1","role":"column-offset"}],"ordering":["none"],"standalone_opcode":false,"state_effects":["Extract a rectangular source region at the encoded row and column offsets.","After complete preflight, execute TEXTRACT with the operand bindings listed above; destination definedness changes only as specified by that handler."]},"depends_on":["PTO-TILE-MODEL-EXECUTION-REARRANGEMENT","PTO-TILE-MODEL-LEGALITY-LAYOUT-REARRANGEMENT"],"engine":"SFU","id":"PTO-TILE-TEXTRACT","mnemonic":"TEXTRACT","summary":"Extract a rectangular source region at the encoded row and column offsets.","surface":"tile"} +// PTO-INSTRUCTION: {"assembly":["TEXTRACT "],"block":["BSTART.SFU TEXTRACT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM LB1 (optional)","B.DIM LB2 (optional)","B.IOT","B.IOR","BSTOP"],"catalog_indices":[69],"catalog_records":[{"arguments":[{"operand":"destination0"},{"operand":"source0"},{"operand":"natural0"},{"operand":"natural1"}],"command_mnemonic":"BSTART.TEPL","contract_status":"reviewed-complete","datr_contract":{"allowed_nonzero_fields":["Layout"],"pad_union":"must-zero"},"disposition":"accepted-direct-operation","effect_contract":"TEXTRACT","family":"TEPL","fault_contract":"ExecuteTileInstruction","function":2,"legality_handler":"TileOperandsLegal_TEXTRACT","mode":3,"name":"TEXTRACT","operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source"},{"field":"natural0","role":"row-offset"},{"field":"natural1","role":"column-offset"}],"restart_contract":"CompleteBundleAtWithAcceptedApplicabilityRules","selector":"0x062","semantic_handler":"TEXTRACT","state_effects":["operand:destination0:destination","operand:source0:source","operand:natural0:row-offset","operand:natural1:column-offset"]}],"classification":["layout-and-rearrangement","layout"],"contract":{"block_composition":["BSTART.SFU TEXTRACT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM LB1 (optional)","B.DIM LB2 (optional)","B.IOT","B.IOR","BSTOP"],"canonical_assembly":["TEXTRACT "],"defaults":["At BSTART the bundle descriptor begins with zero-valued B.DATR and B.DIM state; omitted optional commands retain those reset values, and an encoded zero is a value rather than absence.","The TileOperandsLegal_TEXTRACT schema determines which B.IOR, B.IOT, B.IOS, B.DATR, and B.DIM bindings are required or optional for TEXTRACT."],"encoding_class":"selector-encoded-block-operation","examples":["BSTART.SFU TEXTRACT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; B.IOR; BSTOP"],"exceptions":["ExecuteTileInstruction supplies the operation fault contract; illegal bundles and reserved selector combinations reject before architectural effects.","CompleteBundleAtWithAcceptedApplicabilityRules supplies restart and completion behavior after an accepted operation."],"field_contracts":{},"field_zero_meanings":{},"legality":["TEXTRACT is selected only by its BSTART carrier and selector/function assignment; it has no standalone opcode.","Before effects, TileOperandsLegal_TEXTRACT validates the complete assembled bundle, operand roles, dimensions, data attributes, and applicability.","B.DATR applicability is exactly [{\"allowed_nonzero_fields\":[\"Layout\"],\"pad_union\":\"must-zero\"}]."],"memory_effects":["none"],"operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source"},{"field":"natural0","role":"row-offset"},{"field":"natural1","role":"column-offset"}],"ordering":["none"],"standalone_opcode":false,"state_effects":["Extract a rectangular source region at the encoded row and column offsets.","After complete preflight, execute TEXTRACT with the operand bindings listed above; destination definedness changes only as specified by that handler."]},"depends_on":["PTO-TILE-MODEL-EXECUTION-REARRANGEMENT","PTO-TILE-MODEL-LEGALITY-LAYOUT-REARRANGEMENT"],"engine":"SFU","id":"PTO-TILE-TEXTRACT","mnemonic":"TEXTRACT","summary":"Extract a rectangular source region at the encoded row and column offsets.","surface":"tile"} // PTO-REVIEW: {"review_method":"formal-definition-read","outcome":"FORMAL-COMPLETE","reviewed_fields":["assembly","encoding","defaults","operation","state","memory","ordering","faults","reserved"]} // NDF-BEGIN: PTO-TEXTRACT-CONTRACT-001 // ndf: kind=contract level=L1 layer=tile status=accepted diff --git a/asl/tile/layout-and-rearrangement/layout/TINSERT.asl b/asl/tile/layout-and-rearrangement/layout/TINSERT.asl index c0400a55f..7dc3e7cc9 100644 --- a/asl/tile/layout-and-rearrangement/layout/TINSERT.asl +++ b/asl/tile/layout-and-rearrangement/layout/TINSERT.asl @@ -1,4 +1,4 @@ -// PTO-INSTRUCTION: {"assembly":["TINSERT "],"block":["BSTART.SFU TINSERT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM (LB1/LB2 for 2D)","B.IOT","B.IOR","BSTOP"],"catalog_indices":[70],"catalog_records":[{"arguments":[{"operand":"destination0"},{"operand":"source0"},{"operand":"source1"},{"operand":"natural0"},{"operand":"natural1"}],"command_mnemonic":"BSTART.TEPL","contract_status":"reviewed-complete","datr_contract":{"allowed_nonzero_fields":["Layout"],"pad_union":"must-zero"},"disposition":"accepted-direct-operation","effect_contract":"TINSERT","family":"TEPL","fault_contract":"ExecuteTileInstruction","function":3,"legality_handler":"TileOperandsLegal_TINSERT","mode":3,"name":"TINSERT","operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"persistent old destination"},{"field":"source1","role":"persistent insertion source"},{"field":"natural0","role":"row-offset"},{"field":"natural1","role":"column-offset"}],"restart_contract":"CompleteBundleAtWithAcceptedApplicabilityRules","selector":"0x063","semantic_handler":"TINSERT","state_effects":["operand:destination0:destination","operand:source0:persistent-old-destination","operand:source1:persistent-insertion-source","operand:natural0:row-offset","operand:natural1:column-offset"]}],"classification":["layout-and-rearrangement","layout"],"contract":{"block_composition":["BSTART.SFU TINSERT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM (LB1/LB2 for 2D)","B.IOT","B.IOR","BSTOP"],"canonical_assembly":["TINSERT "],"defaults":["At BSTART the bundle descriptor begins with zero-valued B.DATR and B.DIM state; omitted optional commands retain those reset values, and an encoded zero is a value rather than absence.","The TileOperandsLegal_TINSERT schema determines which B.IOR, B.IOT, B.IOS, B.DATR, and B.DIM bindings are required or optional for TINSERT."],"encoding_class":"selector-encoded-block-operation","examples":["BSTART.SFU TINSERT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; B.IOR; BSTOP"],"exceptions":["ExecuteTileInstruction supplies the operation fault contract; illegal bundles and reserved selector combinations reject before architectural effects.","CompleteBundleAtWithAcceptedApplicabilityRules supplies restart and completion behavior after an accepted operation."],"field_contracts":{},"field_zero_meanings":{},"legality":["TINSERT is selected only by its BSTART carrier and selector/function assignment; it has no standalone opcode.","Before effects, TileOperandsLegal_TINSERT validates the complete assembled bundle, operand roles, dimensions, data attributes, and applicability.","B.DATR applicability is exactly [{\"allowed_nonzero_fields\":[\"Layout\"],\"pad_union\":\"must-zero\"}]."],"memory_effects":["none"],"operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"persistent old destination"},{"field":"source1","role":"persistent insertion source"},{"field":"natural0","role":"row-offset"},{"field":"natural1","role":"column-offset"}],"ordering":["none"],"standalone_opcode":false,"state_effects":["Insert a source Tile into a snapshotted old destination region at the encoded row and column offsets.","After complete preflight, execute TINSERT with the operand bindings listed above; destination definedness changes only as specified by that handler."]},"depends_on":["PTO-TILE-MODEL-EXECUTION-REARRANGEMENT","PTO-TILE-MODEL-LEGALITY-LAYOUT-REARRANGEMENT"],"engine":"SFU","id":"PTO-TILE-TINSERT","mnemonic":"TINSERT","summary":"Insert a source Tile into a snapshotted old destination region at the encoded row and column offsets.","surface":"tile"} +// PTO-INSTRUCTION: {"assembly":["TINSERT "],"block":["BSTART.SFU TINSERT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM LB1 (optional)","B.DIM LB2 (optional)","B.IOT","B.IOR","BSTOP"],"catalog_indices":[70],"catalog_records":[{"arguments":[{"operand":"destination0"},{"operand":"source0"},{"operand":"source1"},{"operand":"natural0"},{"operand":"natural1"}],"command_mnemonic":"BSTART.TEPL","contract_status":"reviewed-complete","datr_contract":{"allowed_nonzero_fields":["Layout"],"pad_union":"must-zero"},"disposition":"accepted-direct-operation","effect_contract":"TINSERT","family":"TEPL","fault_contract":"ExecuteTileInstruction","function":3,"legality_handler":"TileOperandsLegal_TINSERT","mode":3,"name":"TINSERT","operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"persistent old destination"},{"field":"source1","role":"persistent insertion source"},{"field":"natural0","role":"row-offset"},{"field":"natural1","role":"column-offset"}],"restart_contract":"CompleteBundleAtWithAcceptedApplicabilityRules","selector":"0x063","semantic_handler":"TINSERT","state_effects":["operand:destination0:destination","operand:source0:persistent-old-destination","operand:source1:persistent-insertion-source","operand:natural0:row-offset","operand:natural1:column-offset"]}],"classification":["layout-and-rearrangement","layout"],"contract":{"block_composition":["BSTART.SFU TINSERT, DataType","B.DATR (optional)","B.DIM LB0","B.DIM LB1 (optional)","B.DIM LB2 (optional)","B.IOT","B.IOR","BSTOP"],"canonical_assembly":["TINSERT "],"defaults":["At BSTART the bundle descriptor begins with zero-valued B.DATR and B.DIM state; omitted optional commands retain those reset values, and an encoded zero is a value rather than absence.","The TileOperandsLegal_TINSERT schema determines which B.IOR, B.IOT, B.IOS, B.DATR, and B.DIM bindings are required or optional for TINSERT."],"encoding_class":"selector-encoded-block-operation","examples":["BSTART.SFU TINSERT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; B.IOR; BSTOP"],"exceptions":["ExecuteTileInstruction supplies the operation fault contract; illegal bundles and reserved selector combinations reject before architectural effects.","CompleteBundleAtWithAcceptedApplicabilityRules supplies restart and completion behavior after an accepted operation."],"field_contracts":{},"field_zero_meanings":{},"legality":["TINSERT is selected only by its BSTART carrier and selector/function assignment; it has no standalone opcode.","Before effects, TileOperandsLegal_TINSERT validates the complete assembled bundle, operand roles, dimensions, data attributes, and applicability.","B.DATR applicability is exactly [{\"allowed_nonzero_fields\":[\"Layout\"],\"pad_union\":\"must-zero\"}]."],"memory_effects":["none"],"operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"persistent old destination"},{"field":"source1","role":"persistent insertion source"},{"field":"natural0","role":"row-offset"},{"field":"natural1","role":"column-offset"}],"ordering":["none"],"standalone_opcode":false,"state_effects":["Insert a source Tile into a snapshotted old destination region at the encoded row and column offsets.","After complete preflight, execute TINSERT with the operand bindings listed above; destination definedness changes only as specified by that handler."]},"depends_on":["PTO-TILE-MODEL-EXECUTION-REARRANGEMENT","PTO-TILE-MODEL-LEGALITY-LAYOUT-REARRANGEMENT"],"engine":"SFU","id":"PTO-TILE-TINSERT","mnemonic":"TINSERT","summary":"Insert a source Tile into a snapshotted old destination region at the encoded row and column offsets.","surface":"tile"} // PTO-REVIEW: {"review_method":"formal-definition-read","outcome":"FORMAL-COMPLETE","reviewed_fields":["assembly","encoding","defaults","operation","state","memory","ordering","faults","reserved"]} // NDF-BEGIN: PTO-TINSERT-CONTRACT-001 // ndf: kind=contract level=L1 layer=tile status=accepted diff --git a/asl/tile/layout-and-rearrangement/layout/TMOV.asl b/asl/tile/layout-and-rearrangement/layout/TMOV.asl index 3c4651f03..ed99382b9 100644 --- a/asl/tile/layout-and-rearrangement/layout/TMOV.asl +++ b/asl/tile/layout-and-rearrangement/layout/TMOV.asl @@ -1,4 +1,4 @@ -// PTO-INSTRUCTION: {"assembly":["TMOV "],"block":["BSTART.TLSU TMOV, DataType","B.DIM LB0","B.DIM (LB1/LB2 for 2D)","B.IOT","BSTOP"],"catalog_indices":[83],"catalog_records":[{"arguments":[{"operand":"destination0"},{"operand":"source0"}],"command_mnemonic":"BSTART.TMOV","contract_status":"reviewed-complete","datr_contract":{"allowed_nonzero_fields":["Layout"],"pad_union":"must-zero"},"disposition":"accepted-direct-operation","effect_contract":"TMOV","family":"TLSU","fault_contract":"ExecuteTileInstruction","function":2,"legality_handler":"TileOperandsLegal_TMOV","name":"TMOV","operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source"}],"restart_contract":"CompleteBundleAtWithAcceptedApplicabilityRules","semantic_handler":"TMOV","state_effects":["operand:destination0:destination","operand:source0:source"]}],"classification":["layout-and-rearrangement","layout"],"contract":{"block_composition":["BSTART.TLSU TMOV, DataType","B.DIM LB0","B.DIM (LB1/LB2 for 2D)","B.IOT","BSTOP"],"canonical_assembly":["TMOV "],"defaults":["At BSTART the bundle descriptor begins with zero-valued B.DATR and B.DIM state; omitted optional commands retain those reset values, and an encoded zero is a value rather than absence.","The TileOperandsLegal_TMOV schema determines which B.IOR, B.IOT, B.IOS, B.DATR, and B.DIM bindings are required or optional for TMOV."],"encoding_class":"selector-encoded-block-operation","examples":["BSTART.TLSU TMOV, DataType; B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; BSTOP"],"exceptions":["ExecuteTileInstruction supplies the operation fault contract; illegal bundles and reserved selector combinations reject before architectural effects.","CompleteBundleAtWithAcceptedApplicabilityRules supplies restart and completion behavior after an accepted operation."],"field_contracts":{},"field_zero_meanings":{},"legality":["TMOV is selected only by its BSTART carrier and selector/function assignment; it has no standalone opcode.","Before effects, TileOperandsLegal_TMOV validates the complete assembled bundle, operand roles, dimensions, data attributes, and applicability.","B.DATR applicability is exactly [{\"allowed_nonzero_fields\":[\"Layout\"],\"pad_union\":\"must-zero\"}]."],"memory_effects":["Perform only the global, Local, or Shared data movement named by the mnemonic after complete access, shape, stride, and descriptor validation; a fault produces no partial destination or memory effect."],"operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source"}],"ordering":["none"],"standalone_opcode":false,"state_effects":["Copy the source Tile payload and definedness into the destination.","After complete preflight, execute TMOV with the operand bindings listed above; destination definedness changes only as specified by that handler."]},"depends_on":["PTO-TILE-MODEL-MEMORY-SHARED-MOVEMENT","PTO-TILE-MODEL-LEGALITY-MEMORY-SCHEMA"],"engine":"TLSU","id":"PTO-TILE-TMOV","mnemonic":"TMOV","summary":"Copy the source Tile payload and definedness into the destination.","surface":"tile"} +// PTO-INSTRUCTION: {"assembly":["TMOV "],"block":["BSTART.TLSU TMOV, DataType","B.DIM LB0","B.DIM LB1 (optional)","B.DIM LB2 (optional)","B.IOT","BSTOP"],"catalog_indices":[83],"catalog_records":[{"arguments":[{"operand":"destination0"},{"operand":"source0"}],"command_mnemonic":"BSTART.TMOV","contract_status":"reviewed-complete","datr_contract":{"allowed_nonzero_fields":["Layout"],"pad_union":"must-zero"},"disposition":"accepted-direct-operation","effect_contract":"TMOV","family":"TLSU","fault_contract":"ExecuteTileInstruction","function":2,"legality_handler":"TileOperandsLegal_TMOV","name":"TMOV","operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source"}],"restart_contract":"CompleteBundleAtWithAcceptedApplicabilityRules","semantic_handler":"TMOV","state_effects":["operand:destination0:destination","operand:source0:source"]}],"classification":["layout-and-rearrangement","layout"],"contract":{"block_composition":["BSTART.TLSU TMOV, DataType","B.DIM LB0","B.DIM LB1 (optional)","B.DIM LB2 (optional)","B.IOT","BSTOP"],"canonical_assembly":["TMOV "],"defaults":["At BSTART the bundle descriptor begins with zero-valued B.DATR and B.DIM state; omitted optional commands retain those reset values, and an encoded zero is a value rather than absence.","The TileOperandsLegal_TMOV schema determines which B.IOR, B.IOT, B.IOS, B.DATR, and B.DIM bindings are required or optional for TMOV."],"encoding_class":"selector-encoded-block-operation","examples":["BSTART.TLSU TMOV, DataType; B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; BSTOP"],"exceptions":["ExecuteTileInstruction supplies the operation fault contract; illegal bundles and reserved selector combinations reject before architectural effects.","CompleteBundleAtWithAcceptedApplicabilityRules supplies restart and completion behavior after an accepted operation."],"field_contracts":{},"field_zero_meanings":{},"legality":["TMOV is selected only by its BSTART carrier and selector/function assignment; it has no standalone opcode.","Before effects, TileOperandsLegal_TMOV validates the complete assembled bundle, operand roles, dimensions, data attributes, and applicability.","B.DATR applicability is exactly [{\"allowed_nonzero_fields\":[\"Layout\"],\"pad_union\":\"must-zero\"}]."],"memory_effects":["Perform only the global, Local, or Shared data movement named by the mnemonic after complete access, shape, stride, and descriptor validation; a fault produces no partial destination or memory effect."],"operands":[{"field":"destination0","role":"destination"},{"field":"source0","role":"source"}],"ordering":["none"],"standalone_opcode":false,"state_effects":["Copy the source Tile payload and definedness into the destination.","After complete preflight, execute TMOV with the operand bindings listed above; destination definedness changes only as specified by that handler."]},"depends_on":["PTO-TILE-MODEL-MEMORY-SHARED-MOVEMENT","PTO-TILE-MODEL-LEGALITY-MEMORY-SCHEMA"],"engine":"TLSU","id":"PTO-TILE-TMOV","mnemonic":"TMOV","summary":"Copy the source Tile payload and definedness into the destination.","surface":"tile"} // PTO-REVIEW: {"review_method":"formal-definition-read","outcome":"FORMAL-COMPLETE","reviewed_fields":["assembly","encoding","defaults","operation","state","memory","ordering","faults","reserved"]} // NDF-BEGIN: PTO-TMOV-CONTRACT-001 // ndf: kind=contract level=L1 layer=tile status=accepted diff --git a/docs/site/data/instruction-projections/PTO-TILE-TLOAD.json b/docs/site/data/instruction-projections/PTO-TILE-TLOAD.json new file mode 100644 index 000000000..21b553c9f --- /dev/null +++ b/docs/site/data/instruction-projections/PTO-TILE-TLOAD.json @@ -0,0 +1,1183 @@ +{ + "schema": "pto.site-instruction-projection.v1", + "unitId": "PTO-TILE-TLOAD", + "ownerSource": "asl/tile/memory-and-data-movement/regular/TLOAD.asl", + "ownerSourceSha256": "4e6997ee955ecf4aa0915cb518d17f4856dcd2583fa10003cb55a9e493964f13", + "composition": { + "owner": "PTO-TILE-TLOAD", + "variants": [ + { + "id": "ordinary-local", + "label": { + "en": "Ordinary Local destination", + "zh-CN": "普通 Local 目标" + }, + "summary": { + "en": "Load a two-dimensional row-major GM range into one newly allocated Local Tile.", + "zh-CN": "把 GM 中的二维行主序范围载入一个新分配的 Local Tile。" + }, + "canonicalCommandCount": { + "en": "3-command minimum; 8-command complete form", + "zh-CN": "最短 3 条命令;完整形态 8 条命令" + }, + "canonicalMinCommands": 3, + "canonicalMaxCommands": 8, + "minimumSequence": [ + "BSTART.TLOAD U8", + "B.IOT mask=1111, , ->T<1>", + "BSTOP" + ], + "completeSequence": [ + "BSTART.TLOAD U8", + "B.DATR {NORM, DTYPE_NONE, Zero, EQ, Default, 0, 0}", + "B.DIM zero, 64, ->LB0", + "B.DIM zero, 8, ->LB1", + "B.DIM zero, 64, ->LB2", + "B.IOR zero, a0", + "B.IOT mask=1111, , ->T<1>", + "BSTOP" + ], + "relationships": [ + { + "en": "B.IOT is the single Local destination and is mutually exclusive with B.IOS.", + "zh-CN": "B.IOT 是唯一的 Local 目标,并与 B.IOS 互斥。" + }, + { + "en": "B.DIM may appear once for each of LB0, LB1, and LB2; a second write to the same LB is illegal.", + "zh-CN": "B.DIM 可分别为 LB0、LB1、LB2 各出现一次;重复写同一个 LB 非法。" + }, + { + "en": "B.DATR and B.IOR are optional only as complete commands; encoded zero fields are present values, not omission.", + "zh-CN": "B.DATR 与 B.IOR 只能整条省略;已编码的零字段是显式值,不代表省略。" + } + ], + "commands": [ + { + "mnemonic": "BSTART.TLOAD", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Open the block, select TLSU Function 0, and supply the transfer element type.", + "zh-CN": "打开 block、选择 TLSU Function 0,并提供传输元素类型。" + }, + "parameters": [ + { + "name": "DataType", + "meaning": { + "en": "Element type for GM accesses, dense-stride derivation, and the destination descriptor.", + "zh-CN": "用于 GM 访问、紧密行跨度推导和目标描述符的元素类型。" + } + } + ] + }, + { + "mnemonic": "B.DATR", + "minOccurrences": 0, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "optional", + "role": { + "en": "Select ordinary layout attributes; omission selects NORM.", + "zh-CN": "选择普通布局属性;省略时选择 NORM。" + }, + "parameters": [ + { + "name": "Layout", + "meaning": { + "en": "Must remain NORM for ordinary TLOAD.", + "zh-CN": "普通 TLOAD 必须保持 NORM。" + }, + "omission": { + "en": "NORM layout.", + "zh-CN": "NORM 布局。" + } + }, + { + "name": "PadValueOrByteId", + "meaning": { + "en": "Must be zero; ordinary TLOAD has no directional or tail padding control.", + "zh-CN": "必须为零;普通 TLOAD 没有方向 padding 或尾部 padding 控制。" + } + } + ] + }, + { + "mnemonic": "B.DIM", + "minOccurrences": 0, + "maxOccurrences": 3, + "repeatable": true, + "requirement": "optional", + "role": { + "en": "Write the valid rectangle and physical row width, one write-once LB per command.", + "zh-CN": "写入有效矩形与物理行宽;每条命令写一个只能写一次的 LB。" + }, + "parameters": [ + { + "name": "LB0 / ValidCol", + "meaning": { + "en": "Valid elements per row.", + "zh-CN": "每行的有效元素数。" + }, + "omission": { + "en": "Common destination-shape default; source-free Local TLOAD resolves to 1.", + "zh-CN": "采用公共目标形状缺省;无源 Local TLOAD 解析为 1。" + } + }, + { + "name": "LB1 / ValidRow", + "meaning": { + "en": "Number of valid rows.", + "zh-CN": "有效行数。" + }, + "omission": { + "en": "Common destination-shape default; source-free Local TLOAD resolves to 1.", + "zh-CN": "采用公共目标形状缺省;无源 Local TLOAD 解析为 1。" + } + }, + { + "name": "LB2 / Col", + "meaning": { + "en": "Physical columns in elements; ValidCol must not exceed Col.", + "zh-CN": "以元素计的物理列数;ValidCol 不得超过 Col。" + }, + "omission": { + "en": "Resolved ValidCol.", + "zh-CN": "解析后的 ValidCol。" + } + } + ] + }, + { + "mnemonic": "B.IOR", + "minOccurrences": 0, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "optional", + "role": { + "en": "Bind the two per-PE private-GPR address inputs.", + "zh-CN": "绑定两个逐 PE 私有 GPR 地址输入。" + }, + "parameters": [ + { + "name": "RegSrc0", + "meaning": { + "en": "Per-PE GM base byte address.", + "zh-CN": "逐 PE 的 GM 基地址(字节地址)。" + }, + "omission": { + "en": "Base address zero.", + "zh-CN": "基地址为零。" + } + }, + { + "name": "RegSrc1", + "meaning": { + "en": "Per-PE XLEN byte distance between adjacent row starts.", + "zh-CN": "逐 PE 的相邻行起点间 XLEN 字节距离。" + }, + "omission": { + "en": "Dense byte stride ceil(Col * element_bits / 8); an explicitly selected zero GPR is a real zero stride that aliases rows.", + "zh-CN": "紧密字节跨度 ceil(Col * element_bits / 8);显式选择零 GPR 得到真实零跨度,会使各行别名。" + } + } + ] + }, + { + "mnemonic": "B.IOT", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Allocate one Local destination and terminate the Tile-binding stream.", + "zh-CN": "分配一个 Local 目标并终止 Tile 绑定流。" + }, + "parameters": [ + { + "name": "DstTile", + "meaning": { + "en": "Destination hand T, U, M, or N.", + "zh-CN": "目标 hand:T、U、M 或 N。" + } + }, + { + "name": "SizeCode", + "meaning": { + "en": "Capacity code 1..12: 128 B through 256 KiB per participating PE.", + "zh-CN": "容量码 1..12:每个参与 PE 为 128 B 至 256 KiB。" + } + }, + { + "name": "PEMode", + "meaning": { + "en": "Three-bit participation mode decoded to the fixed four-PE mask.", + "zh-CN": "三位参与模式,解码为固定的四 PE mask。" + } + }, + { + "name": "L", + "meaning": { + "en": "Terminates this one-entry destination stream; it is not a source-lifetime flag.", + "zh-CN": "终止这一项目标流;它不是源生命周期标志。" + } + } + ] + }, + { + "mnemonic": "BSTOP", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Close the block and commit only after complete schema and GM-footprint preflight.", + "zh-CN": "关闭 block,并只在完整 schema 与 GM footprint 预检查成功后提交。" + }, + "parameters": [] + } + ] + }, + { + "id": "cube-local", + "label": { + "en": "Local CUBE conversion", + "zh-CN": "Local CUBE 转换" + }, + "summary": { + "en": "Load a two-dimensional ordinary GM range and convert it into persistent Local CUBE storage.", + "zh-CN": "载入二维普通 GM 范围,并转换为持久的 Local CUBE 存储。" + }, + "canonicalCommandCount": { + "en": "6-command minimum; 7-command complete form", + "zh-CN": "最短 6 条命令;完整形态 7 条命令" + }, + "canonicalMinCommands": 6, + "canonicalMaxCommands": 7, + "minimumSequence": [ + "BSTART.TLOAD FP16", + "B.DATR {ND2N8, DTYPE_NONE, Max, EQ, Default, 0, 0}", + "B.DIM a1, 0, ->LB0", + "B.DIM a2, 0, ->LB1", + "B.IOT mask=1111, , ->N<3>", + "BSTOP" + ], + "completeSequence": [ + "BSTART.TLOAD FP16", + "B.DATR {ND2N8, DTYPE_NONE, Max, EQ, Default, 0, 0}", + "B.DIM a1, 0, ->LB0", + "B.DIM a2, 0, ->LB1", + "B.IOR a0, a3", + "B.IOT mask=1111, , ->N<3>", + "BSTOP" + ], + "relationships": [ + { + "en": "B.DATR is conditionally required by the CUBE form and must select ND2M32, ND2M16, or ND2N8 with DTYPE_NONE.", + "zh-CN": "CUBE 形态条件必选 B.DATR,并必须用 DTYPE_NONE 选择 ND2M32、ND2M16 或 ND2N8。" + }, + { + "en": "Exactly two B.DIM commands write LB0 and LB1; LB2 is mutually exclusive with CUBE geometry and is forbidden.", + "zh-CN": "恰好两条 B.DIM 写 LB0 与 LB1;LB2 与 CUBE 几何互斥,禁止出现。" + }, + { + "en": "PadValueOrByteId fills only physical CUBE CELL tail positions after all valid GM reads succeed; it is not directional padding.", + "zh-CN": "PadValueOrByteId 仅在所有有效 GM 读取成功后填充物理 CUBE CELL 尾部;它不是方向 padding。" + } + ], + "commands": [ + { + "mnemonic": "BSTART.TLOAD", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Open TLOAD and supply the concrete transfer element type.", + "zh-CN": "打开 TLOAD 并提供具体传输元素类型。" + }, + "parameters": [ + { + "name": "DataType", + "meaning": { + "en": "Concrete b32, b16, b8, or b4 type supported by the selected CUBE mapping.", + "zh-CN": "所选 CUBE 映射支持的具体 b32、b16、b8 或 b4 类型。" + } + } + ] + }, + { + "mnemonic": "B.DATR", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "conditional", + "role": { + "en": "Conditionally required to select GM-to-Local CUBE conversion and tail PadValue.", + "zh-CN": "CUBE 形态条件必选,用于选择 GM 到 Local CUBE 转换和尾部 PadValue。" + }, + "parameters": [ + { + "name": "Layout", + "meaning": { + "en": "ND2M32, ND2M16, or ND2N8.", + "zh-CN": "ND2M32、ND2M16 或 ND2N8。" + } + }, + { + "name": "DataType", + "meaning": { + "en": "Must be DTYPE_NONE so BSTART remains the source dtype.", + "zh-CN": "必须为 DTYPE_NONE,使 BSTART 继续拥有源 dtype。" + } + }, + { + "name": "PadValueOrByteId", + "meaning": { + "en": "Zero, Max, Min, or Null for physical CELL tail positions only.", + "zh-CN": "仅用于物理 CELL 尾部位置的 Zero、Max、Min 或 Null。" + } + } + ] + }, + { + "mnemonic": "B.DIM", + "minOccurrences": 2, + "maxOccurrences": 2, + "repeatable": true, + "requirement": "conditional", + "role": { + "en": "Conditionally required CUBE valid-shape inputs; write LB0 and LB1 exactly once.", + "zh-CN": "CUBE 形态条件必选的有效形状输入;LB0 与 LB1 各写一次。" + }, + "parameters": [ + { + "name": "LB0 / ValidCol", + "meaning": { + "en": "Explicit nonzero valid columns.", + "zh-CN": "显式非零有效列数。" + } + }, + { + "name": "LB1 / ValidRow", + "meaning": { + "en": "Explicit nonzero valid rows.", + "zh-CN": "显式非零有效行数。" + } + }, + { + "name": "LB2 / Col", + "meaning": { + "en": "Forbidden; Layout, DataType, and the valid shape derive CELL geometry.", + "zh-CN": "禁止;CELL 几何由 Layout、DataType 与有效形状推导。" + } + } + ] + }, + { + "mnemonic": "B.IOR", + "minOccurrences": 0, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "optional", + "role": { + "en": "Bind per-PE GM base and byte row stride.", + "zh-CN": "绑定逐 PE 的 GM 基地址与字节行跨度。" + }, + "parameters": [ + { + "name": "RegSrc0", + "meaning": { + "en": "Per-PE GM base byte address.", + "zh-CN": "逐 PE 的 GM 基字节地址。" + }, + "omission": { + "en": "Base address zero.", + "zh-CN": "基地址为零。" + } + }, + { + "name": "RegSrc1", + "meaning": { + "en": "Per-PE byte row stride.", + "zh-CN": "逐 PE 的字节行跨度。" + }, + "omission": { + "en": "Dense byte stride derived from LB0 ValidCol and DataType.", + "zh-CN": "由 LB0 ValidCol 与 DataType 推导紧密字节跨度。" + } + } + ] + }, + { + "mnemonic": "B.IOT", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Allocate one persistent Local CUBE destination and terminate the binding stream.", + "zh-CN": "分配一个持久 Local CUBE 目标并终止绑定流。" + }, + "parameters": [ + { + "name": "DstTile", + "meaning": { + "en": "Destination hand T, U, M, or N.", + "zh-CN": "目标 hand:T、U、M 或 N。" + } + }, + { + "name": "SizeCode", + "meaning": { + "en": "Per-participating-PE destination capacity code.", + "zh-CN": "每个参与 PE 的目标容量码。" + } + }, + { + "name": "PEMode", + "meaning": { + "en": "Three-bit participation mode decoded to the fixed four-PE mask.", + "zh-CN": "三位参与模式,解码为固定四 PE mask。" + } + }, + { + "name": "L", + "meaning": { + "en": "Terminates the Local binding stream.", + "zh-CN": "终止 Local 绑定流。" + } + } + ] + }, + { + "mnemonic": "BSTOP", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Close the block; publish descriptor, payload, definedness, and tail padding only after complete preflight.", + "zh-CN": "关闭 block;仅在完整预检查后发布描述符、payload、definedness 与尾部 padding。" + }, + "parameters": [] + }, + { + "mnemonic": "B.IOS", + "minOccurrences": 0, + "maxOccurrences": 0, + "repeatable": false, + "requirement": "forbidden", + "role": { + "en": "CUBE conversion has no Shared destination form.", + "zh-CN": "CUBE 转换没有 Shared 目标形态。" + }, + "parameters": [] + } + ] + }, + { + "id": "ordinary-shared", + "label": { + "en": "Ordinary Shared destination", + "zh-CN": "普通 Shared 目标" + }, + "summary": { + "en": "Load a complete two-dimensional logical parent into one absolute Core-private Shared register from one participating issuer.", + "zh-CN": "由一个参与 issuer 把完整二维逻辑 parent 载入一个绝对编号的 Core-private Shared 寄存器。" + }, + "canonicalCommandCount": { + "en": "6-command minimum; 8-command complete form", + "zh-CN": "最短 6 条命令;完整形态 8 条命令" + }, + "canonicalMinCommands": 6, + "canonicalMaxCommands": 8, + "minimumSequence": [ + "BSTART.TLOAD FP16", + "B.DIM zero, 32, ->LB0", + "B.DIM zero, 4, ->LB1", + "B.DIM zero, 32, ->LB2", + "B.IOS mask=0001, ->S7<1>", + "BSTOP" + ], + "completeSequence": [ + "BSTART.TLOAD FP16", + "B.DATR {NORM, DTYPE_NONE, Zero, EQ, Default, 0, 0}", + "B.DIM zero, 32, ->LB0", + "B.DIM zero, 4, ->LB1", + "B.DIM zero, 32, ->LB2", + "B.IOR a0, a1", + "B.IOS mask=0001, ->S7<1>", + "BSTOP" + ], + "relationships": [ + { + "en": "B.IOS is the single Shared destination and is mutually exclusive with B.IOT.", + "zh-CN": "B.IOS 是唯一 Shared 目标,并与 B.IOT 互斥。" + }, + { + "en": "All three B.DIM commands are required: LB0 ValidCol, LB1 ValidRow, and LB2 physical Col.", + "zh-CN": "三条 B.DIM 都必选:LB0 ValidCol、LB1 ValidRow 与 LB2 物理 Col。" + }, + { + "en": "Shared TLOAD is ordinary layout only; B.DATR is optional but every padding field must remain zero.", + "zh-CN": "Shared TLOAD 仅支持普通布局;B.DATR 可选,但所有 padding 字段必须保持零。" + }, + { + "en": "This canonical form has one participating issuer and publishes the complete logical parent. Multiple issuers require B.ASSEMBLE with explicit writer ranges.", + "zh-CN": "该 canonical 形态只有一个参与 issuer,并发布完整逻辑 parent。多个 issuer 必须使用带显式 writer range 的 B.ASSEMBLE。" + } + ], + "commands": [ + { + "mnemonic": "BSTART.TLOAD", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Open TLOAD and supply the transfer element type.", + "zh-CN": "打开 TLOAD 并提供传输元素类型。" + }, + "parameters": [ + { + "name": "DataType", + "meaning": { + "en": "Element type for GM access and the Shared descriptor.", + "zh-CN": "用于 GM 访问和 Shared 描述符的元素类型。" + } + } + ] + }, + { + "mnemonic": "B.DATR", + "minOccurrences": 0, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "optional", + "role": { + "en": "Retain ordinary NORM layout; Shared TLOAD cannot select CUBE conversion.", + "zh-CN": "保持普通 NORM 布局;Shared TLOAD 不能选择 CUBE 转换。" + }, + "parameters": [ + { + "name": "Layout", + "meaning": { + "en": "Must remain NORM.", + "zh-CN": "必须保持 NORM。" + }, + "omission": { + "en": "NORM layout.", + "zh-CN": "NORM 布局。" + } + }, + { + "name": "PadValueOrByteId", + "meaning": { + "en": "Must remain zero; Shared TLOAD has no padding control.", + "zh-CN": "必须保持零;Shared TLOAD 没有 padding 控制。" + } + } + ] + }, + { + "mnemonic": "B.DIM", + "minOccurrences": 3, + "maxOccurrences": 3, + "repeatable": true, + "requirement": "required", + "role": { + "en": "Supply every Shared TLOAD dimension explicitly, one write-once LB per command.", + "zh-CN": "显式提供 Shared TLOAD 的全部维度;每条命令写一个只能写一次的 LB。" + }, + "parameters": [ + { + "name": "LB0 / ValidCol", + "meaning": { + "en": "Explicit nonzero valid columns.", + "zh-CN": "显式非零有效列数。" + } + }, + { + "name": "LB1 / ValidRow", + "meaning": { + "en": "Explicit nonzero valid rows.", + "zh-CN": "显式非零有效行数。" + } + }, + { + "name": "LB2 / Col", + "meaning": { + "en": "Explicit nonzero physical columns; ValidCol must not exceed Col.", + "zh-CN": "显式非零物理列数;ValidCol 不得超过 Col。" + } + } + ] + }, + { + "mnemonic": "B.IOR", + "minOccurrences": 0, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "optional", + "role": { + "en": "Bind the participating issuer's two private-GPR address inputs.", + "zh-CN": "绑定参与 issuer 的两个私有 GPR 地址输入。" + }, + "parameters": [ + { + "name": "RegSrc0", + "meaning": { + "en": "The participating issuer's GM base byte address.", + "zh-CN": "参与 issuer 的 GM 基字节地址。" + }, + "omission": { + "en": "Base address zero.", + "zh-CN": "基地址为零。" + } + }, + { + "name": "RegSrc1", + "meaning": { + "en": "The participating issuer's byte row stride.", + "zh-CN": "参与 issuer 的字节行跨度。" + }, + "omission": { + "en": "Dense byte stride ceil(Col * element_bits / 8).", + "zh-CN": "紧密字节跨度 ceil(Col * element_bits / 8)。" + } + } + ] + }, + { + "mnemonic": "B.IOS", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Name the absolute Shared destination and its allocation and participation fields.", + "zh-CN": "指定绝对 Shared 目标及其分配与参与字段。" + }, + "parameters": [ + { + "name": "SharedTileID", + "meaning": { + "en": "Absolute destination S0 through S63.", + "zh-CN": "绝对目标 S0 至 S63。" + } + }, + { + "name": "SizeCode", + "meaning": { + "en": "Capacity code 1..12: 128 B through 256 KiB for the complete Core-wide Shared object.", + "zh-CN": "容量码 1..12:整个 Core-wide Shared 对象为 128 B 至 256 KiB。" + } + }, + { + "name": "PEMode", + "meaning": { + "en": "Three-bit participation mode. This canonical direct form selects exactly one issuer; wider producer masks require B.ASSEMBLE writer ranges.", + "zh-CN": "三位参与模式。该 canonical direct 形态只选择一个 issuer;更宽的 producer mask 必须提供 B.ASSEMBLE writer range。" + } + } + ] + }, + { + "mnemonic": "BSTOP", + "minOccurrences": 1, + "maxOccurrences": 1, + "repeatable": false, + "requirement": "required", + "role": { + "en": "Close the block and atomically publish the complete Shared logical parent after complete preflight.", + "zh-CN": "关闭 block,并在完整预检查后原子发布完整 Shared 逻辑 parent。" + }, + "parameters": [] + }, + { + "mnemonic": "B.IOT", + "minOccurrences": 0, + "maxOccurrences": 0, + "repeatable": false, + "requirement": "forbidden", + "role": { + "en": "Local and Shared destination domains cannot be mixed.", + "zh-CN": "Local 与 Shared 目标域不能混用。" + }, + "parameters": [] + } + ] + } + ] + }, + "semanticExecution": { + "owner": "PTO-TILE-TLOAD", + "stages": [ + { + "id": "bundle", + "label": { + "en": "1. Bundle structure", + "zh-CN": "1. Bundle 结构" + }, + "summary": { + "en": "Validate one Local or one Shared destination, at most one B.IOR, and the dimension-presence rules for the selected form.", + "zh-CN": "验证恰好一个 Local 或 Shared 目标、至多一条 B.IOR,以及所选形态的维度出现规则。" + }, + "sharedRegions": [ + { + "sourcePath": "asl/block/model/dispatch/tile-schema.asl", + "label": { + "en": "B.IOR operand resolution", + "zh-CN": "B.IOR 操作数解析" + }, + "purpose": { + "en": "Resolve RegSrc0 as GM base and RegSrc1 as byte row stride, including omission defaults.", + "zh-CN": "把 RegSrc0 解析为 GM 基地址、RegSrc1 解析为字节行跨度,并处理整条省略缺省。" + }, + "symbols": [ + "BundleTileInstructionOperands" + ] + }, + { + "sourcePath": "asl/block/model/dispatch/destination-shape.asl", + "label": { + "en": "Local destination-shape defaults", + "zh-CN": "Local 目标形状缺省" + }, + "purpose": { + "en": "Resolve Local ValidRow, ValidCol, and physical Col when B.DIM fields are omitted.", + "zh-CN": "在省略 B.DIM 字段时解析 Local ValidRow、ValidCol 与物理 Col。" + }, + "symbols": [ + "BundleDestinationValidRows", + "BundleDestinationValidColumns", + "BundleDestinationPhysicalColumns" + ] + } + ], + "status": "complete", + "gap": null, + "ownerSymbols": [], + "facts": [ + { + "kind": "inputs", + "label": { + "en": "Inputs", + "zh-CN": "输入" + }, + "items": [ + { + "en": "BSTART.TLOAD selects TLSU Function 0 and carries the transfer DataType.", + "zh-CN": "BSTART.TLOAD 选择 TLSU Function 0 并携带传输 DataType。" + }, + { + "en": "Optional B.DATR, B.DIM, and B.IOR add layout/pad, shape, and GM address inputs.", + "zh-CN": "可选 B.DATR、B.DIM、B.IOR 分别增加 layout/pad、形状和 GM 地址输入。" + } + ] + }, + { + "kind": "checks", + "label": { + "en": "Checks", + "zh-CN": "检查" + }, + "items": [ + { + "en": "Exactly one destination domain is selected: Local B.IOT or Shared B.IOS.", + "zh-CN": "恰好选择一个目标域:Local B.IOT 或 Shared B.IOS。" + }, + { + "en": "Each LB register is written at most once and at most one B.IOR is consumed.", + "zh-CN": "每个 LB 最多写一次,并且最多消费一条 B.IOR。" + } + ] + }, + { + "kind": "faults", + "label": { + "en": "Failure", + "zh-CN": "失败" + }, + "items": [ + { + "en": "Malformed, mixed-domain, duplicate, or incomplete bindings reject before the load executes.", + "zh-CN": "畸形、混合目标域、重复或不完整绑定会在载入执行前拒绝。" + } + ] + } + ] + }, + { + "id": "decode", + "label": { + "en": "2. Decode and bind", + "zh-CN": "2. Decode 与绑定" + }, + "summary": { + "en": "Bind the flattened Local destination, GM base, and byte row stride used by the executable Tile handler.", + "zh-CN": "绑定可执行 Tile handler 使用的扁平 Local 目标、GM 基地址与字节行跨度。" + }, + "sharedRegions": [], + "status": "source-gap", + "gap": { + "en": "The current owner exposes instruction selection but no operand-binding Decode procedure.", + "zh-CN": "当前 owner 仅暴露指令选择,没有操作数绑定 Decode 过程。" + }, + "ownerSymbols": [ + "InstructionContractOperation_TLOAD" + ], + "facts": [ + { + "kind": "inputs", + "label": { + "en": "Source binding", + "zh-CN": "源绑定" + }, + "items": [ + { + "en": "The owner selects TileOperation_TLOAD; operand binding remains in the shared bundle dispatcher.", + "zh-CN": "owner 选择 TileOperation_TLOAD;操作数绑定仍由共享 bundle dispatcher 完成。" + } + ] + } + ] + }, + { + "id": "inputs", + "label": { + "en": "3. Derive range and addresses", + "zh-CN": "3. 推导范围与地址" + }, + "summary": { + "en": "Form the ValidRow by ValidCol coordinate range and map every selected coordinate to a byte address, including packed four-bit nibble selection.", + "zh-CN": "形成 ValidRow × ValidCol 坐标范围,并把每个选中坐标映射为字节地址,包括 packed four-bit nibble 选择。" + }, + "sharedRegions": [ + { + "sourcePath": "asl/tile/model/memory/stride.asl", + "label": { + "en": "Byte-strided GM addressing", + "zh-CN": "字节跨度 GM 寻址" + }, + "purpose": { + "en": "Compute dense byte stride and base + row * row_stride_bytes + column byte offset.", + "zh-CN": "计算紧密字节跨度,以及 base + row * row_stride_bytes + column 字节偏移。" + }, + "symbols": [ + "TileDenseRowStrideBytes", + "TileMemoryStridedByteAddress", + "TileMemoryStridedByteHighNibble" + ] + }, + { + "sourcePath": "asl/block/model/dispatch/shared-tlsu.asl", + "label": { + "en": "Shared bundle input resolution", + "zh-CN": "Shared bundle 输入解析" + }, + "purpose": { + "en": "Read per-PE private GPR inputs, explicit Shared dimensions, dtype, layout, destination identity, capacity, and mask.", + "zh-CN": "读取逐 PE 私有 GPR 输入、显式 Shared 维度、dtype、layout、目标身份、容量与 mask。" + }, + "symbols": [ + "ExecuteBundleSharedTLSUOperation" + ] + } + ], + "status": "complete", + "gap": null, + "ownerSymbols": [ + "InstructionContractGMAddress_TLOAD", + "InstructionContractDenseStride_TLOAD" + ], + "facts": [ + { + "kind": "reads", + "label": { + "en": "Read state", + "zh-CN": "读取状态" + }, + "items": [ + { + "en": "B.IOR.RegSrc0 reads each selected PE private GPR as the GM base byte address.", + "zh-CN": "B.IOR.RegSrc0 读取每个选中 PE 的私有 GPR 作为 GM 基字节地址。" + }, + { + "en": "B.IOR.RegSrc1 reads the byte distance between adjacent row starts.", + "zh-CN": "B.IOR.RegSrc1 读取相邻行起点间的字节距离。" + }, + { + "en": "B.DIM resolves ValidCol, ValidRow, and physical Col; BSTART/B.DATR resolve dtype and layout.", + "zh-CN": "B.DIM 解析 ValidCol、ValidRow 和物理 Col;BSTART/B.DATR 解析 dtype 与 layout。" + } + ] + }, + { + "kind": "inputs", + "label": { + "en": "Address calculation", + "zh-CN": "地址计算" + }, + "items": [ + { + "en": "Each selected coordinate uses base + row * row_stride_bytes + the dtype-dependent column byte offset.", + "zh-CN": "每个选中坐标使用 base + row * row_stride_bytes + dtype 相关列字节偏移。" + } + ] + } + ] + }, + { + "id": "legality", + "label": { + "en": "4. Legality", + "zh-CN": "4. 合法性" + }, + "summary": { + "en": "Reject invalid dtype, shape, destination domain, dimension presence, layout direction, and non-CUBE PadValue use before effects.", + "zh-CN": "在产生效果前拒绝非法 dtype、形状、目标域、维度出现方式、layout 方向,以及非 CUBE 的 PadValue 使用。" + }, + "sharedRegions": [ + { + "sourcePath": "asl/block/model/dispatch/tlsu-layout-conversion.asl", + "label": { + "en": "CUBE legality and allocation", + "zh-CN": "CUBE 合法性与分配" + }, + "purpose": { + "en": "Require explicit LB0/LB1, absent LB2, load-direction layout, supported dtype, Local destination, and rollback on failure.", + "zh-CN": "要求显式 LB0/LB1、缺少 LB2、load 方向 layout、受支持 dtype、Local 目标,并在失败时回滚。" + }, + "symbols": [ + "BundleCubeTransportDimensionsLegal", + "BundleCubeTransportDataAttributesLegal", + "BundleCubeTransportBindingsLegal", + "ExecuteBundleCubeTransportOperation" + ] + } + ], + "status": "complete", + "gap": null, + "ownerSymbols": [ + "InstructionContractDataTypeLegal_TLOAD", + "InstructionContractDestinationShapeLegal_TLOAD", + "InstructionContractCubeDimensionsLegal_TLOAD" + ], + "facts": [ + { + "kind": "checks", + "label": { + "en": "Constraints", + "zh-CN": "约束" + }, + "items": [ + { + "en": "ValidRow and ValidCol are nonzero, ValidCol does not exceed physical Col, and capacity contains the resolved shape.", + "zh-CN": "ValidRow 与 ValidCol 非零,ValidCol 不超过物理 Col,容量能够容纳解析后的形状。" + }, + { + "en": "CUBE requires load-direction layout, supported dtype, explicit LB0/LB1, absent LB2, and a Local destination.", + "zh-CN": "CUBE 要求 load 方向 layout、受支持 dtype、显式 LB0/LB1、缺少 LB2 以及 Local 目标。" + }, + { + "en": "PadValue is zero for ordinary Local/Shared and applies only to Local CUBE physical CELL tails.", + "zh-CN": "普通 Local/Shared 的 PadValue 为零;它只用于 Local CUBE 物理 CELL 尾部。" + } + ] + }, + { + "kind": "faults", + "label": { + "en": "Faults", + "zh-CN": "Fault" + }, + "items": [ + { + "en": "Unsupported dtype/layout, invalid dimensions, schema mismatch, or insufficient capacity rejects before publication.", + "zh-CN": "不支持的 dtype/layout、非法维度、schema 不匹配或容量不足会在发布前拒绝。" + } + ] + } + ] + }, + { + "id": "preflight", + "label": { + "en": "5. Complete-footprint preflight", + "zh-CN": "5. 完整 footprint 预检查" + }, + "summary": { + "en": "Probe every selected GM element before any destination payload or Shared record becomes visible.", + "zh-CN": "在任何目标 payload 或 Shared 记录可见之前,探测所有选中的 GM 元素。" + }, + "sharedRegions": [ + { + "sourcePath": "asl/tile/model/memory/load-store.asl", + "label": { + "en": "Local preflight and load", + "zh-CN": "Local 预检查与载入" + }, + "purpose": { + "en": "Probe the full Local footprint, then load and publish the complete destination and CUBE tail PadValue.", + "zh-CN": "探测完整 Local footprint,然后载入并发布完整目标及 CUBE 尾部 PadValue。" + }, + "symbols": [ + "TLOAD" + ] + }, + { + "sourcePath": "asl/tile/model/memory/shared-movement.asl", + "label": { + "en": "Shared preflight and atomic update", + "zh-CN": "Shared 预检查与原子更新" + }, + "purpose": { + "en": "Probe the complete Shared parent footprint for a singleton issuer before atomic publication; multi-issuer assembly uses explicit writer ranges.", + "zh-CN": "单一 issuer 在原子发布前探测完整 Shared parent footprint;多 issuer assembly 使用显式 writer range。" + }, + "symbols": [ + "TLOADShared" + ] + } + ], + "status": "complete", + "gap": null, + "ownerSymbols": [], + "facts": [ + { + "kind": "reads", + "label": { + "en": "Read footprint", + "zh-CN": "读取 footprint" + }, + "items": [ + { + "en": "Every selected ValidRow × ValidCol GM element is translated and permission-checked before payload writes.", + "zh-CN": "每个选中的 ValidRow × ValidCol GM 元素都在 payload 写入前完成地址转换与权限检查。" + } + ] + }, + { + "kind": "faults", + "label": { + "en": "Fault boundary", + "zh-CN": "Fault 边界" + }, + "items": [ + { + "en": "Any access fault leaves the prior Local/Shared destination and bundle-visible state unpublished.", + "zh-CN": "任何访问 fault 都不会发布 Local/Shared 目标或 bundle 可见状态。" + } + ] + } + ] + }, + { + "id": "operation", + "label": { + "en": "6. Execute the load", + "zh-CN": "6. 执行载入" + }, + "summary": { + "en": "Use typed locals for destination shape, dtype, layout, base, and byte stride, then call the shared memory helpers through mnemonic-specific executable wrappers.", + "zh-CN": "使用具名 typed locals 表达目标形状、dtype、layout、base 与字节跨度,再通过 mnemonic-specific 可执行 wrapper 调用共享内存 helper。" + }, + "sharedRegions": [], + "status": "source-gap", + "gap": { + "en": "The current owner binds the generic Tile handler; executable effects remain in the referenced shared modules.", + "zh-CN": "当前 owner 绑定通用 Tile handler;可执行效果位于下方引用的共享模块。" + }, + "ownerSymbols": [ + "InstructionContractHandler_TLOAD" + ], + "facts": [ + { + "kind": "reads", + "label": { + "en": "Reads", + "zh-CN": "读取" + }, + "items": [ + { + "en": "Read GM values for the complete valid rectangle using the resolved dtype, layout, base, and byte stride.", + "zh-CN": "使用解析后的 dtype、layout、base 与字节跨度读取完整有效矩形的 GM 值。" + } + ] + }, + { + "kind": "writes", + "label": { + "en": "State writes", + "zh-CN": "状态写入" + }, + "items": [ + { + "en": "Populate a new Local Tile or the complete logical parent of one absolute Shared Tile; source GPRs and GM are unchanged.", + "zh-CN": "填充一个新 Local Tile 或一个绝对 Shared Tile 的完整逻辑 parent;源 GPR 与 GM 不变。" + } + ] + } + ] + }, + { + "id": "effects", + "label": { + "en": "7. Publish, commit, or no effect", + "zh-CN": "7. 发布、提交或无效果" + }, + "summary": { + "en": "Zero mask has no effect; success publishes only the destination after preflight; fault preserves the prior destination and aborts bundle-local generations.", + "zh-CN": "零 mask 无效果;成功时只在预检查后发布目标;fault 保留先前目标并中止 bundle-local generation。" + }, + "sharedRegions": [ + { + "sourcePath": "asl/block/model/dispatch/tile-execution.asl", + "label": { + "en": "Bundle commit or abort", + "zh-CN": "Bundle 提交或中止" + }, + "purpose": { + "en": "Commit Local generations and retire dependencies only on success; otherwise abort generations.", + "zh-CN": "仅在成功时提交 Local generation 并退役依赖;否则中止 generation。" + }, + "symbols": [ + "ExecuteBundleTileOperationLocallyWithAcceptedApplicabilityRules" + ] + } + ], + "status": "complete", + "gap": null, + "ownerSymbols": [ + "InstructionContractZeroMaskNoEffect_TLOAD" + ], + "facts": [ + { + "kind": "commit", + "label": { + "en": "Commit / result", + "zh-CN": "提交 / 结果" + }, + "items": [ + { + "en": "PE mask 0000 is a strict no-effect path.", + "zh-CN": "PE mask 0000 是严格无效果路径。" + }, + { + "en": "On success the complete Local destination or complete Shared logical parent publishes atomically after preflight.", + "zh-CN": "成功时,完整 Local 目标或完整 Shared 逻辑 parent 在预检查后原子发布。" + }, + { + "en": "CUBE tail PadValue is applied only after all valid GM reads succeed.", + "zh-CN": "CUBE 尾部 PadValue 只在全部有效 GM 读取成功后应用。" + } + ] + } + ] + } + ] + } +} diff --git a/docs/site/docusaurus.config.ts b/docs/site/docusaurus.config.ts index 66efb1c92..a669bc386 100644 --- a/docs/site/docusaurus.config.ts +++ b/docs/site/docusaurus.config.ts @@ -4,10 +4,27 @@ import type {PluginOptions as DocsPluginOptions} from '@docusaurus/plugin-conten import {themes as prismThemes} from 'prism-react-renderer'; import {readFileSync} from 'node:fs'; import ptoRepositoryLinksRemarkPlugin from './plugins/pto-content/remark-repository-links'; +import {legacyReferenceRoute, unitRoute, type UnitRouteInput} from './plugins/pto-content/routes'; +import {registerAslPrism} from './src/theme/aslPrism'; -const redirects = JSON.parse( +registerAslPrism(); + +const checkedRedirects = JSON.parse( readFileSync(new URL('./redirects.json', import.meta.url), 'utf8'), ) as Array<{from: string[]; to: string}>; +const traceability = JSON.parse( + readFileSync( + new URL('../../spec/evidence/release-traceability-readiness.json', import.meta.url), + 'utf8', + ), +) as {units: UnitRouteInput[]}; +const redirects = [ + ...checkedRedirects, + ...traceability.units.map((unit) => ({ + from: [legacyReferenceRoute(unit.documentation)], + to: unitRoute(unit), + })), +]; function sidebarKeySegment(label: string): string { return label @@ -42,7 +59,7 @@ function addStableCategoryKeys( const path = [...ancestors, sidebarKeySegment(category.label)]; return { ...category, - key: category.key ?? `pto-category:${path.join('/')}`, + key: `pto-category:${path.join('/')}`, items: addStableCategoryKeys(category.items, path), } as T; }); @@ -52,8 +69,12 @@ const stableSidebarItemsGenerator: NonNullable< DocsPluginOptions['sidebarItemsGenerator'] > = async ({ defaultSidebarItemsGenerator, + item, ...args -}) => addStableCategoryKeys(await defaultSidebarItemsGenerator(args)); +}) => addStableCategoryKeys( + await defaultSidebarItemsGenerator({item, ...args}), + [sidebarKeySegment(item.dirName)], +); const config: Config = { title: 'PTO Formal Specification', @@ -112,7 +133,15 @@ const config: Config = { path: '..', routeBasePath: 'reference', sidebarPath: './sidebars.ts', - exclude: ['site/**', 'mkdocs/**', 'status/plans/**'], + exclude: [ + 'arch/**', + 'block/**', + 'scalar/**', + 'tile/**', + 'site/**', + 'mkdocs/**', + 'status/plans/**', + ], beforeDefaultRemarkPlugins: [ptoRepositoryLinksRemarkPlugin], sidebarItemsGenerator: stableSidebarItemsGenerator, showLastUpdateAuthor: true, @@ -123,40 +152,32 @@ const config: Config = { themeConfig: { colorMode: { - defaultMode: 'light', - respectPrefersColorScheme: true, + defaultMode: 'dark', + respectPrefersColorScheme: false, }, navbar: { title: 'PTO SPEC', hideOnScroll: false, items: [ { - to: '/units/PTO-ARCH-OVERVIEW-ARCHITECTURE/', - label: 'PTO Architecture', + to: '/architecture/', + label: 'Architecture', position: 'left', }, { - to: '/search/?kind=asl&surface=scalar', - label: 'Scalar', + to: '/instructions/', + label: 'Instructions', position: 'left', }, { - to: '/search/?kind=asl&surface=block', - label: 'Block', + to: '/ndf/', + label: 'NDF', position: 'left', }, - {to: '/search/?kind=asl&surface=tile', label: 'Tile', position: 'left'}, { - type: 'dropdown', - label: 'ADR / NDF', + to: '/reference/governance/adr-process/', + label: 'Decisions', position: 'left', - items: [ - { - to: '/reference/governance/adr-process/', - label: 'ADR process', - }, - {to: '/explore/ndf/', label: 'NDF Explorer'}, - ], }, {to: '/search/', label: 'Search', position: 'left'}, {type: 'localeDropdown', position: 'right'}, @@ -174,15 +195,15 @@ const config: Config = { title: 'Specification', items: [ { - label: 'PTO Architecture', - to: '/units/PTO-ARCH-OVERVIEW-ARCHITECTURE/', + label: 'Architecture', + to: '/architecture/', }, { label: 'Scalar surface', - to: '/search/?kind=asl&surface=scalar', + to: '/instructions/?surface=scalar', }, - {label: 'Block surface', to: '/search/?kind=asl&surface=block'}, - {label: 'Tile surface', to: '/search/?kind=asl&surface=tile'}, + {label: 'Block surface', to: '/instructions/?surface=block'}, + {label: 'Tile surface', to: '/instructions/?surface=tile'}, ], }, { diff --git a/docs/site/i18n/zh-CN/code.json b/docs/site/i18n/zh-CN/code.json index 5b8482a55..955e048c6 100644 --- a/docs/site/i18n/zh-CN/code.json +++ b/docs/site/i18n/zh-CN/code.json @@ -2,7 +2,7 @@ "homepage.release.status": {"message": "最新验证版本"}, "homepage.release.previewStatus": {"message": "未发布的版本预览"}, "homepage.release.scope": {"message": "生产站点仅展示正式发布版"}, - "homepage.hero.title": {"message": "PTO 架构"}, + "homepage.hero.title": {"message": "架构"}, "homepage.hero.subtitle": {"message": "一个正式发布的架构,按照标量、Block 和直接 Tile 指令表面的顺序阅读。"}, "homepage.hero.action": {"message": "从架构所有者开始"}, "homepage.hero.sourceAction": {"message": "打开原始 ASL 源文件"}, @@ -46,5 +46,6 @@ "homepage.search.label": {"message": "搜索 PTO 形式规范"}, "homepage.search.placeholder": {"message": "搜索指令名、NDF ID、unit、测试、字段、引擎、家族或源路径"}, "homepage.search.submit": {"message": "搜索规范"}, - "homepage.search.hint": {"message": "可尝试 ADD、BSTART.TEPL、TLOAD、NDF 标识或精确源路径。"} + "homepage.search.hint": {"message": "可尝试 ADD、BSTART.TEPL、TLOAD、NDF 标识或精确源路径。"}, + "portal.current.home": {"message": "首页"} } diff --git a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current.json b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current.json new file mode 100644 index 000000000..acfaeea6a --- /dev/null +++ b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current.json @@ -0,0 +1,4 @@ +{ + "sidebar.reference.category.Decisions and architecture records": {"message": "Decision 与架构记录"}, + "sidebar.reference.category.Development and releases": {"message": "开发与发布"} +} diff --git a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TCONCAT.md b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TCONCAT.md index 2f66e9297..8548c5486 100644 --- a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TCONCAT.md +++ b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TCONCAT.md @@ -110,7 +110,8 @@ end; BSTART.SFU TCONCAT, DataType B.DATR (optional) B.DIM LB0 -B.DIM (LB1/LB2 for 2D) +B.DIM LB1 (optional) +B.DIM LB2 (optional) B.IOT BSTOP ``` @@ -188,4 +189,4 @@ end; ## Examples -- BSTART.SFU TCONCAT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; BSTOP +- BSTART.SFU TCONCAT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; BSTOP diff --git a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TEXTRACT.md b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TEXTRACT.md index 90583df66..a56a1b91e 100644 --- a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TEXTRACT.md +++ b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TEXTRACT.md @@ -112,7 +112,8 @@ end; BSTART.SFU TEXTRACT, DataType B.DATR (optional) B.DIM LB0 -B.DIM (LB1/LB2 for 2D) +B.DIM LB1 (optional) +B.DIM LB2 (optional) B.IOT B.IOR BSTOP @@ -195,4 +196,4 @@ end; ## Examples -- BSTART.SFU TEXTRACT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; B.IOR; BSTOP +- BSTART.SFU TEXTRACT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; B.IOR; BSTOP diff --git a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TINSERT.md b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TINSERT.md index ed7fc6118..2266288d7 100644 --- a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TINSERT.md +++ b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TINSERT.md @@ -108,7 +108,8 @@ end; BSTART.SFU TINSERT, DataType B.DATR (optional) B.DIM LB0 -B.DIM (LB1/LB2 for 2D) +B.DIM LB1 (optional) +B.DIM LB2 (optional) B.IOT B.IOR BSTOP @@ -200,4 +201,4 @@ end; ## Examples -- BSTART.SFU TINSERT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; B.IOR; BSTOP +- BSTART.SFU TINSERT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; B.IOR; BSTOP diff --git a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TMOV.md b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TMOV.md index 1ff45a700..30e8c1b34 100644 --- a/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TMOV.md +++ b/docs/site/i18n/zh-CN/docusaurus-plugin-content-docs-reference/current/tile/layout-and-rearrangement/layout/TMOV.md @@ -104,7 +104,8 @@ end; ```asm BSTART.TLSU TMOV, DataType B.DIM LB0 -B.DIM (LB1/LB2 for 2D) +B.DIM LB1 (optional) +B.DIM LB2 (optional) B.IOT BSTOP ``` @@ -168,4 +169,4 @@ end; ## Examples -- BSTART.TLSU TMOV, DataType; B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; BSTOP +- BSTART.TLSU TMOV, DataType; B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; BSTOP diff --git a/docs/site/i18n/zh-CN/docusaurus-theme-classic/footer.json b/docs/site/i18n/zh-CN/docusaurus-theme-classic/footer.json index 40cd06846..afc627d9c 100644 --- a/docs/site/i18n/zh-CN/docusaurus-theme-classic/footer.json +++ b/docs/site/i18n/zh-CN/docusaurus-theme-classic/footer.json @@ -2,7 +2,7 @@ "link.title.Specification": {"message": "规范"}, "link.title.Records": {"message": "记录"}, "link.title.Project": {"message": "项目"}, - "link.item.label.PTO Architecture": {"message": "PTO 架构"}, + "link.item.label.Architecture": {"message": "架构"}, "link.item.label.Scalar surface": {"message": "Scalar 标量表面"}, "link.item.label.Block surface": {"message": "Block 表面"}, "link.item.label.Tile surface": {"message": "Tile 表面"}, diff --git a/docs/site/i18n/zh-CN/docusaurus-theme-classic/navbar.json b/docs/site/i18n/zh-CN/docusaurus-theme-classic/navbar.json index e673d5ee8..d7385a408 100644 --- a/docs/site/i18n/zh-CN/docusaurus-theme-classic/navbar.json +++ b/docs/site/i18n/zh-CN/docusaurus-theme-classic/navbar.json @@ -1,10 +1,9 @@ { "title": {"message": "PTO 规范"}, - "item.label.PTO Architecture": {"message": "PTO 架构"}, - "item.label.Scalar": {"message": "标量"}, - "item.label.Block": {"message": "Block"}, - "item.label.Tile": {"message": "Tile"}, - "item.label.ADR / NDF": {"message": "ADR / NDF"}, + "item.label.Architecture": {"message": "架构"}, + "item.label.Instructions": {"message": "指令"}, + "item.label.NDF": {"message": "NDF"}, + "item.label.Decisions": {"message": "Decision"}, "item.label.ADR process": {"message": "ADR 流程"}, "item.label.NDF Explorer": {"message": "NDF 探索器"}, "item.label.Search": {"message": "搜索"}, diff --git a/docs/site/package.json b/docs/site/package.json index 103baff63..ad90d2a21 100644 --- a/docs/site/package.json +++ b/docs/site/package.json @@ -9,7 +9,7 @@ "serve": "docusaurus serve", "clear": "docusaurus clear", "typecheck": "tsc --noEmit", - "test:unit": "node --experimental-strip-types --test plugins/pto-content/remark-repository-links.test.mjs src/components/encodingModel.test.ts", + "test:unit": "node --experimental-strip-types --test plugins/pto-content/remark-repository-links.test.mjs src/components/encodingModel.test.ts src/theme/aslPrism.test.ts", "test:e2e": "playwright test" }, "dependencies": { diff --git a/docs/site/plugins/pto-content/index.ts b/docs/site/plugins/pto-content/index.ts index 9073eeab0..6d7e25acf 100644 --- a/docs/site/plugins/pto-content/index.ts +++ b/docs/site/plugins/pto-content/index.ts @@ -5,6 +5,8 @@ import path from 'node:path'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { PtoAdrRecord, + PtoArchitectureGuide, + PtoArchitectureOwnerProjection, PtoArtifactEvidence, PtoDocumentationIdentity, PtoGraphEdge, @@ -12,10 +14,18 @@ import type { PtoGraphNode, PtoGraphNodeKind, PtoJsonValue, + PtoInstructionComposition, + PtoInstructionIndexData, + PtoHighLevelAssembly, PtoNdfClause, + PtoNdfCatalogData, + PtoNdfDetailData, + PtoNdfOwnerRoute, PtoNdfGraphData, PtoNdfIndexPageData, PtoReleaseIdentity, + PtoSemanticExecution, + PtoSemanticIdentity, PtoReaderGuide, PtoReaderGuideBlock, PtoReaderGuideOwnerLink, @@ -28,6 +38,7 @@ import type { PtoUnitEncoding, PtoUnitWorkbenchData, } from '../../src/types/pto'; +import {legacyReferenceRoute, unitRoute} from './routes'; const TLOAD_ROUTE = '/instructions/tile/memory-and-data-movement/regular/TLOAD/'; @@ -91,8 +102,108 @@ interface LoadedPtoContent { graph: PtoNdfGraphData; search: PtoSearchData; ndfIndexPages: PtoNdfIndexPageData[]; + adrDecisions: Record; + instructionIndex: PtoInstructionIndexData; + ndfCatalog: PtoNdfCatalogData; + ndfDetails: PtoNdfDetailData[]; } +interface ArchitectureTopicDefinition { + id: string; + label: {en: string; 'zh-CN': string}; + primary: string; + boundaryOwner?: string; + related: Array<{id: string; label: {en: string; 'zh-CN': string}}>; +} + +const ARCHITECTURE_ENTRY = 'PTO-ARCH-OVERVIEW-ARCHITECTURE'; + +const ARCHITECTURE_TOPICS: ArchitectureTopicDefinition[] = [ + { + id: 'execution-model', + label: {en: 'Programming and execution model', 'zh-CN': '编程与执行模型'}, + primary: 'PTO-ARCH-DISPATCH-TOP-LEVEL', + boundaryOwner: 'PTO-ARCH-DISPATCH-TOP-LEVEL', + related: [ + {id: 'PTO-ARCH-OVERVIEW-INSTRUCTION-CLASSIFICATION', label: {en: 'Instruction classes and Tile engines', 'zh-CN': '指令分类与 Tile 执行引擎'}}, + {id: 'PTO-ARCH-PROGRAMMING-MODEL-CORE-PE-TOPOLOGY', label: {en: 'Core and PE topology', 'zh-CN': 'Core 与 PE 拓扑'}}, + {id: 'PTO-BLOCK-BSTART', label: {en: 'Block/bundle start and completion boundary', 'zh-CN': 'Block/bundle 开始与完成边界'}}, + {id: 'PTO-TILE-TLOAD', label: {en: 'Source-backed Tile bundle example', 'zh-CN': '源对齐的 Tile bundle 示例'}}, + ], + }, + { + id: 'architectural-state', + label: {en: 'Architectural state', 'zh-CN': '架构状态'}, + primary: 'PTO-ARCH-PROGRAMMING-MODEL-EXECUTION-CONTEXT', + related: [ + {id: 'PTO-ARCH-STATE-DEFINEDNESS', label: {en: 'Definedness boundary', 'zh-CN': 'Definedness 边界'}}, + {id: 'PTO-ARCH-STATE-PROGRAM-COUNTER', label: {en: 'Program-control state', 'zh-CN': '程序控制状态'}}, + {id: 'PTO-ARCH-STATE-TRAP-CONTEXT', label: {en: 'Saved trap context', 'zh-CN': '保存的 trap context'}}, + {id: 'PTO-ARCH-GQM', label: {en: 'General queue state', 'zh-CN': '通用队列状态'}}, + ], + }, + { + id: 'registers', + label: {en: 'Registers and Tile storage', 'zh-CN': '寄存器与 Tile 存储'}, + primary: 'PTO-ARCH-PROGRAMMING-MODEL-CORE-PE-TOPOLOGY', + related: [ + {id: 'PTO-ARCH-PROGRAMMING-MODEL-SCALAR-REGISTERS', label: {en: 'PE-private GPRs', 'zh-CN': 'PE 私有 GPR'}}, + {id: 'PTO-ARCH-PROGRAMMING-MODEL-PREDICATE-REGISTERS', label: {en: 'Predicate registers', 'zh-CN': 'Predicate 寄存器'}}, + {id: 'PTO-ARCH-PROGRAMMING-MODEL-TILE-REGISTERS', label: {en: 'Local Tile registers', 'zh-CN': 'Local Tile 寄存器'}}, + {id: 'PTO-ARCH-PROGRAMMING-MODEL-SHARED-TILE-REGISTERS', label: {en: 'Shared Tile registers', 'zh-CN': 'Shared Tile 寄存器'}}, + {id: 'PTO-ARCH-DATA-TYPES-SYSTEM-REGISTERS', label: {en: 'System-register namespace', 'zh-CN': 'System register 命名空间'}}, + ], + }, + { + id: 'memory-model', + label: {en: 'Memory model', 'zh-CN': '内存模型'}, + primary: 'PTO-ARCH-MEMORY-MODEL-ORDERING', + related: [ + {id: 'PTO-ARCH-MEMORY-MODEL-ADDRESS-SPACE', label: {en: 'Address space', 'zh-CN': '地址空间'}}, + {id: 'PTO-ARCH-MEMORY-MODEL-GLOBAL-MEMORY-ACCESS', label: {en: 'Global Memory access', 'zh-CN': 'Global Memory 访问'}}, + {id: 'PTO-ARCH-MEMORY-MODEL-MEMORY-EVENTS', label: {en: 'Memory events', 'zh-CN': 'Memory event'}}, + {id: 'PTO-ARCH-MEMORY-MODEL-ATOMICITY', label: {en: 'Atomicity and coherence', 'zh-CN': 'Atomicity 与 coherence'}}, + {id: 'PTO-ARCH-MEMORY-MODEL-FAULT-PRECISION', label: {en: 'Memory fault precision', 'zh-CN': '内存 fault 精确性'}}, + ], + }, + { + id: 'types-and-shape', + label: {en: 'Types and shape model', 'zh-CN': '类型与 shape 模型'}, + primary: 'PTO-ARCH-DATA-TYPES-TILE-DATA-TYPES', + boundaryOwner: 'PTO-ARCH-PROGRAMMING-MODEL-TILE-REGISTERS', + related: [ + {id: 'PTO-ARCH-DATA-TYPES-FORMAT-DESCRIPTOR', label: {en: 'Format descriptors', 'zh-CN': 'Format descriptor'}}, + {id: 'PTO-ARCH-STATE-TILE-DESCRIPTOR', label: {en: 'Tile descriptor state', 'zh-CN': 'Tile descriptor 状态'}}, + {id: 'PTO-ARCH-FEATURES-TILE-ALLOCATION', label: {en: 'Tile allocation and capacity', 'zh-CN': 'Tile allocation 与容量'}}, + {id: 'PTO-BLOCK-B-DIM', label: {en: 'B.DIM dimensions', 'zh-CN': 'B.DIM 维度'}}, + {id: 'PTO-TILE-TLOAD', label: {en: 'TLOAD shape consumer', 'zh-CN': 'TLOAD shape 消费者'}}, + ], + }, + { + id: 'faults', + label: {en: 'Faults, exceptions, and diagnostics', 'zh-CN': 'Fault、exception 与 diagnostic'}, + primary: 'PTO-ARCH-MEMORY-MODEL-FAULT-PRECISION', + boundaryOwner: 'PTO-ARCH-DATA-TYPES-FAULT', + related: [ + {id: 'PTO-ARCH-DATA-TYPES-FAULT', label: {en: 'Fault identities', 'zh-CN': 'Fault 标识'}}, + {id: 'PTO-ARCH-STATE-TRAP-CONTEXT', label: {en: 'Trap context state', 'zh-CN': 'Trap context 状态'}}, + {id: 'PTO-ARCH-PROFILE-TRAP-CONTEXT-RECOVERY', label: {en: 'Trap recovery profile path', 'zh-CN': 'Trap recovery profile 路径'}}, + ], + }, + { + id: 'versioning', + label: {en: 'Version and compatibility', 'zh-CN': '版本与兼容性'}, + primary: 'PTO-ARCH-OVERVIEW-ARCHITECTURE', + boundaryOwner: 'PTO-ARCH-PROFILE-APPLICABILITY', + related: [ + {id: 'PTO-ARCH-PROFILE-APPLICABILITY', label: {en: 'Profile applicability', 'zh-CN': 'Profile 适用性'}}, + {id: 'PTO-ARCH-PROFILE-REFERENCE-PROFILE', label: {en: 'PTO v0 reference profile', 'zh-CN': 'PTO v0 reference profile'}}, + {id: 'PTO-ARCH-PROFILE-EXTENSION-FIRST-USE', label: {en: 'Extension first-use policy', 'zh-CN': '扩展首次使用策略'}}, + {id: 'PTO-ARCH-OVERVIEW-INSTRUCTION-CLASSIFICATION', label: {en: 'Compatibility aliases', 'zh-CN': '兼容 alias'}}, + ], + }, +]; + interface ReaderPresentationBlock { block_id: string; role: string; @@ -337,12 +448,7 @@ export function isCommonMarkFenceClose(line: string, opener: string): boolean { } function referenceRoute(documentationPath: string, locale: string, defaultLocale: string): string { - if (!documentationPath.startsWith('docs/') || !documentationPath.endsWith('.md')) { - fail(`cannot derive reference route for ${documentationPath}`); - } - let relative = documentationPath.slice('docs/'.length, -'.md'.length); - if (relative.endsWith('/index')) relative = relative.slice(0, -'/index'.length); - const route = `/reference/${relative}/`.replace(/\/+/g, '/'); + const route = legacyReferenceRoute(documentationPath); return locale === defaultLocale ? route : `/${locale}${route}`; } @@ -598,6 +704,88 @@ function parseReaderGuide( return {blocks, sha256: sha256(body)}; } +function decisionSection(markdown: string, sourcePath: string): string { + const normalized = markdown.replace(/\r\n?/g, '\n'); + const lines = normalized.split('\n'); + const separators = lines.flatMap((line, index) => line === '---' ? [index] : []); + if (separators.length < 2) fail(`${sourcePath} has no complete ADR frontmatter`); + const body = lines.slice(separators[1] + 1); + const heading = body.findIndex((line) => /^#\s+/.test(line)); + if (heading < 0) fail(`${sourcePath} has no ADR title heading`); + return body.slice(heading + 1).join('\n').trim(); +} + +function parseDecisionNodes( + root: string, + release: PtoReleaseIdentity, + sourcePath: string, + markdown: string, +): PtoReaderNode[] { + const body = decisionSection(markdown, sourcePath); + const lines = body.split('\n'); + const nodes: PtoReaderNode[] = []; + let paragraph: string[] = []; + let fence: string[] | null = null; + let fenceToken = ''; + let fenceLanguage: string | null = null; + const inline = (value: string): PtoReaderInline[] => + parseReaderInline(root, release.commit, sourcePath, value, 'en', 'en'); + const flushParagraph = (): void => { + if (paragraph.length === 0) return; + nodes.push({kind: 'paragraph', children: inline(paragraph.join(' '))}); + paragraph = []; + }; + + for (const line of [...lines, '']) { + if (fence !== null) { + if (isCommonMarkFenceClose(line, fenceToken)) { + nodes.push({kind: 'code-block', language: fenceLanguage, text: fence.join('\n')}); + fence = null; + fenceToken = ''; + fenceLanguage = null; + } else { + fence.push(line); + } + continue; + } + if (/^(?:`{3,}|~{3,})/.test(line.trim())) { + flushParagraph(); + const opening = line.trim().match(/^(`{3,}|~{3,})(.*)$/); + if (opening === null) fail(`malformed Decision code fence in ${sourcePath}`); + fenceToken = opening[1]; + fenceLanguage = opening[2].trim() || null; + fence = []; + } else if (/^\s*(?:[-*+] |\d+[.)] )/.test(line)) { + flushParagraph(); + const ordered = /^\s*\d+[.)] /.test(line); + const text = line.replace(/^\s*(?:[-*+] |\d+[.)] )/, ''); + nodes.push({kind: 'list-item', ordered, children: inline(text)}); + } else if (line.trimStart().startsWith('|') && line.trimEnd().endsWith('|')) { + flushParagraph(); + if (!/^[\s|:=-]+$/.test(line)) { + const cells = line.trim().replace(/^\||\|$/g, '').split('|').map((cell) => inline(cell.trim())); + nodes.push({kind: 'table-row', cells}); + } + } else if (!line.trim()) { + flushParagraph(); + } else if (line.trimStart().startsWith('#')) { + flushParagraph(); + const heading = line.match(/^\s*(#{2,6})\s+(.+)$/); + if (heading === null) fail(`malformed Decision heading in ${sourcePath}`); + nodes.push({kind: 'heading', level: heading[1].length, children: inline(heading[2])}); + } else if (line.trimStart().startsWith('')) fail(`unterminated Decision comment in ${sourcePath}`); + } else { + const renderedSyntax = renderedReaderSyntax(line); + if (/<\/?[A-Za-z][^>]*>/.test(renderedSyntax)) fail(`raw HTML is forbidden in ${sourcePath}`); + paragraph.push(line); + } + } + if (fence !== null) fail(`unterminated Decision code fence in ${sourcePath}`); + return nodes; +} + function approvedReaderGuides(root: string): { acceptedEnglishUnits: Set; acceptedTranslations: Map; @@ -847,7 +1035,466 @@ function parseUnitMetadata( } } -function parseNdfClauses(source: string, sourcePath: string): PtoNdfClause[] { +interface SiteInstructionProjection { + schema: string; + unitId: string; + ownerSource: string; + ownerSourceSha256: string; + composition?: unknown; + semanticExecution?: unknown; +} + +function loadSiteInstructionProjection( + root: string, + unit: TraceabilityUnit, + ownerSourceText: string, +): {path: string; data: SiteInstructionProjection} | null { + const projectionPath = `docs/site/data/instruction-projections/${unit.id}.json`; + if (!existsSync(path.join(root, projectionPath))) return null; + const data = readJson(root, projectionPath); + if ( + data.schema !== 'pto.site-instruction-projection.v1' || + data.unitId !== unit.id || + data.ownerSource !== unit.source || + data.ownerSourceSha256 !== sha256(ownerSourceText) + ) { + fail(`${projectionPath} is stale or bound to the wrong owner`); + } + return {path: projectionPath, data}; +} + +function parseInstructionComposition( + value: unknown, + sourcePath: string, + unitId: string, + traceability: TraceabilityData, + release: PtoReleaseIdentity, +): PtoInstructionComposition | null { + if (value === undefined || value === null) return null; + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + fail(`${sourcePath} PTO-PAGE-COMPOSITION must be an object`); + } + const composition = value as PtoInstructionComposition; + if (composition.owner !== unitId || !Array.isArray(composition.variants) || composition.variants.length === 0) { + fail(`${sourcePath} PTO-PAGE-COMPOSITION owner or variants are invalid`); + } + const localized = (candidate: unknown, field: string): void => { + if ( + candidate === null || + typeof candidate !== 'object' || + Array.isArray(candidate) || + typeof (candidate as Record).en !== 'string' || + typeof (candidate as Record)['zh-CN'] !== 'string' || + !(candidate as Record).en.trim() || + !(candidate as Record)['zh-CN'].trim() + ) { + fail(`${sourcePath} PTO-PAGE-COMPOSITION ${field} must contain en and zh-CN`); + } + }; + const commandUnits = new Map( + traceability.units + .filter((candidate) => candidate.mnemonic !== null) + .map((candidate) => [candidate.mnemonic!, candidate]), + ); + for (const variant of composition.variants) { + if ( + typeof variant.id !== 'string' || + !Number.isInteger(variant.canonicalMinCommands) || + !Number.isInteger(variant.canonicalMaxCommands) || + variant.canonicalMinCommands < 1 || + variant.canonicalMaxCommands < variant.canonicalMinCommands || + !Array.isArray(variant.minimumSequence) || + variant.minimumSequence.length !== variant.canonicalMinCommands || + !Array.isArray(variant.completeSequence) || + variant.completeSequence.length !== variant.canonicalMaxCommands || + !Array.isArray(variant.relationships) || + !Array.isArray(variant.commands) || + variant.commands.length === 0 + ) { + fail(`${sourcePath} has an invalid PTO-PAGE-COMPOSITION variant`); + } + localized(variant.label, `${variant.id}.label`); + localized(variant.summary, `${variant.id}.summary`); + localized(variant.canonicalCommandCount, `${variant.id}.canonicalCommandCount`); + variant.relationships.forEach((relationship, index) => + localized(relationship, `${variant.id}.relationships[${index}]`)); + for (const command of variant.commands) { + if ( + typeof command.mnemonic !== 'string' || + !Number.isInteger(command.minOccurrences) || + !Number.isInteger(command.maxOccurrences) || + command.minOccurrences < 0 || + command.maxOccurrences < command.minOccurrences || + typeof command.repeatable !== 'boolean' || + command.repeatable !== (command.maxOccurrences > 1) || + !['required', 'optional', 'conditional', 'forbidden'].includes(command.requirement) || + !Array.isArray(command.parameters) + ) { + fail(`${sourcePath} has an invalid PTO-PAGE-COMPOSITION command`); + } + localized(command.role, `${variant.id}.${command.mnemonic}.role`); + for (const [index, parameter] of command.parameters.entries()) { + if (typeof parameter.name !== 'string' || !parameter.name.trim()) { + fail(`${sourcePath} has an invalid ${variant.id}.${command.mnemonic} parameter`); + } + localized(parameter.meaning, `${variant.id}.${command.mnemonic}.parameters[${index}].meaning`); + if (parameter.omission !== undefined) { + localized(parameter.omission, `${variant.id}.${command.mnemonic}.parameters[${index}].omission`); + } + } + const referenceUnit = commandUnits.get(command.mnemonic); + if (referenceUnit === undefined) { + fail(`${sourcePath} composition command has no owning unit: ${command.mnemonic}`); + } + command.reference = { + id: referenceUnit.id, + route: unitRoute(referenceUnit), + sourcePath: referenceUnit.source, + sourceUrl: sourceUrl(release.commit, referenceUnit.source), + }; + } + const sequenceLines = [...variant.minimumSequence, ...variant.completeSequence]; + for (const line of sequenceLines) { + if ( + typeof line !== 'string' || + !variant.commands.some((command) => + line === command.mnemonic || line.startsWith(`${command.mnemonic} `)) + ) { + fail(`${sourcePath} has an unowned ${variant.id} sequence line: ${String(line)}`); + } + } + } + return composition; +} + +function parseSemanticExecution( + root: string, + release: PtoReleaseIdentity, + source: string, + sourcePath: string, + unitId: string, +): unknown { + const matches = [...source.matchAll( + /^\/\/ PTO-PAGE-SEMANTICS-BEGIN\s*$\n([\s\S]*?)^\/\/ PTO-PAGE-SEMANTICS-END\s*$/gm, + )]; + if (matches.length === 0) return null; + if (matches.length !== 1) fail(`${sourcePath} has duplicate PTO-PAGE-SEMANTICS metadata`); + let raw: unknown; + try { + raw = JSON.parse( + matches[0][1] + .split(/\r?\n/) + .map((line) => line.replace(/^\/\/(?: )?/, '')) + .join('\n'), + ); + } catch (error) { + fail(`invalid PTO-PAGE-SEMANTICS metadata in ${sourcePath}: ${String(error)}`); + } + if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) { + fail(`${sourcePath} PTO-PAGE-SEMANTICS must be an object`); + } + const metadata = raw as Record; + if (metadata.owner !== unitId || !Array.isArray(metadata.stages) || metadata.stages.length === 0) { + fail(`${sourcePath} PTO-PAGE-SEMANTICS owner or stages are invalid`); + } + const localized = (candidate: unknown, field: string): {en: string; 'zh-CN': string} => { + if ( + candidate === null || + typeof candidate !== 'object' || + Array.isArray(candidate) || + typeof (candidate as Record).en !== 'string' || + typeof (candidate as Record)['zh-CN'] !== 'string' + ) { + fail(`${sourcePath} PTO-PAGE-SEMANTICS ${field} must contain en and zh-CN`); + } + return candidate as {en: string; 'zh-CN': string}; + }; + const region = ( + exactSourcePath: string, + exactSource: string, + regionName: string, + marker: 'DOC' | 'PTO-SITE-REGION', + id: string, + label: {en: string; 'zh-CN': string}, + purpose: {en: string; 'zh-CN': string}, + ) => { + if (!exactSourcePath.startsWith('asl/')) { + fail(`${sourcePath} semantic region escapes ASL owners: ${exactSourcePath}`); + } + const begin = `// ${marker}-BEGIN: ${regionName}`; + const end = `// ${marker}-END: ${regionName}`; + const lines = exactSource.replace(/\r\n?/g, '\n').split('\n'); + const starts = lines.flatMap((line, index) => line.trim() === begin ? [index] : []); + const finishes = lines.flatMap((line, index) => line.trim() === end ? [index] : []); + if (starts.length !== 1 || finishes.length !== 1 || finishes[0] <= starts[0]) { + fail(`${exactSourcePath} must contain one ordered ${marker} region ${regionName}`); + } + const text = lines.slice(starts[0] + 1, finishes[0]).join('\n'); + return { + id, + label, + purpose, + sourcePath: exactSourcePath, + sourceUrl: sourceUrl(release.commit, exactSourcePath, starts[0] + 2, finishes[0]), + sourceSha256: sha256(exactSource), + fragmentSha256: sha256(text), + startLine: starts[0] + 2, + endLine: finishes[0], + text, + }; + }; + + return { + owner: unitId, + stages: metadata.stages.map((stageValue, stageIndex) => { + if (stageValue === null || typeof stageValue !== 'object' || Array.isArray(stageValue)) { + fail(`${sourcePath} has invalid semantic stage ${stageIndex}`); + } + const stage = stageValue as Record; + if ( + typeof stage.id !== 'string' || + typeof stage.ownerRegion !== 'string' || + !Array.isArray(stage.sharedRegions) + ) { + fail(`${sourcePath} has invalid semantic stage ${stageIndex}`); + } + const label = localized(stage.label, `${stage.id}.label`); + const summary = localized(stage.summary, `${stage.id}.summary`); + return { + id: stage.id, + label, + summary, + ownerRegion: region( + sourcePath, + source, + stage.ownerRegion, + 'DOC', + `${stage.id}-owner`, + label, + summary, + ), + sharedRegions: stage.sharedRegions.map((sharedValue, sharedIndex) => { + if (sharedValue === null || typeof sharedValue !== 'object' || Array.isArray(sharedValue)) { + fail(`${sourcePath} has invalid ${stage.id} shared region ${sharedIndex}`); + } + const shared = sharedValue as Record; + if (typeof shared.sourcePath !== 'string' || typeof shared.region !== 'string') { + fail(`${sourcePath} has invalid ${stage.id} shared region ${sharedIndex}`); + } + const sharedSource = readText(root, shared.sourcePath); + return region( + shared.sourcePath, + sharedSource, + shared.region, + 'PTO-SITE-REGION', + `${stage.id}-shared-${sharedIndex + 1}`, + localized(shared.label, `${stage.id}.shared[${sharedIndex}].label`), + localized(shared.purpose, `${stage.id}.shared[${sharedIndex}].purpose`), + ); + }), + }; + }), + }; +} + +function extractAslFunctionRegion( + release: PtoReleaseIdentity, + sourcePath: string, + sourceText: string, + symbol: string, + id: string, + label: {en: string; 'zh-CN': string}, + purpose: {en: string; 'zh-CN': string}, +) { + const lines = sourceText.replace(/\r\n?/g, '\n').split('\n'); + const declaration = new RegExp(`^(?:(?:pure|readonly|impdef) )?func ${symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`); + const starts = lines.flatMap((line, index) => declaration.test(line) ? [index] : []); + if (starts.length !== 1) { + fail(`${sourcePath} must define exactly one top-level function ${symbol}`); + } + const nextFunction = /^(?:(?:pure|readonly|impdef) )?func [A-Za-z_][A-Za-z0-9_]*\b/; + let end = lines.findIndex((line, index) => index > starts[0] && nextFunction.test(line)); + if (end < 0) end = lines.length; + while (end > starts[0] + 1 && !lines[end - 1].trim()) end -= 1; + const text = lines.slice(starts[0], end).join('\n'); + return { + id, + label, + purpose, + sourcePath, + sourceUrl: sourceUrl(release.commit, sourcePath, starts[0] + 1, end), + sourceSha256: sha256(sourceText), + fragmentSha256: sha256(text), + startLine: starts[0] + 1, + endLine: end, + text, + }; +} + +function parseSemanticExecutionProjection( + root: string, + release: PtoReleaseIdentity, + raw: unknown, + projectionPath: string, + unit: TraceabilityUnit, + ownerSourceText: string, +): PtoSemanticExecution | null { + if (raw === undefined || raw === null) return null; + if (typeof raw !== 'object' || Array.isArray(raw)) { + fail(`${projectionPath} semanticExecution must be an object`); + } + const metadata = raw as Record; + if (metadata.owner !== unit.id || !Array.isArray(metadata.stages) || metadata.stages.length === 0) { + fail(`${projectionPath} semanticExecution owner or stages are invalid`); + } + const localized = (candidate: unknown, field: string): {en: string; 'zh-CN': string} => { + if ( + candidate === null || typeof candidate !== 'object' || Array.isArray(candidate) || + typeof (candidate as Record).en !== 'string' || + typeof (candidate as Record)['zh-CN'] !== 'string' + ) fail(`${projectionPath} ${field} must contain en and zh-CN`); + return candidate as {en: string; 'zh-CN': string}; + }; + const combineRegions = ( + regions: ReturnType[], + id: string, + label: {en: string; 'zh-CN': string}, + purpose: {en: string; 'zh-CN': string}, + ) => { + if (regions.length === 0) fail(`${projectionPath} region ${id} has no symbols`); + const sourcePath = regions[0].sourcePath; + if (!regions.every((region) => + region.sourcePath === sourcePath && + region.sourceSha256 === regions[0].sourceSha256)) { + fail(`${projectionPath} region ${id} mixes source owners`); + } + const text = regions.map((region) => region.text).join('\n\n'); + const startLine = Math.min(...regions.map((region) => region.startLine)); + const endLine = Math.max(...regions.map((region) => region.endLine)); + return { + id, + label, + purpose, + sourcePath, + sourceUrl: sourceUrl(release.commit, sourcePath, startLine, endLine), + sourceSha256: regions[0].sourceSha256, + fragmentSha256: sha256(text), + startLine, + endLine, + text, + }; + }; + return { + owner: unit.id, + stages: metadata.stages.map((stageValue, stageIndex) => { + if (stageValue === null || typeof stageValue !== 'object' || Array.isArray(stageValue)) { + fail(`${projectionPath} has invalid semantic stage ${stageIndex}`); + } + const stage = stageValue as Record; + if ( + typeof stage.id !== 'string' || + !['complete', 'source-gap'].includes(String(stage.status)) || + !Array.isArray(stage.facts) || + !Array.isArray(stage.ownerSymbols) || + !Array.isArray(stage.sharedRegions) + ) fail(`${projectionPath} has invalid semantic stage ${stageIndex}`); + const label = localized(stage.label, `${stage.id}.label`); + const summary = localized(stage.summary, `${stage.id}.summary`); + const status = stage.status as 'complete' | 'source-gap'; + const gap = stage.gap === null || stage.gap === undefined + ? undefined + : localized(stage.gap, `${stage.id}.gap`); + if ((status === 'source-gap') !== (gap !== undefined)) { + fail(`${projectionPath} ${stage.id} gap status is inconsistent`); + } + const facts = stage.facts.map((factValue, factIndex) => { + if (factValue === null || typeof factValue !== 'object' || Array.isArray(factValue)) { + fail(`${projectionPath} has invalid ${stage.id} fact ${factIndex}`); + } + const fact = factValue as Record; + if ( + !['inputs', 'checks', 'faults', 'reads', 'writes', 'commit'].includes(String(fact.kind)) || + !Array.isArray(fact.items) || fact.items.length === 0 + ) fail(`${projectionPath} has invalid ${stage.id} fact ${factIndex}`); + return { + kind: fact.kind as 'inputs' | 'checks' | 'faults' | 'reads' | 'writes' | 'commit', + label: localized(fact.label, `${stage.id}.facts[${factIndex}].label`), + items: fact.items.map((item, itemIndex) => + localized(item, `${stage.id}.facts[${factIndex}].items[${itemIndex}]`)), + }; + }); + const ownerSymbols = stage.ownerSymbols.map((symbol) => { + if (typeof symbol !== 'string') fail(`${projectionPath} has invalid owner symbol`); + return extractAslFunctionRegion( + release, unit.source, ownerSourceText, symbol, + `${stage.id}-owner-symbol-${symbol}`, label, summary, + ); + }); + const ownerRegions = ownerSymbols.length === 0 ? [] : [ + combineRegions(ownerSymbols, `${stage.id}-owner`, label, summary), + ]; + const sharedRegions = stage.sharedRegions.map((sharedValue, sharedIndex) => { + if (sharedValue === null || typeof sharedValue !== 'object' || Array.isArray(sharedValue)) { + fail(`${projectionPath} has invalid shared region`); + } + const shared = sharedValue as Record; + if (typeof shared.sourcePath !== 'string' || !shared.sourcePath.startsWith('asl/') || !Array.isArray(shared.symbols)) { + fail(`${projectionPath} shared source is invalid or outside ASL`); + } + const sharedSource = readText(root, shared.sourcePath); + const sharedLabel = localized(shared.label, `${stage.id}.shared[${sharedIndex}].label`); + const purpose = localized(shared.purpose, `${stage.id}.shared[${sharedIndex}].purpose`); + const regions = shared.symbols.map((symbol) => { + if (typeof symbol !== 'string') fail(`${projectionPath} has invalid shared symbol`); + return extractAslFunctionRegion( + release, shared.sourcePath as string, sharedSource, symbol, + `${stage.id}-shared-symbol-${sharedIndex + 1}-${symbol}`, + sharedLabel, purpose, + ); + }); + return combineRegions( + regions, + `${stage.id}-shared-${sharedIndex + 1}`, + sharedLabel, + purpose, + ); + }); + if (ownerRegions.length === 0 && sharedRegions.length === 0) { + fail(`${projectionPath} stage ${stage.id} has no exact source region`); + } + return {id: stage.id, label, summary, status, ...(gap ? {gap} : {}), facts, ownerRegions, sharedRegions}; + }), + }; +} + +function ndfIdentity(id: string, unit?: TraceabilityUnit): PtoSemanticIdentity { + const facets: PtoSemanticIdentity['facets'] = []; + if (unit !== undefined) { + facets.push({role: 'surface', label: unit.surface.toLocaleUpperCase('en-US')}); + facets.push({role: 'owner', label: unit.mnemonic ?? unit.id}); + const ownerSlug = (unit.mnemonic ?? unit.id) + .toLocaleUpperCase('en-US') + .replace(/[^A-Z0-9]+/g, '-'); + const instructionPrefix = `PTO-INST-${unit.surface.toLocaleUpperCase('en-US')}-${ownerSlug}`; + const ownerPrefix = `PTO-${ownerSlug}-`; + let remainder = id === instructionPrefix ? 'INSTRUCTION' : + id.startsWith(ownerPrefix) ? id.slice(ownerPrefix.length) : id.slice('PTO-'.length); + const caseMatch = remainder.match(/-(\d{3})$/); + if (caseMatch !== null) remainder = remainder.slice(0, -caseMatch[0].length); + facets.push({role: 'category', label: remainder}); + if (caseMatch !== null) facets.push({role: 'case', label: caseMatch[1]}); + } else { + facets.push({role: 'category', label: id.slice('PTO-'.length)}); + } + return {fullId: id, kind: 'ndf', anchor: `ndf-${id.toLocaleLowerCase('en-US')}`, facets}; +} + +function parseNdfClauses( + source: string, + sourcePath: string, + release?: PtoReleaseIdentity, + unit?: TraceabilityUnit, +): PtoNdfClause[] { const lines = source.split(/\r?\n/); const clauses: PtoNdfClause[] = []; @@ -881,16 +1528,24 @@ function parseNdfClauses(source: string, sourcePath: string): PtoNdfClause[] { fail(`unterminated NDF clause ${id}`); } + const text = body.join('\n'); clauses.push({ id, kind: metadata[1], level: metadata[2], layer: metadata[3], status: metadata[4], - text: body.join('\n'), + text, sourcePath, startLine: index + 1, endLine: endIndex + 1, + sourceSha256: sha256(source), + clauseSha256: sha256(text), + githubUrl: release === undefined + ? '' + : sourceUrl(release.commit, sourcePath, index + 1, endIndex + 1), + affectedUnits: unit === undefined ? [] : [unit.id], + identity: ndfIdentity(id, unit), }); index = endIndex; } @@ -989,6 +1644,7 @@ function instructionTests( traceability: TraceabilityData, testIds: Set, ): PtoTestEvidence[] { + const unitBySource = new Map(traceability.units.map((unit) => [unit.source, unit])); return traceability.tests .filter((test) => testIds.has(test.id)) .sort((left, right) => left.id.localeCompare(right.id)) @@ -1008,6 +1664,21 @@ function instructionTests( if (stringValue(metadata, 'source') !== test.source) { fail(`test source mismatch for ${test.path}`); } + const owner = unitBySource.get(test.source); + if (owner === undefined) fail(`test source has no owning unit: ${test.source}`); + const caseMatch = test.id.match(/-(\d{3})$/); + if (caseMatch === null) fail(`AVS identity has no case index: ${test.id}`); + const identity: PtoSemanticIdentity = { + fullId: test.id, + kind: 'avs', + anchor: `avs-${test.id.toLocaleLowerCase('en-US')}`, + facets: [ + {role: 'surface', label: owner.surface.toLocaleUpperCase('en-US')}, + {role: 'owner', label: owner.mnemonic ?? owner.id}, + {role: 'category', label: test.kind.toLocaleUpperCase('en-US')}, + {role: 'case', label: caseMatch[1]}, + ], + }; return { ...test, summary: stringValue(metadata, 'summary'), @@ -1015,6 +1686,10 @@ function instructionTests( sourceText, sourceAssetUrl: `/evidence/test-sources/${digest}.asl`, githubUrl: sourceUrl(commit, test.path), + identity, + ownerId: owner.id, + ownerMnemonic: owner.mnemonic, + surface: owner.surface, }; }); } @@ -1025,18 +1700,456 @@ interface PtoCatalogs { tileOperations: Record[]; } -function unitRoute(unit: TraceabilityUnit): string { - if (unit.mnemonic === null) { - return `/units/${encodeURIComponent(unit.id)}/`; +function architectureGuide( + context: LoadContext, + release: PtoReleaseIdentity, + units: Array<{data: PtoUnitWorkbenchData; route: string}>, +): PtoArchitectureGuide { + const locale = context.i18n.currentLocale; + const chinese = locale === 'zh-CN'; + const unitsById = new Map( + units.map((unit) => [String(unit.data.unit.id ?? unit.data.metadata.id), unit]), + ); + const owner = ( + id: string, + label: string, + includeGuide: boolean, + ): PtoArchitectureOwnerProjection => { + const unit = unitsById.get(id); + if (unit === undefined) fail(`architecture landing references missing unit ${id}`); + const guide = unit.data.readerGuide; + if (guide.blocks.length === 0) { + fail(`architecture landing owner has no reviewed reader guide: ${id}`); + } + if (locale !== context.i18n.defaultLocale && guide.contentLocale !== locale) { + fail(`architecture landing owner lacks ${locale} projection: ${id}`); + } + if (guide.sha256 === null) { + fail(`architecture landing owner has no reviewed guide hash: ${id}`); + } + const blocks = includeGuide + ? guide.blocks.filter((block) => block.role !== 'example-usage') + : []; + if (includeGuide && blocks.length === 0) { + fail(`architecture landing owner has no reader-facing contract blocks: ${id}`); + } + return { + id, + label, + route: localizedRoute(context, unit.route), + referenceRoute: unit.data.documentation.referenceRoute, + sourcePath: unit.data.source.path, + sourceSha256: unit.data.source.sha256, + sourceUrl: unit.data.source.githubUrl, + guideStatus: guide.status, + guideSha256: guide.sha256, + contentLocale: guide.contentLocale, + blocks, + }; + }; + const boundBlock = ( + id: string, + role: PtoReaderGuideRole, + ) => { + const unit = unitsById.get(id); + if (unit === undefined) fail(`architecture block binding references missing unit ${id}`); + const guide = unit.data.readerGuide; + if (guide.sha256 === null) fail(`architecture block binding has no guide hash: ${id}`); + const matches = guide.blocks.filter((block) => block.role === role); + if (matches.length !== 1) { + fail(`architecture block binding ${id}/${role} resolves ${matches.length} blocks`); + } + return { + ownerId: id, + sourcePath: unit.data.source.path, + sourceSha256: unit.data.source.sha256, + guideSha256: guide.sha256, + block: matches[0], + }; + }; + const entry = owner( + ARCHITECTURE_ENTRY, + chinese ? '架构所有者' : 'Architecture owner', + true, + ); + return { + schema: 'pto.site-architecture-guide.v1', + locale, + release, + entry, + topics: ARCHITECTURE_TOPICS.map((topic) => ({ + id: topic.id, + label: chinese ? topic.label['zh-CN'] : topic.label.en, + scenario: boundBlock(topic.primary, 'example-usage'), + sourceBoundary: topic.boundaryOwner === undefined + ? null + : boundBlock(topic.boundaryOwner, 'boundaries'), + primary: owner( + topic.primary, + chinese ? topic.label['zh-CN'] : topic.label.en, + true, + ), + related: topic.related.map((related) => owner( + related.id, + chinese ? related.label['zh-CN'] : related.label.en, + false, + )), + })), + }; +} + +function highLevelAssembly( + unit: TraceabilityUnit, + metadata: Record, +): PtoHighLevelAssembly | null { + if (unit.surface !== 'tile' || unit.mnemonic === null) return null; + const contractValue = metadata.contract; + if (contractValue === null || typeof contractValue !== 'object' || Array.isArray(contractValue)) { + fail(`${unit.id} has no contract for high-level Tile assembly`); + } + const contract = contractValue as Record; + const contractBlockLines = (Array.isArray(contract.block_composition) ? contract.block_composition : []) + .filter((value): value is string => typeof value === 'string'); + const metadataBlockLines = (Array.isArray(metadata.block) ? metadata.block : []) + .filter((value): value is string => typeof value === 'string'); + const blockLines = [...new Set([...contractBlockLines, ...metadataBlockLines])]; + if (blockLines.length === 0) fail(`${unit.id} has no block composition for high-level Tile assembly`); + const parameters: PtoHighLevelAssembly['parameters'] = []; + const seenParameters = new Set(); + const addParameter = (display: string, source: string, role: string): void => { + if (seenParameters.has(display)) return; + seenParameters.add(display); + parameters.push({display, source, role}); + }; + const dimensionBindings = new Map(); + for (const line of blockLines.filter((value) => value.startsWith('B.DIM'))) { + const body = line.slice('B.DIM'.length).trim(); + const compact = body.match( + /^LB([0-2])\/([A-Za-z][A-Za-z0-9_]*),\s*LB([0-2])\/([A-Za-z][A-Za-z0-9_]*),\s*LB([0-2])\/([A-Za-z][A-Za-z0-9_]*)(?:\s+\([^)]*\))?$/, + ); + const compactAssigned = body.match( + /^LB([0-2])\s*=\s*([A-Za-z][A-Za-z0-9_]*),\s*LB([0-2])\s*=\s*([A-Za-z][A-Za-z0-9_]*),\s*LB([0-2])\s*=\s*([A-Za-z][A-Za-z0-9_]*)(?:\s+\([^)]*\))?$/, + ); + const single = body.match( + /^LB([0-2])(?:\s*=\s*|\s+)([A-Za-z][A-Za-z0-9_]*)(?:\s+or\s+cooperative\s+group_M)?(?:\s+\([^)]*\))?$/, + ); + const groupedBare = body.match( + /^LB([0-2])\/LB([0-2])\/LB([0-2])(?:\s+\(optional\))?$/, + ); + const bare = body.match(/^LB([0-2])(?:\s+\(optional\))?$/); + const candidates: Array<[string, string | null]> = compact !== null + ? [[compact[1], compact[2]], [compact[3], compact[4]], [compact[5], compact[6]]] + : compactAssigned !== null + ? [[compactAssigned[1], compactAssigned[2]], [compactAssigned[3], compactAssigned[4]], [compactAssigned[5], compactAssigned[6]]] + : single !== null + ? [[single[1], single[2] === 'group_M' ? 'M' : single[2]]] + : groupedBare !== null + ? [[groupedBare[1], null], [groupedBare[2], null], [groupedBare[3], null]] + : bare !== null + ? [[bare[1], null]] + : []; + if (candidates.length === 0) { + fail(`${unit.id} has non-structured B.DIM projection: ${line}`); + } + for (const [slot, semantic] of candidates) { + const key = `LB${slot}`; + const previous = dimensionBindings.get(key); + if (previous !== undefined && previous.semantic !== semantic) { + fail(`${unit.id} assigns ${key} to conflicting high-level dimensions`); + } + dimensionBindings.set(key, {semantic, source: line}); + } + } + for (const [slot, binding] of [...dimensionBindings.entries()].sort()) { + addParameter( + binding.semantic === null ? slot : `${slot}:${binding.semantic}`, + binding.source, + 'dimension', + ); } - const stem = path.posix.basename(unit.source, '.asl'); - const segments = [ - 'instructions', - unit.surface, - ...unit.classification, - stem, - ].map((segment) => encodeURIComponent(segment)); - return `/${segments.join('/')}/`; + const parameterPatterns: Array<[RegExp, string, string]> = [ + [/\bAType\b/, 'DataTypeA', 'left data type'], + [/\bBType\b/, 'DataTypeB', 'right data type'], + [/\bDataType\b/, 'DataType', 'data type'], + [/\bLayout\b/, 'Layout', 'layout'], + [/\bPadValue\b/, 'PadValue', 'padding value'], + [/\bByteId\b/, 'ByteId', 'byte selector'], + [/\bDstDataType\b/, 'DstDataType', 'destination data type'], + ]; + for (const [pattern, display, role] of parameterPatterns) { + const source = blockLines.find((line) => pattern.test(line)); + if (source !== undefined) addParameter(display, source, role); + } + const operandValues = Array.isArray(contract.operands) ? contract.operands : []; + const operands = operandValues.map((value, index) => { + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + fail(`${unit.id} contract.operands[${index}] is malformed`); + } + const operand = value as Record; + const field = stringValue(operand, 'field'); + const role = stringValue(operand, 'role'); + if (field === null || role === null) fail(`${unit.id} contract.operands[${index}] lacks field or role`); + return {field, role, index}; + }); + const outputOperands = operands.filter(({field}) => /^destination\d+$/.test(field)); + const displayField = (field: string, output: boolean): string => { + const indexed = field.match(/^(source|destination|scalar|natural|positive|flag)(\d+)$/); + if (indexed !== null) { + const [, kind, index] = indexed; + if (kind === 'source') return `SrcTile${index}`; + if (kind === 'destination') return outputOperands.length === 1 ? 'DstTile' : `DstTile${index}`; + return `${kind[0].toLocaleUpperCase()}${kind.slice(1)}${index}`; + } + const named: Record = { + address: 'Address', + comparison: 'Comparison', + diagonal: 'Diagonal', + numeric_control: 'NumericControl', + selected_byte: 'SelectedByte', + sort_width: 'SortWidth', + }; + const display = named[field]; + if (display === undefined || output) fail(`${unit.id} has unsupported high-level operand field ${field}`); + return display; + }; + const inputs = operands + .filter((operand) => !outputOperands.includes(operand)) + .map(({field, role, index}) => ({ + display: displayField(field, false), + source: `contract.operands[${index}].${field}`, + role, + })); + const outputs = outputOperands.map(({field, role, index}) => ({ + display: displayField(field, true), + source: `contract.operands[${index}].${field}`, + role, + })); + const parameterText = parameters.length > 0 + ? ` <${parameters.map(({display}) => display).join(', ')}>` + : ''; + const inputText = inputs.length > 0 ? ` ${inputs.map(({display}) => display).join(', ')}` : ''; + const outputText = outputs.length > 0 ? ` -> ${outputs.map(({display}) => display).join(', ')}` : ''; + const attributes = blockLines.map((line, index) => ({ + display: line.match(/\b(B(?:START)?(?:\.[A-Z0-9]+)+)\b/)?.[1] ?? `BundleRule${index + 1}`, + source: line, + role: 'ASL-owned block composition', + })); + return { + form: `${unit.mnemonic}${parameterText}${inputText}${outputText}`, + parameters, + inputs, + outputs, + attributes, + basis: [...new Set([ + ...blockLines, + ...operands.map(({index}) => `contract.operands[${index}]`), + ])], + }; +} + +function instructionIndexData( + context: LoadContext, + release: PtoReleaseIdentity, + units: Array<{data: PtoUnitWorkbenchData; route: string}>, +): PtoInstructionIndexData { + const entries = units + .filter(({data}) => typeof data.unit.mnemonic === 'string') + .map(({data, route}) => { + const surface = String(data.unit.surface ?? data.metadata.surface); + if (!['scalar', 'block', 'tile'].includes(surface)) { + fail(`instruction index has unsupported surface ${surface}`); + } + const classification = Array.isArray(data.unit.classification) + ? data.unit.classification.filter((value): value is string => typeof value === 'string') + : []; + return { + id: String(data.unit.id ?? data.metadata.id), + mnemonic: String(data.unit.mnemonic), + surface: surface as 'scalar' | 'block' | 'tile', + classification, + route: localizedRoute(context, route), + summary: stringValue(data.metadata, 'summary') ?? stringValue(data.unit, 'summary') ?? '', + sourcePath: data.source.path, + sourceSha256: data.source.sha256, + }; + }) + .sort( + (left, right) => + left.surface.localeCompare(right.surface) || + left.mnemonic.localeCompare(right.mnemonic) || + left.id.localeCompare(right.id), + ); + return {release, entries}; +} + +function ndfCatalogData( + root: string, + context: LoadContext, + release: PtoReleaseIdentity, + traceability: TraceabilityData, + adrIndex: AdrIndexData, + graph: PtoNdfGraphData, + requirementSources: Map, + evidence: PtoArtifactEvidence[], + units: Array<{data: PtoUnitWorkbenchData; route: string}>, +): {catalog: PtoNdfCatalogData; details: PtoNdfDetailData[]} { + const unitsById = new Map( + units.map((unit) => [String(unit.data.unit.id ?? unit.data.metadata.id), unit]), + ); + const clauses = new Map(); + const occurrenceOwners = new Map>(); + for (const unit of units) { + const unitId = String(unit.data.unit.id ?? unit.data.metadata.id); + for (const clause of unit.data.ndfClauses) { + const previous = clauses.get(clause.id); + if (previous !== undefined && ( + previous.clauseSha256 !== clause.clauseSha256 || + previous.sourceSha256 !== clause.sourceSha256 || + previous.text !== clause.text + )) { + fail(`NDF catalog has inconsistent duplicate clause ${clause.id}`); + } + clauses.set(clause.id, clause); + occurrenceOwners.set(clause.id, new Set([ + ...(occurrenceOwners.get(clause.id) ?? []), + unitId, + ])); + } + } + const instructionContracts = new Map( + traceability.units + .filter((unit) => typeof unit.instruction_contract?.ndf_clause === 'string') + .map((unit) => [unit.instruction_contract?.ndf_clause as string, unit]), + ); + const expectedIds = traceability.requirements + .map((requirement) => requirement.id) + .sort(); + const actualIds = [...new Set([...clauses.keys(), ...instructionContracts.keys()])].sort(); + if (JSON.stringify(actualIds) !== JSON.stringify(expectedIds)) { + fail(`NDF catalog does not equal release requirements: ${actualIds.length}/${expectedIds.length}`); + } + const ownerRoute = (unitId: string): PtoNdfOwnerRoute => { + const unit = unitsById.get(unitId); + if (unit === undefined) fail(`NDF catalog references missing affected unit ${unitId}`); + return { + id: unitId, + mnemonic: typeof unit.data.unit.mnemonic === 'string' ? unit.data.unit.mnemonic : null, + surface: String(unit.data.unit.surface ?? unit.data.metadata.surface), + route: localizedRoute(context, unit.route), + sourcePath: unit.data.source.path, + }; + }; + const graphNodes = new Map(graph.nodes.map((node) => [node.id, node])); + const relationships = new Map(); + const relationshipHref = (node: PtoGraphNode): {href: string; external: boolean} => { + if (node.kind === 'asl') { + const owner = unitsById.get(node.id); + if (owner === undefined) fail(`NDF relationship references missing ASL unit ${node.id}`); + return {href: localizedRoute(context, owner.route), external: false}; + } + if (node.kind === 'ndf') { + const instructionOwner = instructionContracts.get(node.id); + if (instructionOwner !== undefined) { + const owner = unitsById.get(instructionOwner.id); + if (owner === undefined) fail(`NDF relationship references missing instruction unit ${instructionOwner.id}`); + return {href: localizedRoute(context, owner.route), external: false}; + } + return {href: localizedRoute(context, `/ndf/${encodeURIComponent(node.id)}/`), external: false}; + } + if (node.sourceUrl === null) fail(`NDF relationship ${node.id} has no source URL`); + return {href: node.sourceUrl, external: true}; + }; + for (const edge of graph.edges) { + const addRelationship = ( + center: string, + otherId: string, + direction: 'incoming' | 'outgoing', + ): void => { + const node = graphNodes.get(otherId); + if (node === undefined) fail(`NDF relationship references missing graph node ${otherId}`); + relationships.set(center, [ + ...(relationships.get(center) ?? []), + {kind: edge.kind, direction, node, ...relationshipHref(node)}, + ]); + }; + addRelationship(edge.source, edge.target, 'outgoing'); + addRelationship(edge.target, edge.source, 'incoming'); + } + const requirementsById = new Map( + traceability.requirements.map((requirement) => [requirement.id, requirement]), + ); + const adrRecordsById = new Map(adrIndex.records.map((record) => [record.id, record])); + const details = [...clauses.keys()].sort().map((id) => { + const clause = clauses.get(id); + if (clause === undefined) fail(`NDF catalog lost ${id}`); + const requirement = requirementsById.get(id); + if (requirement === undefined) fail(`NDF detail references missing requirement ${id}`); + const ownerIds = [...new Set([ + ...(occurrenceOwners.get(id) ?? []), + ...clause.affectedUnits, + ])].sort(); + const adrIds = new Set(requirement.readiness_subjects); + for (const record of adrIndex.records) { + if (record.affected_ndf.includes(id)) adrIds.add(record.id); + } + const missingAdrs = [...adrIds].filter((adrId) => !adrRecordsById.has(adrId)); + if (missingAdrs.length > 0) fail(`NDF detail ${id} references missing ADRs: ${missingAdrs.join(', ')}`); + return { + release, + clause, + owners: ownerIds.map(ownerRoute), + adrs: [...adrIds].sort().map((adrId) => publicAdr(root, release, adrRecordsById.get(adrId)!)), + tests: instructionTests(root, release.commit, traceability, new Set(requirement.tests)) + .map(({sourceText: _sourceText, ...test}) => test), + evidence, + relationships: (relationships.get(id) ?? []).sort( + (left, right) => left.node.id.localeCompare(right.node.id) || left.kind.localeCompare(right.kind), + ), + relationshipFallbackRoute: localizedRoute(context, '/explore/ndf/'), + }; + }); + const detailsById = new Map(details.map((detail) => [detail.clause.id, detail])); + const entries = expectedIds.map((id): PtoNdfCatalogData['entries'][number] => { + const detail = detailsById.get(id); + if (detail !== undefined) { + const {clause, owners} = detail; + return { + id, + entryKind: 'clause', + kind: clause.kind, + level: clause.level, + layer: clause.layer, + status: clause.status, + text: clause.text, + route: localizedRoute(context, `/ndf/${encodeURIComponent(id)}/`), + sourcePath: clause.sourcePath, + sourceSha256: clause.sourceSha256, + clauseSha256: clause.clauseSha256, + owners, + }; + } + const instructionOwner = instructionContracts.get(id); + const identity = requirementSources.get(id); + if (instructionOwner === undefined || identity === undefined) { + fail(`NDF catalog cannot resolve instruction-contract identity ${id}`); + } + const owner = ownerRoute(instructionOwner.id); + return { + id, + entryKind: 'instruction-contract', + kind: 'instruction-contract', + level: instructionOwner.surface, + layer: 'instruction', + status: requirementsById.get(id)?.executable ? 'executable' : 'contract', + text: null, + route: owner.route, + sourcePath: identity.sourcePath, + sourceSha256: identity.sourceSha256, + clauseSha256: identity.clauseSha256, + owners: [owner], + }; + }); + return {catalog: {release, entries}, details}; } function catalogEncoding( @@ -1095,7 +2208,63 @@ function catalogEncoding( fail(`mnemonic unit ${unit.id} has unsupported surface ${unit.surface}`); } +function assemblerSymbols( + metadata: Record, + encoding: PtoUnitEncoding, +): PtoUnitWorkbenchData['assemblerSymbols'] { + const contract = metadata.contract; + const contractRecord = + contract !== null && typeof contract === 'object' && !Array.isArray(contract) + ? contract as Record + : {}; + const roles = new Map(); + if (Array.isArray(contractRecord.operands)) { + for (const value of contractRecord.operands) { + if (value === null || typeof value !== 'object' || Array.isArray(value)) continue; + const operand = value as Record; + const field = stringValue(operand, 'field'); + const role = stringValue(operand, 'role'); + if (field && role) roles.set(field, role); + } + } + const zeroMeanings = + contractRecord.field_zero_meanings !== null && + typeof contractRecord.field_zero_meanings === 'object' && + !Array.isArray(contractRecord.field_zero_meanings) + ? contractRecord.field_zero_meanings as Record + : {}; + const symbols = new Map(); + for (const form of encoding.catalogForms) { + const fields = Array.isArray(form.fields) ? form.fields : []; + for (const value of fields) { + if (value === null || typeof value !== 'object' || Array.isArray(value)) continue; + const field = value as Record; + const name = stringValue(field, 'name'); + if (!name || symbols.has(name)) continue; + symbols.set(name, { + field: name, + width: field.width === undefined ? '—' : String(field.width), + signedness: stringValue(field, 'signedness') ?? '—', + role: roles.get(name) ?? '—', + zeroMeaning: stringValue(zeroMeanings, name) ?? '—', + }); + } + } + for (const [field, role] of roles) { + if (symbols.has(field)) continue; + symbols.set(field, { + field, + width: '—', + signedness: '—', + role, + zeroMeaning: stringValue(zeroMeanings, field) ?? '—', + }); + } + return [...symbols.values()]; +} + function unitAdrs( + root: string, release: PtoReleaseIdentity, unit: TraceabilityUnit, requirementIds: Set, @@ -1125,7 +2294,7 @@ function unitAdrs( } return [...requested] .sort((left, right) => left.localeCompare(right)) - .map((id) => publicAdr(release.commit, recordsById.get(id)!)); + .map((id) => publicAdr(root, release, recordsById.get(id)!)); } function buildUnitData( @@ -1162,7 +2331,7 @@ function buildUnitData( fail(`metadata and traceability mnemonics disagree for ${unit.source}`); } - const ndfClauses = parseNdfClauses(sourceText, unit.source); + const ndfClauses = parseNdfClauses(sourceText, unit.source, release, unit); const requirementIds = new Set(ndfClauses.map((clause) => clause.id)); for (const clause of ndfClauses) { const requirement = requirementsById.get(clause.id); @@ -1226,6 +2395,8 @@ function buildUnitData( }), ); + const encoding = catalogEncoding(unit, catalogs, metadata); + const siteProjection = loadSiteInstructionProjection(root, unit, sourceText); return { release, source, @@ -1235,13 +2406,36 @@ function buildUnitData( ndfClauses, unit: unit as unknown as Record, tests, - adrs: unitAdrs(release, unit, requirementIds, requirementsById, adrIndex), + adrs: unitAdrs(root, release, unit, requirementIds, requirementsById, adrIndex), evidence, - encoding: catalogEncoding(unit, catalogs, metadata), + encoding, + assemblerSymbols: assemblerSymbols(metadata, encoding), + composition: parseInstructionComposition( + siteProjection?.data.composition, + siteProjection?.path ?? unit.source, + unit.id, + traceability, + release, + ), + semanticExecution: parseSemanticExecutionProjection( + root, + release, + siteProjection?.data.semanticExecution, + siteProjection?.path ?? unit.source, + unit, + sourceText, + ), + highLevelAssembly: highLevelAssembly(unit, metadata), }; } -function publicAdr(commit: string, record: AdrIndexRecord): PtoAdrRecord { +function publicAdr(root: string, release: PtoReleaseIdentity, record: AdrIndexRecord): PtoAdrRecord { + if (!record.path.startsWith('docs/status/decisions/')) { + fail(`ADR source is outside canonical decision owners: ${record.path}`); + } + const source = readText(root, record.path); + const decisionNumber = record.id.match(/^ADR-(\d{4})$/)?.[1]; + if (decisionNumber === undefined) fail(`invalid ADR identity ${record.id}`); return { id: record.id, title: record.title, @@ -1251,7 +2445,18 @@ function publicAdr(commit: string, record: AdrIndexRecord): PtoAdrRecord { affectedNdf: record.affected_ndf, affectedUnits: record.affected_units, targetReleases: record.target_releases, - githubUrl: sourceUrl(commit, record.path), + decisionAssetUrl: `/evidence/decisions/${record.id}.json`, + githubUrl: sourceUrl(release.commit, record.path), + sourceSha256: sha256(source), + identity: { + fullId: record.id, + kind: 'adr', + anchor: `adr-${record.id.toLocaleLowerCase('en-US')}`, + facets: [ + {role: 'decision', label: 'ADR'}, + {role: 'case', label: decisionNumber}, + ], + }, }; } @@ -1482,6 +2687,11 @@ function searchData( requirementSources: Map, ): PtoSearchData { const entries = new Map(); + const instructionContracts = new Map( + traceability.units + .filter((unit) => typeof unit.instruction_contract?.ndf_clause === 'string') + .map((unit) => [unit.instruction_contract?.ndf_clause as string, unit]), + ); const addEntry = (entry: PtoSearchEntry): void => { if (entries.has(entry.id)) { fail(`search index contains duplicate identity ${entry.id}`); @@ -1513,12 +2723,18 @@ function searchData( for (const requirement of traceability.requirements) { const identity = requirementSources.get(requirement.id); if (identity === undefined) fail(`missing search source for ${requirement.id}`); + const instructionOwner = instructionContracts.get(requirement.id); addEntry({ id: requirement.id, label: requirement.id, kind: 'ndf', path: identity.sourcePath, - url: identity.sourceUrl, + url: localizedRoute( + context, + instructionOwner === undefined + ? `/ndf/${encodeURIComponent(requirement.id)}/` + : unitRoute(instructionOwner), + ), sha256: identity.clauseSha256, startLine: identity.startLine, endLine: identity.endLine, @@ -1673,11 +2889,32 @@ export default function ptoContentPlugin(context: LoadContext): Plugin [ + record.id, + parseDecisionNodes(root, release, record.path, readText(root, record.path)), + ]), + ), search: searchData( context, release, @@ -1721,6 +2958,16 @@ export default function ptoContentPlugin(context: LoadContext): Plugin test), + semanticExecution: unit.data.semanticExecution === null ? null : { + ...unit.data.semanticExecution, + stages: unit.data.semanticExecution.stages.map((stage) => ({ + ...stage, + sharedRegions: stage.sharedRegions.map(({text: _text, ...region}) => ({ + ...region, + sourceAssetUrl: `/evidence/asl-fragments/${region.fragmentSha256}.asl`, + })), + })), + }, }; return { ...unit, @@ -1731,6 +2978,27 @@ export default function ptoContentPlugin(context: LoadContext): Plugin ({ + detail, + module: await actions.createData( + `ndf-detail-${String(index + 1).padStart(4, '0')}.json`, + JSON.stringify(detail), + ), + })), + ); const graphDataModule = await actions.createData( 'ndf-graph.json', JSON.stringify(content.graph), @@ -1757,6 +3025,32 @@ export default function ptoContentPlugin(context: LoadContext): Plugin typeof data.unit.mnemonic === 'string') + .map(({data, route}) => { + const docRegion = (name: string): string | null => { + const match = data.source.text.match( + new RegExp(`^// DOC-BEGIN: ${name}\\s*$\\n([\\s\\S]*?)^// DOC-END: ${name}\\s*$`, 'm'), + ); + return match?.[1]?.trim() ?? null; + }; + const decode = docRegion('decode'); + const operation = docRegion('operation'); + const contract = data.metadata.contract !== null && + typeof data.metadata.contract === 'object' && + !Array.isArray(data.metadata.contract) + ? data.metadata.contract as Record + : {}; + const assemblyCount = Array.isArray(data.metadata.assembly) + ? data.metadata.assembly.length : 0; + const bundleLines = Array.isArray(data.metadata.block) + ? data.metadata.block.filter((line) => typeof line === 'string' && !line.startsWith('#')).length + : 0; + const bundleRuleCount = Array.isArray(contract.block_composition) + ? contract.block_composition.length : 0; + const exampleCount = Array.isArray(contract.examples) ? contract.examples.length : 0; + const constraintCount = ['legality', 'exceptions'].reduce( + (sum, key) => sum + (Array.isArray(contract[key]) ? contract[key].length : 0), 0, + ); + const stateContractCount = ['operands', 'state_effects', 'memory_effects', 'ordering'].reduce( + (sum, key) => sum + (Array.isArray(contract[key]) ? contract[key].length : 0), 0, + ); + const encodedFieldCount = data.encoding.catalogForms.reduce( + (sum, form) => sum + (Array.isArray(form.fields) ? form.fields.length : 0), + 0, + ); + const decodeStatus = decode === null ? 'missing' : + /\bDecode_[A-Za-z0-9_]+\s*\(/.test(decode) ? 'complete' : 'source-binding'; + const operationStatus = operation === null ? 'missing' : + /\bExecuteDecoded_[A-Za-z0-9_]+\s*\(/.test(operation) ? 'complete' : 'source-binding'; + const explainedGaps: string[] = []; + const unexplainedGaps: string[] = []; + if (data.encoding.catalogForms.length === 0) { + explainedGaps.push('owner is an encoding alias or has no standalone encoded form'); + } + if (decodeStatus !== 'complete') { + explainedGaps.push(decodeStatus === 'missing' + ? 'owner has no DOC decode region' + : 'owner exposes exact instruction-selection/source binding without an operand-binding Decode procedure'); + } + if (operationStatus !== 'complete') { + explainedGaps.push(operationStatus === 'missing' + ? 'owner has no DOC operation region' + : 'owner exposes exact handler/helper binding without a mnemonic-specific ExecuteDecoded procedure'); + } + if (exampleCount === 0) explainedGaps.push('owner contract declares no example'); + if (constraintCount === 0) explainedGaps.push('owner contract declares no explicit legality or exception list'); + if (stateContractCount === 0) explainedGaps.push('owner contract declares no operand/effect/ordering list'); + const bundleStatus = String(data.unit.surface ?? data.metadata.surface) !== 'tile' + ? 'not-applicable' + : data.composition !== null + ? 'complete' + : bundleLines > 0 && bundleRuleCount <= bundleLines + ? 'complete' + : 'source-gap'; + if (bundleStatus === 'source-gap') { + explainedGaps.push('owner bundle rules are not fully expanded by source block-sequence metadata'); + } + if (assemblyCount === 0) unexplainedGaps.push('missing assembly metadata'); + if (encodedFieldCount > 0 && data.assemblerSymbols.length === 0) { + unexplainedGaps.push('encoded fields lack assembler-symbol projection'); + } + const localizedInstructionRoute = localizedRoute(context, route); + const localePrefix = locale === context.i18n.defaultLocale ? '/' : `/${locale}/`; + if (!localizedInstructionRoute.startsWith(localePrefix)) { + unexplainedGaps.push('route escapes locale tree'); + } + return { + id: String(data.unit.id ?? data.metadata.id), + mnemonic: String(data.unit.mnemonic), + surface: String(data.unit.surface ?? data.metadata.surface), + route: localizedInstructionRoute, + layout_contract: 'UnitWorkbenchView.arm-style.v1', + source: data.source.path, + source_sha256: data.source.sha256, + encoding: data.encoding.catalogForms.length > 0 ? 'complete' : 'explained-gap', + syntax: assemblyCount > 0 ? 'complete' : 'missing', + assembler_symbols: encodedFieldCount === 0 || data.assemblerSymbols.length > 0 + ? 'complete' : 'missing', + decode_source: decodeStatus, + operation_source: operationStatus, + behavior: data.readerGuide.status, + constraints_faults: constraintCount > 0 ? 'complete' : 'explained-gap', + state_reads_writes_result: stateContractCount > 0 ? 'complete' : 'explained-gap', + bundle_source: bundleLines > 0 ? 'owner-metadata' : 'not-applicable', + bundle_status: bundleStatus, + bundle_lines: bundleLines, + bundle_rules: bundleRuleCount, + decisions: data.adrs.length, + ndf_owners: data.readerGuide.owners.filter((owner) => owner.kind === 'ndf').length, + evidence: data.tests.length, + source_records: data.readerGuide.owners.length > 0 && data.tests.length > 0 + ? 'complete' : 'explained-gap', + examples: exampleCount, + locale, + content_locale: data.readerGuide.contentLocale, + locale_status: data.readerGuide.status, + specialized_projection: data.composition !== null || data.semanticExecution !== null, + explained_gaps: explainedGaps, + unexplained_gaps: unexplainedGaps, + }; + }); + const surfaceTotals = Object.fromEntries( + ['scalar', 'block', 'tile'].map((surface) => [surface, + instructionCoverage.filter((entry) => entry.surface === surface).length]), + ); + const unexplainedOmissions = instructionCoverage.reduce( + (sum, entry) => sum + entry.unexplained_gaps.length, 0, + ); + const matrix = { + schema: 'pto.site-instruction-coverage.v1', + locale, + source_commit: content.release.commit, + instruction_count: instructionCoverage.length, + surface_totals: surfaceTotals, + layout_contract: 'UnitWorkbenchView.arm-style.v1', + unexplained_omissions: unexplainedOmissions, + entries: instructionCoverage, + }; + writeFileSync( + path.join(outDir, 'pto-instruction-coverage.json'), + `${JSON.stringify(matrix, null, 2)}\n`, + 'utf8', + ); + if (unexplainedOmissions !== 0) { + fail(`instruction coverage has ${unexplainedOmissions} unexplained omissions for ${locale}`); + } + const siteOutDir = locale === context.i18n.defaultLocale ? outDir : path.dirname(outDir); @@ -1836,6 +3266,38 @@ export default function ptoContentPlugin(context: LoadContext): Plugin(); + for (const {data} of content.units) { + for (const stage of data.semanticExecution?.stages ?? []) { + for (const region of stage.sharedRegions) { + if (region.text === undefined || sha256(region.text) !== region.fragmentSha256) { + fail(`build content has stale shared ASL fragment ${region.id}`); + } + const previous = fragments.get(region.fragmentSha256); + if (previous !== undefined && previous !== region.text) { + fail(`shared ASL fragment hash collision for ${region.id}`); + } + fragments.set(region.fragmentSha256, region.text); + } + } + } + for (const [digest, source] of fragments) { + writeFileSync(path.join(fragmentDirectory, `${digest}.asl`), source, 'utf8'); + } + + const decisionDirectory = path.join(siteOutDir, 'evidence/decisions'); + mkdirSync(decisionDirectory, {recursive: true}); + for (const [id, nodes] of Object.entries(content.adrDecisions)) { + if (!/^ADR-\d{4}$/.test(id)) fail(`unsafe ADR asset identity ${id}`); + writeFileSync( + path.join(decisionDirectory, `${id}.json`), + `${JSON.stringify(nodes)}\n`, + 'utf8', + ); + } }, }; } diff --git a/docs/site/plugins/pto-content/remark-repository-links.test.mjs b/docs/site/plugins/pto-content/remark-repository-links.test.mjs index ff48974c4..243879251 100644 --- a/docs/site/plugins/pto-content/remark-repository-links.test.mjs +++ b/docs/site/plugins/pto-content/remark-repository-links.test.mjs @@ -2,12 +2,16 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import path from 'node:path'; import ptoRepositoryLinksRemarkPlugin, { + presentGeneratedAslReference, rewriteRepositoryLink, } from './remark-repository-links.ts'; const repositoryRoot = path.resolve(import.meta.dirname, '..', '..', '..', '..'); const markdownPath = path.join(repositoryRoot, 'docs', 'guides', 'example.md'); const commit = '0123456789abcdef0123456789abcdef01234567'; +const unitRoutes = new Map([ + ['docs/arch/overview/architecture.md', '/units/PTO-ARCH-OVERVIEW-ARCHITECTURE/'], +]); test('rewrites repository-relative links that escape docs and preserves fragments', () => { assert.equal( @@ -66,8 +70,9 @@ test('projects existing Markdown links to docs-root absolute file references', ( repositoryLayout, repositoryRoot, commit, + unitRoutes, ), - '/arch/overview/architecture.md#state', + '/units/PTO-ARCH-OVERVIEW-ARCHITECTURE/#state', ); const localizedRepositoryLayout = path.join( repositoryRoot, @@ -86,8 +91,9 @@ test('projects existing Markdown links to docs-root absolute file references', ( localizedRepositoryLayout, repositoryRoot, commit, + unitRoutes, ), - '/arch/overview/architecture.md#state', + '/zh-CN/units/PTO-ARCH-OVERVIEW-ARCHITECTURE/#state', ); }); @@ -128,3 +134,30 @@ test('fails closed when a relative link escapes the repository', () => { /escapes repository root/, ); }); + +test('presents generated ASL references in reader-first language with provenance last', () => { + const tree = { + type: 'root', + children: [ + {type: 'heading', depth: 1, children: [{type: 'text', value: 'ADD'}]}, + {type: 'paragraph', children: [{type: 'text', value: 'Normative ASL source: '}, {type: 'inlineCode', value: 'asl/scalar/alu/ADD.asl'}]}, + {type: 'heading', depth: 2, children: [{type: 'text', value: 'Normative identity'}]}, + {type: 'paragraph', children: [{type: 'text', value: 'The current instruction contract is owned by the ASL source linked above.'}]}, + {type: 'heading', depth: 2, children: [{type: 'text', value: 'Reader guide'}]}, + {type: 'blockquote', children: [{type: 'paragraph', children: [{type: 'text', value: 'Non-normative explanation.'}]}]}, + {type: 'heading', depth: 2, children: [{type: 'text', value: 'Assembly'}]}, + {type: 'heading', depth: 2, children: [{type: 'text', value: 'Operation'}]}, + ], + }; + + presentGeneratedAslReference(tree); + + const renderedText = JSON.stringify(tree); + assert.match(renderedText, /Instruction identity/); + assert.match(renderedText, /Behavior/); + assert.match(renderedText, /Assembly syntax/); + assert.match(renderedText, /ASL execution definition/); + assert.doesNotMatch(renderedText, /Non-normative explanation/); + assert.equal(tree.children.at(-2).children[0].value, 'Sources and release identity'); + assert.match(JSON.stringify(tree.children.at(-1)), /asl\/scalar\/alu\/ADD\.asl/); +}); diff --git a/docs/site/plugins/pto-content/remark-repository-links.ts b/docs/site/plugins/pto-content/remark-repository-links.ts index be429418f..c35e07abf 100644 --- a/docs/site/plugins/pto-content/remark-repository-links.ts +++ b/docs/site/plugins/pto-content/remark-repository-links.ts @@ -1,12 +1,15 @@ import {execFileSync} from 'node:child_process'; -import {existsSync, statSync} from 'node:fs'; +import {existsSync, readFileSync, statSync} from 'node:fs'; import path from 'node:path'; +import {unitRoute, type UnitRouteInput} from './routes.ts'; const GITHUB_REPOSITORY = 'https://github.com/PTO-ISA/pto-spec'; interface MarkdownNode { type?: string; + depth?: number; url?: string; + value?: string; children?: MarkdownNode[]; } @@ -68,13 +71,78 @@ function isInside(parent: string, candidate: string): boolean { return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative)); } +function isGeneratedAslReference(markdownPath: string, repositoryRoot: string): boolean { + const relative = path.relative(path.join(repositoryRoot, 'docs'), markdownPath).split(path.sep); + if (['arch', 'block', 'scalar', 'tile'].includes(relative[0] ?? '')) return true; + return ( + relative[0] === 'site' && + relative[1] === 'i18n' && + relative[3] === 'docusaurus-plugin-content-docs-reference' && + relative[4] === 'current' && + ['arch', 'block', 'scalar', 'tile'].includes(relative[5] ?? '') + ); +} + +function readerFacingText(value: string): string { + return value + .replace(/non-normative/gi, 'illustrative') + .replace(/非规范性?/g, '示例性'); +} + +function plainText(node: MarkdownNode): string { + return typeof node.value === 'string' + ? node.value + : (node.children ?? []).map(plainText).join(''); +} + +function replaceHeading(node: MarkdownNode, labels: Record): void { + if (node.type !== 'heading') return; + const replacement = labels[plainText(node).trim()]; + if (replacement === undefined) return; + node.children = [{type: 'text', value: replacement}]; +} + +export function presentGeneratedAslReference(tree: MarkdownNode): void { + if (tree.type !== 'root' || tree.children === undefined) return; + const sourceIdentity: MarkdownNode[] = []; + const visible: MarkdownNode[] = []; + for (const node of tree.children) { + const text = plainText(node).trim(); + if (node.type === 'paragraph' && text.startsWith('Normative ASL source:')) { + sourceIdentity.push(node); + continue; + } + if ( + (node.type === 'blockquote' && /Non-normative explanation/i.test(text)) || + (node.type === 'paragraph' && text === 'The current instruction contract is owned by the ASL source linked above.') + ) { + continue; + } + replaceHeading(node, { + 'Normative identity': 'Instruction identity', + 'Reader guide': 'Behavior', + Assembly: 'Assembly syntax', + Operation: 'ASL execution definition', + 'Normative ASL': 'ASL definition', + }); + visible.push(node); + } + if (sourceIdentity.length > 0) { + visible.push( + {type: 'heading', depth: 2, children: [{type: 'text', value: 'Sources and release identity'}]}, + ...sourceIdentity, + ); + } + tree.children = visible; +} + export function rewriteRepositoryLink( url: string, markdownPath: string, repositoryRoot: string, commit: string, + unitRoutesByDocumentation: Map = new Map(), ): string { - if (!isRelativeFileLink(url)) return url; const {target, suffix} = splitSuffix(url); if (target === '') return url; @@ -85,6 +153,19 @@ export function rewriteRepositoryLink( markdownParts[1] === 'i18n' && markdownParts[3] === 'docusaurus-plugin-content-docs-reference' && markdownParts[4] === 'current'; + const localePrefix = localized ? `/${markdownParts[2]}` : ''; + const canonicalUnitRoute = (documentation: string): string | null => { + const route = unitRoutesByDocumentation.get(documentation); + return route === undefined ? null : `${localePrefix}${route}${suffix}`; + }; + if (target.startsWith('/') && /^\/(?:arch|block|scalar|tile)\/.+\.md$/i.test(target)) { + const route = canonicalUnitRoute(`docs${target}`); + if (route === null) { + throw new Error(`[pto-repository-links] no canonical unit route for ${target}`); + } + return route; + } + if (!isRelativeFileLink(url)) return url; const canonicalMarkdownPath = localized ? path.join(docsRoot, ...markdownParts.slice(5)) : markdownPath; @@ -98,6 +179,8 @@ export function rewriteRepositoryLink( ? path.join(absoluteTarget, 'index.md') : absoluteTarget; const relative = path.relative(docsRoot, markdownTarget).split(path.sep).join('/'); + const unitRouteTarget = canonicalUnitRoute(`docs/${relative}`); + if (unitRouteTarget !== null) return unitRouteTarget; const excluded = relative.startsWith('site/') || relative.startsWith('mkdocs/') || @@ -131,7 +214,12 @@ function transformTree( markdownPath: string, repositoryRoot: string, commit: string, + readerFacing: boolean, + unitRoutesByDocumentation: Map, ): void { + if (readerFacing && node.type === 'text' && typeof node.value === 'string') { + node.value = readerFacingText(node.value); + } if ( (node.type === 'link' || node.type === 'definition') && typeof node.url === 'string' @@ -141,10 +229,11 @@ function transformTree( markdownPath, repositoryRoot, commit, + unitRoutesByDocumentation, ); } for (const child of node.children ?? []) { - transformTree(child, markdownPath, repositoryRoot, commit); + transformTree(child, markdownPath, repositoryRoot, commit, readerFacing, unitRoutesByDocumentation); } } @@ -155,6 +244,15 @@ export default function ptoRepositoryLinksRemarkPlugin( options.repositoryRoot ?? process.cwd(), ); const commit = options.commit ?? currentCommit(repositoryRoot); + const traceability = JSON.parse( + readFileSync( + path.join(repositoryRoot, 'spec/evidence/release-traceability-readiness.json'), + 'utf8', + ), + ) as {units: UnitRouteInput[]}; + const unitRoutesByDocumentation = new Map( + traceability.units.map((unit) => [unit.documentation, unitRoute(unit)]), + ); return (tree: MarkdownNode, file: MarkdownFile): void => { if (file.path === undefined) { @@ -166,6 +264,15 @@ export default function ptoRepositoryLinksRemarkPlugin( `[pto-repository-links] Markdown source is outside docs/: ${markdownPath}`, ); } - transformTree(tree, markdownPath, repositoryRoot, commit); + const generatedReference = isGeneratedAslReference(markdownPath, repositoryRoot); + if (generatedReference) presentGeneratedAslReference(tree); + transformTree( + tree, + markdownPath, + repositoryRoot, + commit, + generatedReference, + unitRoutesByDocumentation, + ); }; } diff --git a/docs/site/plugins/pto-content/routes.ts b/docs/site/plugins/pto-content/routes.ts new file mode 100644 index 000000000..f48912d9e --- /dev/null +++ b/docs/site/plugins/pto-content/routes.ts @@ -0,0 +1,28 @@ +export interface UnitRouteInput { + id: string; + mnemonic: string | null; + source: string; + surface: string; + classification: string[]; + documentation: string; +} + +export function unitRoute(unit: UnitRouteInput): string { + if (unit.mnemonic === null) { + return `/units/${encodeURIComponent(unit.id)}/`; + } + const sourceName = unit.source.split('/').at(-1) ?? unit.mnemonic; + const stem = sourceName.endsWith('.asl') ? sourceName.slice(0, -4) : sourceName; + const segments = ['instructions', unit.surface, ...unit.classification, stem] + .map((segment) => encodeURIComponent(segment)); + return `/${segments.join('/')}/`; +} + +export function legacyReferenceRoute(documentationPath: string): string { + if (!documentationPath.startsWith('docs/') || !documentationPath.endsWith('.md')) { + throw new TypeError(`cannot derive reference route for ${documentationPath}`); + } + let relative = documentationPath.slice('docs/'.length, -'.md'.length); + if (relative.endsWith('/index')) relative = relative.slice(0, -'/index'.length); + return `/reference/${relative}/`.replace(/\/+/g, '/'); +} diff --git a/docs/site/redirects.json b/docs/site/redirects.json index 8e0d77540..01ae5025c 100644 --- a/docs/site/redirects.json +++ b/docs/site/redirects.json @@ -1,7 +1,7 @@ [ { "from": ["/docs/PTO-Virtual-ISA-Manual.html", "/manual/index.html"], - "to": "/reference/arch/overview/architecture/" + "to": "/architecture/" }, { "from": ["/docs/isa/README.html"], @@ -9,18 +9,18 @@ }, { "from": ["/docs/coding/ProgrammingModel.html"], - "to": "/reference/arch/programming-model/execution-context/" + "to": "/units/PTO-ARCH-PROGRAMMING-MODEL-EXECUTION-CONTEXT/" }, { "from": ["/docs/machine/abstract-machine.html", "/manual/08-virtual-isa-and-ir.html"], - "to": "/reference/arch/overview/architecture/" + "to": "/architecture/" }, { "from": ["/manual/10-memory-ordering-consistency.html"], - "to": "/reference/arch/memory-model/ordering/" + "to": "/units/PTO-ARCH-MEMORY-MODEL-ORDERING/" }, { "from": ["/manual/11-backend-profiles-conformance.html"], - "to": "/reference/arch/profile/reference-profile/" + "to": "/units/PTO-ARCH-PROFILE-REFERENCE-PROFILE/" } ] diff --git a/docs/site/sidebars.ts b/docs/site/sidebars.ts index 9d590f4a9..5e38b479b 100644 --- a/docs/site/sidebars.ts +++ b/docs/site/sidebars.ts @@ -1,12 +1,30 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; /** - * Reference navigation follows the generated documentation tree. ASL-backed - * instruction workbench routes are generated by the PTO content plugin and - * must not be manually enumerated here. + * ASL-backed units use one canonical workbench route. This sidebar retains + * only project records that do not duplicate a generated unit workbench. */ const sidebars: SidebarsConfig = { - reference: [{type: 'autogenerated', dirName: '.'}], + reference: [ + { + type: 'category', + label: 'Decisions and architecture records', + collapsed: false, + items: [ + {type: 'autogenerated', dirName: 'governance'}, + {type: 'autogenerated', dirName: 'status/decisions'}, + {type: 'autogenerated', dirName: 'status/open'}, + ], + }, + { + type: 'category', + label: 'Development and releases', + items: [ + {type: 'autogenerated', dirName: 'development'}, + {type: 'autogenerated', dirName: 'releases'}, + ], + }, + ], }; export default sidebars; diff --git a/docs/site/src/components/ASLViewer.tsx b/docs/site/src/components/ASLViewer.tsx index 53deec62e..a7bd395f3 100644 --- a/docs/site/src/components/ASLViewer.tsx +++ b/docs/site/src/components/ASLViewer.tsx @@ -1,30 +1,135 @@ -import React from 'react'; +import React, {useState} from 'react'; import styles from './PtoWorkbench.module.css'; -import {firstText, record, sourceHref} from './data'; +import {firstText, record} from './data'; +import AslCode from './AslCode'; + +interface SourceRegion { + name: string; + text: string; + startLine: number; + endLine: number; +} + +function sourceRegion(sourceText: string, name: string): SourceRegion | null { + const lines = sourceText.split('\n'); + const start = lines.findIndex((line) => line.trim() === `// DOC-BEGIN: ${name}`); + if (start < 0) return null; + const end = lines.findIndex( + (line, index) => index > start && line.trim() === `// DOC-END: ${name}`, + ); + if (end < 0) return null; + return { + name, + text: lines.slice(start + 1, end).join('\n'), + startLine: start + 2, + endLine: end, + }; +} + +function isInstructionSelection(region: SourceRegion): boolean { + return ( + /InstructionContractOperation_/.test(region.text) && + !/\b(?:let|var|if|case|assert)\b/.test(region.text) + ); +} + +function SourceCode({region, wrap}: {region: SourceRegion; wrap: boolean}): React.JSX.Element { + return ( +
+ +
+ ); +} export interface ASLViewerProps { source: unknown; - title?: string; + chinese?: boolean; } -export default function ASLViewer({source, title = 'Embedded ASL source'}: ASLViewerProps): React.JSX.Element { +export default function ASLViewer({source, chinese = false}: ASLViewerProps): React.JSX.Element { + const [wrap, setWrap] = useState(false); const data = record(source); const sourceText = firstText(data, ['text', 'content', 'source']); - const href = sourceHref(data); - const path = firstText(data, ['path'], 'ASL owner'); + const operation = sourceRegion(sourceText, 'operation'); + const decode = sourceRegion(sourceText, 'decode'); + const selectionOnly = decode !== null && isInstructionSelection(decode); + const pageShapedDecode = decode !== null && !selectionOnly; + const operationBindingOnly = operation !== null && + !/\bExecuteDecoded_[A-Za-z0-9_]+\s*\(/.test(operation.text) && + /InstructionContractHandler_/.test(operation.text); + const fullSource: SourceRegion = { + name: 'complete owner', + text: sourceText || (chinese ? '此发布制品未包含源文本。' : 'Source text is not present in this release artifact.'), + startLine: 1, + endLine: Math.max(1, sourceText.split('\n').length), + }; + const primary = operation ?? fullSource; return ( -
+
- Normative · embedded verbatim -

{title}

+

{chinese ? 'ASL 伪代码' : 'ASL pseudocode'}

- {href && Open exact source ↗} +
-
-
{sourceText || 'Source text is not present in this release artifact.'}
-
+

+ {chinese + ? pageShapedDecode + ? 'Decode 与 Operation 均直接来自指令所有者,并按执行阶段分开显示。' + : operation ? '下面是该指令所有者中的 Operation;页面没有重写这段行为。' : '下面直接显示完整的 ASL 所有者。' + : pageShapedDecode + ? 'Decode and Operation come directly from the instruction owner and remain separated by execution phase.' + : operation ? 'This Operation comes directly from the instruction owner; the page does not rewrite its behavior.' : 'The complete ASL owner is shown directly below.'} +

+ {selectionOnly && ( + + )} + {operationBindingOnly && ( + + )} + {decode ? ( + <> +

{selectionOnly + ? (chinese ? 'Decode 源绑定' : 'Decode source binding') + : 'Decode ASL'}

+ + {operation && ( + <> +

{operationBindingOnly + ? (chinese ? 'Operation 源绑定' : 'Operation source binding') + : 'Operation ASL'}

+ + + )} + + ) : ( + + )} + {operation && ( +
+ {chinese ? '查看完整 ASL 所有者' : 'View the complete ASL owner'} + +
+ )}
); } diff --git a/docs/site/src/components/ArchitectureOverview.module.css b/docs/site/src/components/ArchitectureOverview.module.css new file mode 100644 index 000000000..beb779d96 --- /dev/null +++ b/docs/site/src/components/ArchitectureOverview.module.css @@ -0,0 +1,291 @@ +.page { + width: min(100%, 92rem); + padding: 2.5rem clamp(1rem, 3vw, 3rem) 5rem; + margin: 0 auto; +} + +.hero { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 2rem; + align-items: end; + padding: clamp(1.5rem, 4vw, 3rem); + color: var(--pto-hero-text); + background: var(--pto-workbench); + border: 1px solid var(--pto-border-strong); +} + +.hero h1 { + margin: 0.2rem 0 0.8rem; + color: inherit; + font-size: clamp(2.35rem, 5vw, 4.6rem); +} + +.hero p:last-child { + max-width: 54rem; + margin: 0; + color: var(--pto-hero-muted); + font-size: 1.05rem; +} + +.kicker, +.sectionHeading p, +.topicHeader p { + margin: 0; + color: var(--ifm-color-primary); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.09em; + text-transform: uppercase; +} + +.hero .kicker { + color: var(--pto-accent-soft); +} + +.versionBadge { + display: grid; + gap: 0.2rem; + min-width: 8rem; + padding: 0.85rem 1rem; + border-left: 2px solid var(--pto-accent-soft); +} + +.versionBadge span { + color: var(--pto-hero-muted); + font-size: 0.72rem; + text-transform: uppercase; +} + +.section, +.topic { + margin-top: 2rem; + padding: clamp(1.25rem, 2.5vw, 2.25rem); + border: 1px solid var(--pto-border); + background: var(--ifm-background-surface-color); +} + +.sectionHeading h2, +.topicHeader h2 { + margin: 0.25rem 0 0; +} + +.entrySummary { + margin-top: 1.4rem; +} + +.sourceExcerpt + .sourceExcerpt { + margin-top: 1.2rem; + padding-top: 1.2rem; + border-top: 1px solid var(--pto-border); +} + +.sourceExcerpt h3, +.sourceExcerpt h4 { + font-size: 1.05rem; +} + +.sourceExcerpt a { + text-decoration: underline; + text-underline-offset: 0.15em; +} + +.readingRail { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.7rem; + padding: 0; + margin: 1.5rem 0 0; + list-style: none; +} + +.readingRail a { + display: grid; + gap: 0.45rem; + height: 100%; + padding: 0.9rem; + color: var(--ifm-font-color-base); + background: var(--pto-surface-subtle); + border-left: 3px solid var(--ifm-color-primary); + text-decoration: none; +} + +.readingRail span { + color: var(--pto-text-muted); + font-family: var(--ifm-font-family-monospace); + font-size: 0.72rem; +} + +.readingRail code, +.topicHeader > code, +.relatedOwners code { + overflow-wrap: anywhere; + background: transparent; + font-size: 0.68rem; +} + +.tableViewport { + margin-top: 1.25rem; + overflow-x: auto; +} + +.tableViewport table { + width: 100%; + min-width: 42rem; + margin: 0; +} + +.tableViewport caption { + padding: 0.5rem 0; + color: var(--pto-text-muted); + text-align: left; +} + +.topicList { + display: grid; + gap: 1rem; +} + +.topic { + scroll-margin-top: calc(var(--ifm-navbar-height) + 1rem); +} + +.topicHeader { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(12rem, auto); + gap: 1rem; + align-items: start; +} + +.topicHeader > code { + justify-self: end; + color: var(--pto-text-muted); +} + +.scenario { + max-width: 62rem; + margin-top: 0.55rem; + color: var(--pto-text-muted); +} + +.sourceGap { + display: grid; + gap: 0.25rem; + padding: 0.85rem 1rem; + margin: 1.25rem 0; + background: color-mix(in srgb, var(--ifm-color-warning) 11%, transparent); + border-left: 3px solid var(--ifm-color-warning-dark); +} + +.primaryContract { + padding: 1.25rem; + margin-top: 1.25rem; + background: var(--pto-surface-subtle); + border: 1px solid var(--pto-border); +} + +.ownerActions, +.relatedActions { + display: flex; + flex-wrap: wrap; + gap: 0.8rem; + margin-top: 1rem; + font-size: 0.84rem; + font-weight: 700; +} + +.relatedOwners { + margin-top: 1.5rem; +} + +.relatedOwners h3 { + font-size: 1rem; +} + +.relatedOwners ul, +.provenance ul { + padding: 0; + margin: 0; + list-style: none; +} + +.relatedOwners li { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 1rem; + align-items: center; + padding: 0.75rem 0; + border-top: 1px solid var(--pto-border); +} + +.relatedOwners li > div:first-child { + display: grid; + gap: 0.2rem; +} + +.relatedActions { + margin: 0; +} + +.provenance { + margin-top: 2rem; + padding: 1rem 1.25rem; + background: var(--pto-surface-subtle); + border: 1px solid var(--pto-border); +} + +.provenance summary { + cursor: pointer; + font-weight: 750; +} + +.provenance dl, +.provenance ul { + margin-top: 1rem; +} + +.provenance dl div, +.provenance li { + display: grid; + grid-template-columns: 10rem minmax(0, 1fr); + gap: 1rem; + padding: 0.5rem 0; + border-top: 1px solid var(--pto-border); +} + +.provenance li { + grid-template-columns: minmax(14rem, 0.8fr) minmax(0, 1fr) minmax(12rem, 0.8fr); +} + +.provenance code, +.provenance a { + overflow-wrap: anywhere; +} + +@media (max-width: 1100px) { + .readingRail { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 720px) { + .page { + padding-inline: 0.75rem; + } + + .hero, + .topicHeader, + .relatedOwners li, + .provenance dl div, + .provenance li { + grid-template-columns: 1fr; + } + + .topicHeader > code { + justify-self: start; + } + + .readingRail { + grid-template-columns: 1fr; + } +} diff --git a/docs/site/src/components/AslCode.tsx b/docs/site/src/components/AslCode.tsx new file mode 100644 index 000000000..ed94b5ce5 --- /dev/null +++ b/docs/site/src/components/AslCode.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import {Highlight, type PrismTheme} from 'prism-react-renderer'; +import '@site/src/theme/aslPrism'; +import styles from './PtoWorkbench.module.css'; + +const ASL_THEME: PrismTheme = { + plain: {color: 'var(--pto-asl-plain)', backgroundColor: 'transparent'}, + styles: [ + {types: ['comment'], style: {color: 'var(--pto-asl-comment)', fontStyle: 'italic'}}, + {types: ['keyword'], style: {color: 'var(--pto-asl-keyword)', fontWeight: '600'}}, + {types: ['boolean'], style: {color: 'var(--pto-asl-boolean)', fontWeight: '600'}}, + {types: ['number'], style: {color: 'var(--pto-asl-number)'}}, + {types: ['string'], style: {color: 'var(--pto-asl-string)'}}, + {types: ['builtin'], style: {color: 'var(--pto-asl-builtin)'}}, + {types: ['function'], style: {color: 'var(--pto-asl-function)'}}, + {types: ['operator'], style: {color: 'var(--pto-asl-operator)'}}, + {types: ['punctuation'], style: {color: 'var(--pto-asl-punctuation)'}}, + ], +}; + +export default function AslCode({ + text, + startLine = 1, + label, + lineNumbers = true, +}: { + text: string; + startLine?: number; + label: string; + lineNumbers?: boolean; +}): React.JSX.Element { + return ( + + {({className, tokens, getLineProps, getTokenProps}) => ( +
+          
+            {tokens.map((line, index) => {
+              const lineProps = getLineProps({line});
+              return (
+                
+                  {lineNumbers && (
+                    
+                  )}
+                  
+                    {line.map((token, tokenIndex) => (
+                      
+                    ))}
+                  
+                
+              );
+            })}
+          
+        
+ )} +
+ ); +} diff --git a/docs/site/src/components/AssemblerSymbols.tsx b/docs/site/src/components/AssemblerSymbols.tsx new file mode 100644 index 000000000..545c207c1 --- /dev/null +++ b/docs/site/src/components/AssemblerSymbols.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import type {PtoAssemblerSymbol} from '@site/src/types/pto'; +import styles from './PtoWorkbench.module.css'; + +export default function AssemblerSymbols({ + symbols, + chinese, +}: { + symbols: PtoAssemblerSymbol[]; + chinese: boolean; +}): React.JSX.Element { + return ( +
+

{chinese ? '汇编符号' : 'Assembler symbols'}

+ {symbols.length === 0 ? ( +

{chinese + ? '该指令没有编码操作数字段;此项不适用。' + : 'This instruction has no encoded operand fields; assembler symbols are not applicable.'}

+ ) :
+ + + + + + + + + + + + + {symbols.map((row) => ( + + + + + + + + ))} + +
{chinese ? '汇编字段与架构角色' : 'Assembly fields and architectural roles'}
{chinese ? '字段' : 'Field'}{chinese ? '位宽' : 'Bits'}{chinese ? '有符号性' : 'Signedness'}{chinese ? '架构角色' : 'Architectural role'}{chinese ? '编码零' : 'Encoded zero'}
{row.field}{row.width}{row.signedness}{row.role}{row.zeroMeaning}
+
} +
+ ); +} diff --git a/docs/site/src/components/CatalogIndex.module.css b/docs/site/src/components/CatalogIndex.module.css new file mode 100644 index 000000000..ecc75e020 --- /dev/null +++ b/docs/site/src/components/CatalogIndex.module.css @@ -0,0 +1,198 @@ +.page { + width: min(100%, 92rem); + padding: 2.5rem clamp(1rem, 3vw, 3rem) 5rem; + margin: 0 auto; +} + +.hero { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 2rem; + align-items: end; + padding: clamp(1.5rem, 4vw, 3rem); + color: var(--pto-hero-text); + background: linear-gradient(120deg, var(--pto-workbench), #231316); + border: 1px solid var(--pto-border-strong); +} + +.hero h1 { + margin: 0.25rem 0 0.7rem; + color: inherit; + font-size: clamp(2.25rem, 5vw, 4.25rem); +} + +.hero p { + max-width: 58rem; + margin: 0; + color: var(--pto-hero-muted); +} + +.kicker { + color: var(--pto-highlight); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.count { + padding-left: 1rem; + color: var(--pto-highlight); + border-left: 2px solid var(--pto-highlight); + font-family: var(--ifm-font-family-monospace); + font-size: 1.15rem; +} + +.controls { + display: grid; + grid-template-columns: minmax(16rem, 1fr) auto; + gap: 1rem; + align-items: center; + padding: 1rem; + margin: 1.25rem 0; + background: var(--ifm-background-surface-color); + border: 1px solid var(--pto-border); +} + +.controls input { + min-width: 0; + padding: 0.72rem 0.85rem; + color: var(--ifm-font-color-base); + background: var(--pto-surface-subtle); + border: 1px solid var(--pto-border); +} + +.filters { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; +} + +.filters button { + padding: 0.55rem 0.75rem; + color: var(--ifm-font-color-base); + cursor: pointer; + background: var(--pto-surface-subtle); + border: 1px solid var(--pto-border); +} + +.filters button[aria-pressed='true'] { + color: var(--pto-nav-active); + background: var(--pto-surface-focus); + border-color: var(--ifm-color-primary); +} + +.resultLine { + color: var(--pto-text-muted); + font-size: 0.86rem; +} + +.grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.75rem; +} + +.card { + min-width: 0; + padding: 1rem; + background: var(--ifm-background-surface-color); + border: 1px solid var(--pto-border); + border-left: 3px solid var(--ifm-color-primary); +} + +.card:hover { + border-color: color-mix(in srgb, var(--pto-highlight) 54%, var(--pto-border)); +} + +.card h2 { + margin: 0.35rem 0 0.55rem; + font-size: 1.05rem; +} + +.identity { + color: var(--pto-highlight); + font-family: var(--ifm-font-family-monospace); + font-size: 0.7rem; + overflow-wrap: anywhere; +} + +.meta, +.summary { + color: var(--pto-text-muted); + font-size: 0.82rem; +} + +.summary { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: 4; +} + +.owners { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; + padding: 0; + margin: 0.7rem 0 0; + list-style: none; +} + +.owners a { + display: inline-block; + padding: 0.2rem 0.4rem; + color: var(--pto-highlight); + background: color-mix(in srgb, var(--pto-highlight) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--pto-highlight) 28%, var(--pto-border)); + font-family: var(--ifm-font-family-monospace); + font-size: 0.68rem; +} + +.detailOwners, +.provenance { + padding: 1.25rem; + margin-top: 1rem; + background: var(--ifm-background-surface-color); + border: 1px solid var(--pto-border); +} + +.detailOwners ul { + padding: 0; + list-style: none; +} + +.detailOwners li { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + padding: 0.55rem 0; + border-top: 1px solid var(--pto-border); +} + +.provenance summary { + cursor: pointer; + font-weight: 750; +} + +.provenance code { + overflow-wrap: anywhere; +} + +@media (max-width: 1100px) { + .grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 720px) { + .page { + padding-inline: 0.75rem; + } + + .hero, + .controls, + .grid { + grid-template-columns: 1fr; + } +} diff --git a/docs/site/src/components/DocumentationIdentity.tsx b/docs/site/src/components/DocumentationIdentity.tsx index 24ac7d9d1..dd163faf6 100644 --- a/docs/site/src/components/DocumentationIdentity.tsx +++ b/docs/site/src/components/DocumentationIdentity.tsx @@ -12,7 +12,7 @@ export default function DocumentationIdentity({documentation}: {documentation: P
Projection path {documentation.path} - Read this generated reference page + Embedded in the canonical workbench above
Projection SHA-256 diff --git a/docs/site/src/components/EncodingBitfield.tsx b/docs/site/src/components/EncodingBitfield.tsx index 927d48814..2177b0235 100644 --- a/docs/site/src/components/EncodingBitfield.tsx +++ b/docs/site/src/components/EncodingBitfield.tsx @@ -14,7 +14,7 @@ function waveDromSource(word: EncodingWordModel): WaveDromRegisterSource { reg: word.fields.slice().reverse().map((field) => ({ bits: field.width, name: field.name, - ...(field.value ? {attr: field.value} : {}), + ...(field.kind === 'field' && field.value ? {attr: field.value} : {}), })), config: { bits: word.width, @@ -26,7 +26,15 @@ function waveDromSource(word: EncodingWordModel): WaveDromRegisterSource { }; } -export default function EncodingBitfield({encoding}: {encoding: unknown}): React.JSX.Element { +export default function EncodingBitfield({ + encoding, + entryOnly = false, + chinese = false, +}: { + encoding: unknown; + entryOnly?: boolean; + chinese?: boolean; +}): React.JSX.Element { const data = record(encoding); const operation = record(data.tileOperation); if (!Array.isArray(data.catalogForms) || data.catalogForms.length === 0) { @@ -41,8 +49,18 @@ export default function EncodingBitfield({encoding}: {encoding: unknown}): React return (
- Generated from released catalog -

Encoding

+

+ {entryOnly + ? (chinese ? '入口指令编码:BSTART' : 'Entry instruction encoding: BSTART') + : (chinese ? '编码' : 'Encoding')} +

+ {entryOnly && ( +

+ {chinese + ? '这里只编码 bundle 的 BSTART 入口,不是完整 TLOAD。完整语义还需要上面的 B.DATR、B.DIM、B.IOR、B.IOT/B.IOS 与 BSTOP。' + : 'This encodes only the bundle-entry BSTART command, not the complete TLOAD. The complete operation also requires the B.DATR, B.DIM, B.IOR, B.IOT/B.IOS, and BSTOP structure shown above.'} +

+ )} {forms.map((commandForm, formIndex) => { const words = parseEncodingForm(commandForm); const assembly = firstText(commandForm, ['asm', 'mnemonic'], firstText(operation, ['command_mnemonic'])); @@ -61,13 +79,13 @@ export default function EncodingBitfield({encoding}: {encoding: unknown}): React
- + {word.fields.map((field, index) => ( - + - + ))} diff --git a/docs/site/src/components/EvidenceIndex.tsx b/docs/site/src/components/EvidenceIndex.tsx index a2e6886c0..9a3963015 100644 --- a/docs/site/src/components/EvidenceIndex.tsx +++ b/docs/site/src/components/EvidenceIndex.tsx @@ -1,6 +1,10 @@ import React, {useEffect, useMemo, useState} from 'react'; import styles from './PtoWorkbench.module.css'; import {firstText, itemSearchText, list, record, sourceHref, type UnknownRecord} from './data'; +import AslCode from './AslCode'; +import {ReaderNodes} from './ReaderGuide'; +import SemanticIdPath, {CopyIdentityButton} from './SemanticIdPath'; +import type {PtoReaderNode, PtoSemanticIdentity} from '@site/src/types/pto'; interface EvidenceGroup { id: string; @@ -8,6 +12,37 @@ interface EvidenceGroup { items: UnknownRecord[]; } +function DecisionRecord({id, url}: {id: string; url: string}): React.JSX.Element { + const [nodes, setNodes] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + let active = true; + void fetch(url) + .then((response) => { + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return response.json() as Promise; + }) + .then((value) => { + if (!Array.isArray(value)) throw new TypeError('Decision asset must be an array'); + if (active) setNodes(value); + }) + .catch((reason: unknown) => { + if (active) setError(`Decision record could not be loaded: ${String(reason)}`); + }); + return () => { active = false; }; + }, [url]); + + return ( +
+

Decision record

+ {nodes === null && error === null &&

Loading {id}…

} + {error &&

{error}

} + {nodes !== null && } +
+ ); +} + export interface EvidenceIndexProps { tests?: unknown; adrs?: unknown; @@ -41,9 +76,9 @@ function EvidenceSource({id, url}: {id: string; url: string}): React.JSX.Element {loading &&

Loading exact commit-scoped source…

} {error &&

{error}

} {source !== null && ( -
-          {source}
-        
+
+ +
)} ); @@ -51,7 +86,7 @@ function EvidenceSource({id, url}: {id: string; url: string}): React.JSX.Element export default function EvidenceIndex({tests, adrs, evidence}: EvidenceIndexProps): React.JSX.Element { const [query, setQuery] = useState(''); - const [openGroups, setOpenGroups] = useState>(new Set(['tests', 'evidence', 'adrs'])); + const [openGroups, setOpenGroups] = useState>(new Set(['adrs'])); const [pages, setPages] = useState>({}); const groups = useMemo(() => [ {id: 'tests', label: 'Executable evidence', items: list(tests).map(record)}, @@ -81,7 +116,6 @@ export default function EvidenceIndex({tests, adrs, evidence}: EvidenceIndexProp return (
- Release evidence · status preserved

Evidence index

@@ -106,23 +140,35 @@ export default function EvidenceIndex({tests, adrs, evidence}: EvidenceIndexProp const passCondition = firstText(item, ['passCondition']); const sourceAssetUrl = firstText(item, ['sourceAssetUrl']); const role = firstText(item, ['role', 'kind']); - const hash = firstText(item, ['sha256']); + const hash = firstText(item, ['sha256', 'sourceSha256']); + const decisionAssetUrl = firstText(item, ['decisionAssetUrl']); + const identity = item.identity as PtoSemanticIdentity | undefined; + const affectedUnits = list(item.affectedUnits).map(String); + const affectedNdf = list(item.affectedNdf).map(String); return ( -
  • -
    +
  • +
    - {id} - {title || path}{status ? ` · ${status}` : ''} + {title || path}{status ? ` · ${status}` : ''} + {identity ? : {id}} -
    -
    Path
    {path}
    - {role && <>
    Kind / role
    {role}
    } - {requirements.length > 0 && <>
    Requirements
    {requirements.join(', ')}
    } - {passCondition && <>
    Pass condition
    {passCondition}
    } - {hash && <>
    SHA-256
    {hash}
    } -
    + {identity &&
    } + {decisionAssetUrl && } {sourceAssetUrl && } - {href && Open exact source ↗ for {id}} +
    + Sources and references +
    +
    Complete stable ID
    {id}
    +
    Path
    {path}
    + {role && <>
    Kind / role
    {role}
    } + {requirements.length > 0 && <>
    Requirements
    {requirements.join(', ')}
    } + {affectedUnits.length > 0 && <>
    Affected units
    {affectedUnits.join(', ')}
    } + {affectedNdf.length > 0 && <>
    Affected NDF
    {affectedNdf.join(', ')}
    } + {passCondition && <>
    Pass condition
    {passCondition}
    } + {hash && <>
    SHA-256
    {hash}
    } +
    + {href && {decisionAssetUrl ? 'Open exact decision source' : 'Open exact source'} ↗ for {id}} +
  • ); diff --git a/docs/site/src/components/GeneratedMetadata.tsx b/docs/site/src/components/GeneratedMetadata.tsx index 7f3f07c8f..0ce3afdb3 100644 --- a/docs/site/src/components/GeneratedMetadata.tsx +++ b/docs/site/src/components/GeneratedMetadata.tsx @@ -20,7 +20,6 @@ export default function GeneratedMetadata({metadata, unit}: GeneratedMetadataPro return (
    - Generated projection · not a semantic owner

    Unit metadata

    {entries.length === 0 ?

    No generated metadata is attached to this unit.

    : (
    diff --git a/docs/site/src/components/HighLevelAssembly.tsx b/docs/site/src/components/HighLevelAssembly.tsx new file mode 100644 index 000000000..f314cf6c9 --- /dev/null +++ b/docs/site/src/components/HighLevelAssembly.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import type {PtoHighLevelAssembly} from '@site/src/types/pto'; +import styles from './PtoWorkbench.module.css'; + +export default function HighLevelAssembly({ + assembly, + chinese, +}: { + assembly: PtoHighLevelAssembly; + chinese: boolean; +}): React.JSX.Element { + const bindings = [ + ...assembly.parameters.map((binding) => ({...binding, group: chinese ? '参数' : 'Parameter'})), + ...assembly.inputs.map((binding) => ({...binding, group: chinese ? '输入' : 'Input'})), + ...assembly.outputs.map((binding) => ({...binding, group: chinese ? '输出' : 'Output'})), + ]; + return ( +
    + {assembly.form} +
    + {chinese ? '显示高层汇编 operand 到 owning ASL 的映射' : 'Show High Level Assembly operands mapped to the owning ASL'} +
    +
  • Generated encoding fields{assembly ? ` for ${assembly}` : ''}{words.length > 1 ? `, word ${wordPosition + 1}` : ''}
    FieldBit rangeFixed value
    Decoded itemBit rangeValue
    {field.name || `Field ${index + 1}`}{field.kind === 'constant' ? 'Constant' : field.name || `Field ${index + 1}`} {field.bits || 'not specified'}{field.value || 'variable'}{field.kind === 'constant' ? field.name : field.value || (field.kind === 'field' ? 'variable' : 'unspecified')}
    + + + + {bindings.map((binding, index) => ( + + + + + + + ))} + +
    {chinese ? '高层汇编 operand 的 source binding' : 'Source bindings for High Level Assembly operands'}
    {chinese ? '分组' : 'Group'}Operand{chinese ? '含义' : 'Role'}{chinese ? 'ASL 来源' : 'ASL source'}
    {binding.group}{binding.display}{binding.role}{binding.source}
    +
    + +
    + ); +} diff --git a/docs/site/src/components/InstructionComposition.tsx b/docs/site/src/components/InstructionComposition.tsx new file mode 100644 index 000000000..0fc3f3ec7 --- /dev/null +++ b/docs/site/src/components/InstructionComposition.tsx @@ -0,0 +1,310 @@ +import React, {useState} from 'react'; +import type { + PtoHighLevelAssembly, + PtoInstructionComposition, + PtoInstructionCompositionCommand, + PtoLocalizedText, + PtoNdfClause, +} from '@site/src/types/pto'; +import HighLevelAssembly from './HighLevelAssembly'; +import styles from './PtoWorkbench.module.css'; + +function localize(value: PtoLocalizedText, chinese: boolean): string { + return chinese ? value['zh-CN'] : value.en; +} + +function localizedRoute(route: string, chinese: boolean): string { + return chinese ? `/zh-CN${route}` : route; +} + +function occurrenceLabel(command: PtoInstructionCompositionCommand, chinese: boolean): string { + if (command.maxOccurrences === 0) return chinese ? '不允许出现' : 'not permitted'; + if (command.minOccurrences === command.maxOccurrences) { + return chinese ? `恰好 ${command.minOccurrences} 条` : `exactly ${command.minOccurrences}`; + } + return chinese + ? `${command.minOccurrences}–${command.maxOccurrences} 条` + : `${command.minOccurrences}–${command.maxOccurrences}`; +} + +function commandForLine( + commands: PtoInstructionCompositionCommand[], + line: string, +): PtoInstructionCompositionCommand | undefined { + return commands.find((command) => line === command.mnemonic || line.startsWith(`${command.mnemonic} `)); +} + +export default function InstructionComposition({ + composition, + chinese, + ownerSourceUrl, + ndfClauses, + apiForms, + highLevelAssembly, +}: { + composition: PtoInstructionComposition; + chinese: boolean; + ownerSourceUrl: string; + ndfClauses: PtoNdfClause[]; + apiForms: string[]; + highLevelAssembly: PtoHighLevelAssembly; +}): React.JSX.Element { + const [selectedId, setSelectedId] = useState(composition.variants[0]?.id ?? ''); + const selected = composition.variants.find((variant) => variant.id === selectedId) + ?? composition.variants[0]; + if (selected === undefined) throw new TypeError('instruction composition has no variants'); + + const labels = chinese ? { + title: '汇编格式', + apiLayer: '1. 高层汇编', + bundleLayer: '2. 完整 Bundle Assembly', + mappingLayer: '3. Bundle 操作数与参数语义', + dataFlow: '二维数据流', + intro: 'TLOAD 不是一条独立编码。BSTART.TLOAD 只选择入口和 DataType;后续 B.xxx 命令共同形成一个在 BSTOP 提交的 bundle。', + count: '命令数量', + minimum: '最短合法组合', + complete: '包含全部可选输入的完整组合', + command: '命令', + presence: '约束', + purpose: '作用', + parameters: '参数来源与缺省', + omission: '省略时', + required: '必选', + optional: '可选', + conditional: '条件必选', + forbidden: '禁止', + repeatable: '可重复', + relationships: '组合关系', + detail: '指令详情', + source: 'ASL 源', + owner: 'TLOAD owning ASL', + } : { + title: 'Assembly syntax', + apiLayer: '1. High Level Assembly', + bundleLayer: '2. Complete Bundle Assembly', + mappingLayer: '3. Bundle operand and parameter semantics', + dataFlow: 'Two-dimensional data flow', + intro: 'TLOAD is not one standalone encoding. BSTART.TLOAD selects the entry and DataType; the following B.xxx commands form one bundle committed by BSTOP.', + count: 'Command count', + minimum: 'Minimum legal bundle', + complete: 'Complete bundle with every optional input', + command: 'Command', + presence: 'Constraint', + purpose: 'Purpose', + parameters: 'Parameter source and defaults', + omission: 'When omitted', + required: 'Required', + optional: 'Optional', + conditional: 'Conditionally required', + forbidden: 'Forbidden', + repeatable: 'Repeatable', + relationships: 'Composition relationships', + detail: 'Instruction page', + source: 'ASL source', + owner: 'TLOAD owning ASL', + }; + + const sequence = (lines: string[], label: string): React.JSX.Element => ( +
    +

    {label}

    +
      + {lines.map((line, index) => { + const command = commandForLine(selected.commands, line); + return ( +
    1. + {index + 1} +
      + {line} + {command?.reference && ( + + {labels.detail} + {labels.source} ↗ + + )} +
      +
    2. + ); + })} +
    +
    + ); + + const flowGroups = [ + { + id: 'address', + label: chinese ? 'GM 地址' : 'GM address', + commands: selected.commands.filter((command) => command.mnemonic === 'B.IOR'), + items: [], + }, + { + id: 'shape', + label: chinese ? '二维形状' : '2D shape', + commands: selected.commands.filter((command) => command.mnemonic === 'B.DIM'), + items: [], + }, + { + id: 'format', + label: chinese ? '格式与类型' : 'Format and type', + commands: selected.commands.filter((command) => + command.mnemonic === 'BSTART.TLOAD' || command.mnemonic === 'B.DATR'), + items: [], + }, + { + id: 'checks', + label: chinese ? '组合检查' : 'Bundle checks', + commands: [], + items: selected.relationships, + }, + { + id: 'destination', + label: chinese ? '目的 Tile' : 'Destination Tile', + commands: selected.commands.filter((command) => + (command.mnemonic === 'B.IOT' || command.mnemonic === 'B.IOS') && + command.maxOccurrences > 0), + items: [], + }, + ].filter((group) => group.commands.length > 0 || group.items.length > 0); + + return ( +
    +
    +
    +

    {labels.title}

    +

    {labels.intro}

    +
    +
    + {labels.owner} ↗ + {ndfClauses.map((clause) => {clause.id})} +
    +
    +
    +

    {labels.apiLayer}

    + +
    + {chinese ? '显示 owning ASL 的原始 assembly alias' : 'Show the owning ASL assembly alias'} +
    {apiForms.map((form) => {form})}
    +
    +
    +
    +

    {labels.bundleLayer}

    +
    + {composition.variants.map((variant) => ( + + ))} +
    +
    +
    +

    {localize(selected.summary, chinese)}

    +

    {labels.count}: {localize(selected.canonicalCommandCount, chinese)}

    +
    +
    + {sequence(selected.minimumSequence, labels.minimum)} + {sequence(selected.completeSequence, labels.complete)} +
    +
    +

    {labels.relationships}

    +
      {selected.relationships.map((item, index) =>
    • {localize(item, chinese)}
    • )}
    +
    +
    +
    +
    +

    {labels.mappingLayer}

    +
    +

    {labels.dataFlow}

    +
    + {flowGroups.map((group, groupIndex) => ( + +
    +
    {group.label}
    + {group.commands.map((command) => ( +
    + {command.mnemonic} +

    {localize(command.role, chinese)}

    + {command.parameters.length > 0 && ( +
      + {command.parameters.map((parameter) => ( +
    • + {parameter.name}: {localize(parameter.meaning, chinese)} + {parameter.omission && {labels.omission}: {localize(parameter.omission, chinese)}} +
    • + ))} +
    + )} +
    + ))} + {group.items.length > 0 && ( +
      + {group.items.map((item, index) =>
    • {localize(item, chinese)}
    • )} +
    + )} +
    + {groupIndex < flowGroups.length - 1 && } +
    + ))} +
    +
    +
    + + + + + + {selected.commands.map((command) => ( + + + + + + + ))} + +
    {labels.command}{labels.presence}{labels.purpose}{labels.parameters}
    + {command.mnemonic} + {command.reference && ( + + {labels.detail} + {labels.source} ↗ + + )} + + + {labels[command.requirement]} + + {command.repeatable && {labels.repeatable}} + {occurrenceLabel(command, chinese)} + {localize(command.role, chinese)} + {command.parameters.length === 0 ? : ( +
    + {command.parameters.map((parameter) => ( + +
    {parameter.name}
    +
    + {localize(parameter.meaning, chinese)} + {parameter.omission && {labels.omission}: {localize(parameter.omission, chinese)}} +
    +
    + ))} +
    + )} +
    +
    +
    +
    + ); +} diff --git a/docs/site/src/components/InstructionContractSummary.tsx b/docs/site/src/components/InstructionContractSummary.tsx new file mode 100644 index 000000000..ec6eab4dc --- /dev/null +++ b/docs/site/src/components/InstructionContractSummary.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import type {PtoJsonValue} from '@site/src/types/pto'; +import styles from './PtoWorkbench.module.css'; + +function object(value: PtoJsonValue | undefined): Record { + return value !== null && typeof value === 'object' && !Array.isArray(value) + ? value as Record : {}; +} + +function strings(value: PtoJsonValue | undefined): string[] { + return Array.isArray(value) + ? value.filter((item): item is string => typeof item === 'string') + : []; +} + +export default function InstructionContractSummary({ + metadata, + chinese, +}: { + metadata: Record; + chinese: boolean; +}): React.JSX.Element | null { + const contract = object(metadata.contract); + const operands = Array.isArray(contract.operands) + ? contract.operands.map(object).filter((operand) => + typeof operand.field === 'string' && typeof operand.role === 'string') + : []; + const constraints = [...strings(contract.legality), ...strings(contract.exceptions)]; + const effects = [ + ...strings(contract.state_effects), + ...strings(contract.memory_effects), + ...strings(contract.ordering), + ]; + if (operands.length === 0 && constraints.length === 0 && effects.length === 0) return null; + return ( +
    +

    {chinese ? '指令契约' : 'Instruction contract'}

    +
    +
    +

    {chinese ? '操作数与参数' : 'Operands and parameters'}

    + {operands.length === 0 ?

    : ( +
    + {operands.map((operand) => ( + +
    {String(operand.field)}
    +
    {String(operand.role)}
    +
    + ))} +
    + )} +
    +
    +

    {chinese ? '约束、检查与 Fault' : 'Constraints, checks, and faults'}

    + {constraints.length === 0 ?

    :
      {constraints.map((item, index) =>
    • {item}
    • )}
    } +
    +
    +

    {chinese ? '状态读取、写入与结果' : 'State reads, writes, and result'}

    + {effects.length === 0 ?

    :
      {effects.map((item, index) =>
    • {item}
    • )}
    } +
    +
    +
    + ); +} diff --git a/docs/site/src/components/InstructionOverview.tsx b/docs/site/src/components/InstructionOverview.tsx new file mode 100644 index 000000000..b41541901 --- /dev/null +++ b/docs/site/src/components/InstructionOverview.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import type {PtoHighLevelAssembly, PtoJsonValue} from '@site/src/types/pto'; +import HighLevelAssembly from './HighLevelAssembly'; +import styles from './PtoWorkbench.module.css'; +import {list, record, text} from './data'; + +function stringList(value: unknown): string[] { + return list(value).map((item) => text(item)).filter(Boolean); +} + +function assemblyForms(metadata: Record): string[] { + const contract = record(metadata.contract); + const canonical = stringList(contract.canonical_assembly); + return canonical.length > 0 ? canonical : stringList(metadata.assembly); +} + +export default function InstructionOverview({ + metadata, + mnemonic, + chinese, + highLevelAssembly, +}: { + metadata: Record; + mnemonic: boolean; + chinese: boolean; + highLevelAssembly: PtoHighLevelAssembly | null; +}): React.JSX.Element | null { + const assembly = assemblyForms(metadata); + const rawBlock = stringList(metadata.block).filter((line) => !line.startsWith('#')); + const contract = record(metadata.contract); + const bundleRules = stringList(contract.block_composition); + const examples = stringList(contract.examples); + if (!mnemonic) return null; + + const requirement = (line: string): 'optional' | 'conditional' | 'required' => { + if (/optional|omitted/i.test(line)) return 'optional'; + if (/only when|requires|if selected|depending on/i.test(line)) return 'conditional'; + return 'required'; + }; + const expandSegment = (segment: string): string[] => { + const optionalGroup = segment.match(/^optional\s+(B\.[A-Z0-9.]+(?:\/B\.[A-Z0-9.]+)+)(.*)$/i); + if (optionalGroup === null) return [segment]; + return optionalGroup[1].split('/').map((mnemonic) => `optional ${mnemonic}${optionalGroup[2]}`); + }; + const inlineVariants = rawBlock.some((line) => /^[^;]+:\s*BSTART\b/.test(line) && line.includes(';')); + const metadataVariants = inlineVariants + ? rawBlock.map((line, index) => { + const segments = line.split(';').map((segment) => segment.trim()).filter(Boolean); + const first = segments.shift() ?? ''; + const colon = first.indexOf(':'); + const label = colon > 0 ? first.slice(0, colon).trim() : `Form ${index + 1}`; + const firstCommand = colon > 0 ? first.slice(colon + 1).trim() : first; + return {label, lines: [firstCommand, ...segments].flatMap(expandSegment).filter(Boolean)}; + }) + : [{label: '', lines: rawBlock.flatMap(expandSegment)}]; + const contractExampleVariants = + bundleRules.length > metadataVariants.length && + bundleRules.length === examples.length && + examples.every((example) => /\bBSTART\b/.test(example)) + ? bundleRules.map((rule, index) => ({ + label: rule.match(/^The (.+?) form\b/i)?.[1] ?? `Form ${index + 1}`, + lines: examples[index].split(';').map((line) => line.trim()).filter(Boolean), + rule, + })) + : null; + const bundleVariants = contractExampleVariants ?? metadataVariants.map((variant, index) => ({ + ...variant, + rule: bundleRules[index] ?? '', + })); + const block = bundleVariants.flatMap((variant) => variant.lines); + const commandKey = (line: string): string => + line.trim().replace(/^optional\s+/i, '').split(/\s+/, 1)[0] ?? line; + + return ( +
    +

    {chinese ? '汇编格式' : 'Assembly syntax'}

    + {block.length > 0 &&

    {chinese ? '1. 高层汇编' : '1. High Level Assembly'}

    } + {highLevelAssembly !== null ? ( + + ) : assembly.length > 0 ? ( +
    + {assembly.map((form) => {form})} +
    + ) : ( +

    {chinese ? '该条目没有独立的汇编格式。' : 'This entry has no standalone assembly form.'}

    + )} + {block.length > 0 && ( + <> +
    +

    {chinese ? '2. 完整 Bundle Assembly' : '2. Complete Bundle Assembly'}

    + {chinese ? '直接来自 owner metadata' : 'Exact owner metadata'} +
    + {bundleVariants.map((variant, variantIndex) => { + const counts = new Map(); + for (const line of variant.lines) { + const key = commandKey(line); + counts.set(key, (counts.get(key) ?? 0) + 1); + } + const minimum = variant.lines.filter((line) => requirement(line) !== 'optional'); + return ( +
    + {variant.label &&

    {variant.label}

    } + {variant.rule &&

    {variant.rule}

    } +
      + {variant.lines.map((line, index) => ( +
    1. + {index + 1} + {line} + + {requirement(line) === 'optional' + ? (chinese ? '可选' : 'Optional') + : requirement(line) === 'conditional' + ? (chinese ? '条件必选' : 'Conditional') + : (chinese ? '必选' : 'Required')} + + {(counts.get(commandKey(line)) ?? 0) > 1 && ( + {chinese ? '可重复' : 'Repeatable'} + )} + {/\bor\b|replace|mutually/i.test(line) && ( + {chinese ? '互斥' : 'Mutually exclusive'} + )} +
    2. + ))} +
    +
    {chinese ? '最短 source-declared bundle' : 'Minimum source-declared bundle'}
    +
    + {minimum.map((line, index) => {line})} +
    +
    + ); + })} + {(bundleRules.length > 0 || examples.length > 0) && ( +
    + {chinese ? '展开 Bundle 规则、互斥关系与示例' : 'Show bundle rules, relationships, and examples'} + {bundleRules.length > 0 &&
      {bundleRules.map((rule, index) =>
    • {rule}
    • )}
    } + {examples.length > 0 && ( +
    + {examples.map((example) => {example})} +
    + )} +
    + )} + + )} +
    + ); +} diff --git a/docs/site/src/components/NdfClause.tsx b/docs/site/src/components/NdfClause.tsx index aa9209d03..858ec7b16 100644 --- a/docs/site/src/components/NdfClause.tsx +++ b/docs/site/src/components/NdfClause.tsx @@ -1,34 +1,112 @@ -import React from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; +import type {PtoNdfClause} from '@site/src/types/pto'; +import SemanticIdPath from './SemanticIdPath'; import styles from './PtoWorkbench.module.css'; -import {firstText, list, record, sourceHref} from './data'; -export default function NdfClause({clauses, release}: {clauses: unknown; release?: unknown}): React.JSX.Element { - const items = list(clauses).map(record); - const commit = firstText(record(release), ['commit']); +export default function NdfClause({ + clauses, + chinese = false, +}: { + clauses: PtoNdfClause[]; + release?: unknown; + chinese?: boolean; +}): React.JSX.Element { + const canonicalIds = useMemo(() => clauses.map((clause) => clause.id), [clauses]); + const byId = useMemo(() => new Map(clauses.map((clause) => [clause.id, clause])), [clauses]); + const [order, setOrder] = useState(canonicalIds); + const [dragging, setDragging] = useState(null); + const [announcement, setAnnouncement] = useState(''); + + useEffect(() => setOrder(canonicalIds), [canonicalIds]); + + function move(id: string, delta: -1 | 1): void { + setOrder((current) => { + const from = current.indexOf(id); + const to = from + delta; + if (from < 0 || to < 0 || to >= current.length) return current; + const next = [...current]; + [next[from], next[to]] = [next[to], next[from]]; + setAnnouncement(`${id} ${delta < 0 ? 'moved up' : 'moved down'} for this page session`); + return next; + }); + } + + function drop(target: string): void { + if (dragging === null || dragging === target) return; + setOrder((current) => { + const next = current.filter((id) => id !== dragging); + next.splice(next.indexOf(target), 0, dragging); + return next; + }); + setAnnouncement(`${dragging} moved before ${target} for this page session`); + setDragging(null); + } + + function reset(): void { + setOrder(canonicalIds); + setAnnouncement('NDF display order restored from canonical source order'); + } + return (
    - Normative · embedded verbatim -

    NDF clauses

    - {items.length === 0 ?

    No NDF clause is attached to this unit.

    : ( +
    +
    +

    {chinese ? 'NDF 条款' : 'NDF clauses'}

    +

    {chinese ? '正文来自 owning ASL。拖拽或按钮只临时改变当前页面显示顺序。' : 'Bodies come from owning ASL. Dragging or buttons change only this page-session view order.'}

    +
    + +
    +

    {announcement}

    + {order.length === 0 ?

    No NDF clause is attached to this unit.

    : (
      - {items.map((clause, index) => { - const id = firstText(clause, ['id'], `Clause ${index + 1}`); - const body = firstText(clause, ['body', 'text', 'content']); - const sourcePath = firstText(clause, ['sourcePath', 'path']); - const startLine = firstText(clause, ['startLine']); - const endLine = firstText(clause, ['endLine']); - const generatedHref = commit && sourcePath - ? `https://github.com/PTO-ISA/pto-spec/blob/${encodeURIComponent(commit)}/${sourcePath}#L${startLine || '1'}${endLine ? `-L${endLine}` : ''}` - : ''; - const href = sourceHref(record(clause.source)) || sourceHref(clause) || generatedHref; - const meta = [firstText(clause, ['kind']), firstText(clause, ['level']), firstText(clause, ['status'])].filter(Boolean).join(' · '); + {order.map((id, index) => { + const clause = byId.get(id); + if (clause === undefined) return null; + const meta = [clause.kind, clause.level, clause.status].filter(Boolean).join(' · '); return ( -
    • -
      - {id}{meta ? ` — ${meta}` : ''} -

      {body}

      - {href && Open exact clause source ↗} -
      +
    • event.preventDefault()} + onDrop={() => drop(id)} + > +
      +
      + +
      + +

      {clause.kind === 'contract' ? (chinese ? '规范契约' : 'Normative contract') : clause.kind}

      +
      + {meta && {meta}} +
      +

      {clause.text}

      +
      + + +
      +
      + {chinese ? '来源与引用' : 'Sources and references'} +
      +
      {chinese ? '完整稳定 ID' : 'Complete stable ID'}
      {clause.id}
      +
      {chinese ? '来源路径' : 'Source path'}
      {clause.sourcePath}
      +
      {chinese ? '适用单元' : 'Affected units'}
      {clause.affectedUnits.join(', ')}
      +
      {chinese ? '源 SHA-256' : 'Source SHA-256'}
      {clause.sourceSha256}
      +
      {chinese ? '条款 SHA-256' : 'Clause SHA-256'}
      {clause.clauseSha256}
      +
      + {chinese ? '打开精确 canonical source' : 'Open exact canonical source'} ↗ +
      +
    • ); })} diff --git a/docs/site/src/components/NdfGlobalExplorer.tsx b/docs/site/src/components/NdfGlobalExplorer.tsx index 286f4a6e0..8ca814c3e 100644 --- a/docs/site/src/components/NdfGlobalExplorer.tsx +++ b/docs/site/src/components/NdfGlobalExplorer.tsx @@ -154,12 +154,12 @@ function WebGlExplorer({graph, initialQuery}: {graph: PtoNdfGraphData; initialQu const themeStyle = getComputedStyle(document.documentElement); const cssColor = (name: string, fallback: string) => themeStyle.getPropertyValue(name).trim() || fallback; const colorByKind: Record = { - ndf: cssColor('--ifm-color-primary', '#1688b8'), - asl: cssColor('--ifm-color-info-dark', '#486fd1'), + ndf: cssColor('--ifm-color-primary', '#f15b61'), + asl: cssColor('--pto-highlight', '#d9b85c'), avs: cssColor('--ifm-color-success-dark', '#2f8d60'), adr: cssColor('--ifm-color-warning-dark', '#98691d'), }; - const edgeColor = cssColor('--ifm-color-emphasis-400', dark ? '#526070' : '#bcc6d1'); + const edgeColor = cssColor('--ifm-color-emphasis-400', dark ? '#4a4547' : '#b9b0a8'); void api.react(plotElement, [ {type: 'scattergl', mode: 'lines', x: edgeX, y: edgeY, hoverinfo: 'skip', line: {color: edgeColor, width: 1}}, { @@ -167,7 +167,7 @@ function WebGlExplorer({graph, initialQuery}: {graph: PtoNdfGraphData; initialQu text: nodes.map((node, index) => `${nodeId(node, index)} · ${firstText(node, ['kind'], 'node')}`), customdata: nodes.map((_, index) => index), hovertemplate: '%{text}', - marker: {color: nodes.map((node) => colorByKind[firstText(node, ['kind'])] ?? '#13a8d2'), size: 7, opacity: .88}, + marker: {color: nodes.map((node) => colorByKind[firstText(node, ['kind'])] ?? '#f15b61'), size: 7, opacity: .88}, }, ], { autosize: true, dragmode: 'pan', hovermode: 'closest', margin: {l: 16, r: 16, t: 16, b: 16}, diff --git a/docs/site/src/components/PortalShell.module.css b/docs/site/src/components/PortalShell.module.css new file mode 100644 index 000000000..af66f0fdd --- /dev/null +++ b/docs/site/src/components/PortalShell.module.css @@ -0,0 +1,142 @@ +.shell { + display: grid; + grid-template-columns: 15rem minmax(0, 1fr); + min-height: calc(100vh - var(--ifm-navbar-height)); +} + +.desktopRail { + position: sticky; + top: var(--ifm-navbar-height); + align-self: start; + height: calc(100vh - var(--ifm-navbar-height)); + padding: 1.25rem 0.8rem 2rem; + overflow-y: auto; + background: var(--ifm-background-surface-color); + border-right: 1px solid var(--pto-border); +} + +.content { + min-width: 0; +} + +.navigation { + display: grid; + gap: 1.35rem; +} + +.currentPage { + display: grid; + gap: 0.25rem; + padding: 0.7rem; + background: var(--pto-surface-subtle); + border: 1px solid var(--pto-border); +} + +.currentPage span, +.navigationGroup summary { + margin: 0 0.55rem 0.45rem; + color: var(--pto-text-muted); + font-size: 0.7rem; + font-weight: 750; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.currentPage a { + color: var(--pto-nav-active); + font-weight: 750; + overflow-wrap: anywhere; +} + +.navigationGroup summary { + cursor: pointer; +} + +.navigationGroup[open] summary { + margin-bottom: 0.45rem; +} + +.navigationGroup ul { + padding: 0; + margin: 0; + list-style: none; +} + +.navLink, +.navLinkActive { + display: block; + padding: 0.48rem 0.65rem; + color: var(--ifm-font-color-base); + border-left: 3px solid transparent; + border-radius: 0 3px 3px 0; + font-size: 0.88rem; + line-height: 1.35; + text-decoration: none; +} + +.navLink:hover { + color: var(--pto-nav-active); + background: var(--pto-surface-subtle); + text-decoration: none; +} + +.navLinkActive { + color: var(--pto-nav-active); + background: var(--pto-surface-focus); + border-left-color: var(--ifm-color-primary); + font-weight: 750; +} + +.mobileNavigation { + display: none; +} + +@media (max-width: 996px) { + .shell { + display: block; + } + + .desktopRail { + display: none; + } + + .mobileNavigation { + display: block; + padding: 0.65rem 1rem; + background: var(--ifm-background-surface-color); + border-bottom: 1px solid var(--pto-border); + } + + .mobileNavigation details { + max-width: 100%; + } + + .mobileNavigation > details > summary { + width: fit-content; + padding: 0.48rem 0.7rem; + color: var(--pto-nav-active); + cursor: pointer; + border: 1px solid var(--pto-border); + border-radius: 3px; + font-size: 0.88rem; + font-weight: 750; + } + + .mobileNavigation > details[open] > summary { + margin-bottom: 0.85rem; + } + + .mobileNavigation .navigation { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.8rem; + padding: 0.75rem; + background: var(--ifm-background-color); + border: 1px solid var(--pto-border); + } +} + +@media (max-width: 620px) { + .mobileNavigation .navigation { + grid-template-columns: 1fr; + } +} diff --git a/docs/site/src/components/PortalShell.tsx b/docs/site/src/components/PortalShell.tsx new file mode 100644 index 000000000..6fdf0f1b4 --- /dev/null +++ b/docs/site/src/components/PortalShell.tsx @@ -0,0 +1,36 @@ +import React, {type ReactNode} from 'react'; +import Layout from '@theme/Layout'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import SpecNavigation from './SpecNavigation'; +import styles from './PortalShell.module.css'; + +export default function PortalShell({ + title, + description, + currentPageLabel, + children, +}: { + title: string; + description: string; + currentPageLabel?: string; + children: ReactNode; +}): React.JSX.Element { + const {i18n} = useDocusaurusContext(); + const chinese = i18n.currentLocale === 'zh-CN'; + return ( + +
      + +
      +
      + {chinese ? '浏览规范' : 'Browse specification'} + +
      +
      +
      {children}
      +
      +
      + ); +} diff --git a/docs/site/src/components/PtoWorkbench.module.css b/docs/site/src/components/PtoWorkbench.module.css index a18590613..0795de497 100644 --- a/docs/site/src/components/PtoWorkbench.module.css +++ b/docs/site/src/components/PtoWorkbench.module.css @@ -2,12 +2,37 @@ --pto-panel: var(--ifm-background-surface-color); --pto-line: var(--ifm-color-emphasis-300); --pto-muted: var(--ifm-color-emphasis-700); - --pto-accent: var(--ifm-color-primary); + --pto-accent: var(--pto-nav-active); + --pto-asl-plain: #24292f; + --pto-asl-comment: #57606a; + --pto-asl-keyword: #8250df; + --pto-asl-boolean: #cf222e; + --pto-asl-number: #0550ae; + --pto-asl-string: #0a3069; + --pto-asl-builtin: #116329; + --pto-asl-function: #953800; + --pto-asl-operator: #cf222e; + --pto-asl-punctuation: #57606a; max-width: 1480px; + width: 100%; + box-sizing: border-box; margin: 0 auto; padding: 2rem clamp(1rem, 3vw, 3rem) 4rem; } +[data-theme='dark'] .page { + --pto-asl-plain: #e7e2da; + --pto-asl-comment: #c2bbb2; + --pto-asl-keyword: #ff8d96; + --pto-asl-boolean: #ffd34f; + --pto-asl-number: #ffd34f; + --pto-asl-string: #ffe071; + --pto-asl-builtin: #8fc3a8; + --pto-asl-function: #f2a766; + --pto-asl-operator: #ff4d5a; + --pto-asl-punctuation: #b9b4ac; +} + .hero, .toolbar, .identity, @@ -114,6 +139,229 @@ var(--pto-panel); } +.readingLead { + display: grid; + grid-template-columns: minmax(0, 1.05fr) minmax(22rem, .95fr); + gap: clamp(1.5rem, 4vw, 4rem); + align-items: start; + padding: clamp(1.25rem, 3vw, 2rem); + margin-bottom: 1rem; +} + +.overviewCopy p { + max-width: 72ch; + margin-bottom: 0; + font-size: 1.08rem; + line-height: 1.75; +} + +.assemblyPanel { + min-width: 0; + padding-left: clamp(1rem, 3vw, 2rem); + border-left: 1px solid var(--pto-line); +} + +.assemblyForms { + display: grid; + min-width: 0; + gap: .65rem; +} + +.assemblyForms code { + display: block; + width: 100%; + max-width: 100%; + min-width: 0; + box-sizing: border-box; + padding: .85rem 1rem; + overflow-x: auto; + border: 1px solid color-mix(in srgb, var(--pto-accent) 30%, var(--pto-line)); + border-radius: 6px; + color: var(--ifm-font-color-base); + background: color-mix(in srgb, var(--pto-accent) 5%, var(--ifm-pre-background)); + font-size: .92rem; + line-height: 1.6; + white-space: pre; +} + +.highLevelAssembly { + display: grid; + gap: .65rem; +} + +.highLevelAssemblyForm { + display: block; + padding: .9rem 1rem; + color: var(--pto-highlight); + background: color-mix(in srgb, var(--pto-highlight) 7%, var(--ifm-pre-background)); + border: 1px solid color-mix(in srgb, var(--pto-highlight) 42%, var(--pto-line)); + font-size: clamp(.84rem, 1.35vw, 1rem); + font-weight: 750; + line-height: 1.55; + overflow-wrap: anywhere; + white-space: normal; +} + +.highLevelAssembly details { + padding: .55rem .7rem; + border-left: 3px solid var(--ifm-color-primary); +} + +.highLevelAssembly summary { + cursor: pointer; + color: var(--pto-muted); + font-size: .8rem; + font-weight: 700; +} + +.highLevelBindingViewport { + margin-top: .65rem; + overflow-x: auto; +} + +.highLevelBindingViewport table { + width: 100%; + min-width: 42rem; + margin: 0; +} + +.genericBundleHeading { + display: flex; + gap: 1rem; + align-items: baseline; + justify-content: space-between; + padding-top: 1rem; + margin-top: 1rem; + border-top: 1px solid var(--pto-line); +} + +.genericBundleHeading span { + color: var(--pto-muted); + font-size: .75rem; +} + +.genericBundleSequence { + padding: 0; + margin: .75rem 0 1rem; + list-style: none; +} + +.genericBundleVariant { + padding: .75rem; + margin-bottom: .75rem; + border: 1px solid var(--pto-line); + border-radius: 6px; +} + +.genericBundleVariant > h4 { + margin-bottom: .35rem; + color: var(--pto-accent); +} + +.genericBundleRule { + margin: 0 0 .65rem; + color: var(--pto-muted); + font-size: .82rem; +} + +.genericBundleVariant > h5 { + margin: .75rem 0 .45rem; + font-size: .8rem; + text-transform: uppercase; +} + +.genericBundleSequence li { + display: grid; + grid-template-columns: 1.8rem minmax(12rem, 1fr) auto auto auto; + gap: .5rem; + align-items: center; + padding: .5rem 0; + border-top: 1px solid var(--pto-line); +} + +.genericBundleSequence li > span { + display: grid; + width: 1.5rem; + height: 1.5rem; + place-items: center; + border: 1px solid var(--pto-line); + border-radius: 50%; + font-size: .7rem; +} + +.genericBundleSequence em { + padding: .12rem .38rem; + border: 1px solid var(--pto-line); + border-radius: 999px; + color: var(--ifm-font-color-base); + font-size: .68rem; + font-style: normal; + font-weight: 700; + white-space: nowrap; +} + +.bundleRequirement_optional, +.bundleRequirement_conditional { + background: color-mix(in srgb, #9a741d 10%, transparent); +} + +.bundleRequirement_required { + background: color-mix(in srgb, #2d7659 10%, transparent); +} + +.bundleRequirement_repeatable, +.bundleRequirement_mutual { + border-style: dashed !important; +} + +.minimumBundleSequence { + display: grid; + gap: .35rem; + padding-left: 1rem; + margin-bottom: 1rem; + border-left: 2px solid var(--pto-accent); +} + +.genericBundleDetails > summary { + padding: .55rem 0; + cursor: pointer; + font-weight: 700; +} + +.contractSummaryGrid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: .75rem; +} + +.contractSummaryGrid > article { + min-width: 0; + padding: .8rem; + border: 1px solid var(--pto-line); + border-radius: 6px; +} + +.contractSummaryGrid h3 { + margin-bottom: .55rem; + font-size: 1rem; +} + +.contractSummaryGrid ul { + padding-left: 1.1rem; + margin: 0; +} + +.contractOperands { + display: grid; + grid-template-columns: minmax(7rem, auto) minmax(0, 1fr); + gap: .4rem .65rem; + margin: 0; +} + +.contractOperands dd { + margin: 0; +} + .readerGuideHeader { display: flex; gap: 1rem; @@ -218,8 +466,10 @@ } .readerTableViewport table { + display: table; width: 100%; margin: 0; + overflow: visible; } .readerTableViewport caption { @@ -304,6 +554,9 @@ } .source { + width: 100%; + max-width: 100%; + min-width: 0; max-height: 38rem; overflow: auto; border: 1px solid var(--pto-line); @@ -311,6 +564,41 @@ background: var(--ifm-pre-background); } +.aslDefinition { + padding: clamp(1.15rem, 2.5vw, 1.75rem); + margin-bottom: 1rem; + border-color: color-mix(in srgb, var(--pto-accent) 34%, var(--pto-line)); +} + +.aslIntroduction { + max-width: 86ch; + color: var(--pto-muted); +} + +.aslPhaseHeading { + padding-top: 1.25rem; + margin-top: 1.25rem; + border-top: 1px solid var(--pto-line); +} + +.symbolsViewport { + overflow-x: auto; +} + +.symbolsTable { + width: 100%; + margin: 0; +} + +.symbolsTable th, +.symbolsTable td { + vertical-align: top; +} + +.symbolsTable tbody th { + white-space: nowrap; +} + .source pre { margin: 0; overflow: visible; @@ -319,6 +607,120 @@ white-space: pre; } +.sourceLine { + display: flex; + width: max-content; + min-width: 100%; + min-height: 1.55em; +} + +.sourceLineNumber { + flex: 0 0 4.25rem; + padding-right: 1rem; + color: var(--pto-muted); + font-weight: 600; + text-align: right; + user-select: none; +} + +.sourceLineText { + flex: 0 0 auto; + padding-right: 1rem; + padding-left: 1rem; + border-left: 1px solid color-mix(in srgb, var(--pto-line) 72%, transparent); +} + +.sourceWrap .sourceLine { + width: 100%; +} + +.sourceWrap .sourceLineText { + min-width: 0; + flex: 1 1 auto; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.sourceDisclosure { + margin-top: .85rem; +} + +.sourceDisclosure > summary { + padding: .55rem 0; + cursor: pointer; + font-weight: 700; +} + +.sourceDisclosure .source { + max-height: 46rem; + margin-top: .35rem; +} + +.sourceLedger { + margin-top: 1rem; + background: color-mix(in srgb, var(--pto-accent) 3%, var(--pto-panel)); +} + +.ledgerGrid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1px; + padding: 1px; + overflow: hidden; + border-radius: 6px; + background: var(--pto-line); +} + +.ledgerItem { + min-width: 0; + padding: .85rem 1rem; + background: var(--pto-panel); +} + +.ledgerItem dt { + margin-bottom: .35rem; + color: var(--pto-muted); + font-size: .75rem; + font-weight: 800; + letter-spacing: .06em; + text-transform: uppercase; +} + +.ledgerItem dd { + margin: 0; + overflow-wrap: anywhere; +} + +.ledgerItem code { + white-space: normal; + overflow-wrap: anywhere; +} + +.ownerLedger { + padding-top: 1rem; + margin-top: 1rem; + border-top: 1px solid var(--pto-line); +} + +.ownerLedger ul { + display: grid; + gap: .4rem; + padding-left: 1.2rem; + margin-bottom: 0; +} + +.ownerLedger li, +.ownerLedger code { + overflow-wrap: anywhere; + white-space: normal; +} + +.ownerLedger a { + text-decoration: underline; + text-decoration-thickness: .12em; + text-underline-offset: .16em; +} + .waveDromViewport { min-height: 4.75rem; overflow-x: auto; @@ -457,25 +859,725 @@ .evidenceEntry > summary { display: grid; - grid-template-columns: minmax(8rem, .45fr) minmax(0, 1fr); + grid-template-columns: minmax(0, 1fr) auto; gap: .75rem; + align-items: center; cursor: pointer; } -.evidenceDetails { +.evidenceIdentity { display: grid; - grid-template-columns: minmax(7rem, auto) minmax(0, 1fr); - gap: .35rem .75rem; - margin: .75rem 0; + gap: .35rem; + min-width: 0; } -.evidenceDetails dt { - color: var(--pto-muted); - font-weight: 700; +.evidenceSummary { + align-self: center; } -.evidenceDetails dd { - margin: 0; +.evidenceFacets { + display: flex; + flex-wrap: wrap; + gap: .3rem; +} + +.evidenceFacets span { + padding: .16rem .42rem; + border: 1px solid color-mix(in srgb, var(--pto-accent) 28%, var(--pto-line)); + border-radius: 999px; + color: var(--pto-accent); + background: color-mix(in srgb, var(--pto-accent) 7%, transparent); + font-family: var(--ifm-font-family-monospace); + font-size: .7rem; + font-weight: 700; + letter-spacing: .02em; +} + +.decisionBody { + padding: .9rem 1rem; + margin: .75rem 0; + border-left: 3px solid var(--pto-accent); + background: color-mix(in srgb, var(--pto-accent) 4%, var(--pto-panel)); +} + +.decisionBody h4 { + margin: 0 0 .65rem; +} + +.decisionBody > :last-child { + margin-bottom: 0; +} + +.composition { + scroll-margin-top: calc(var(--ifm-navbar-height) + 1rem); +} + +.compositionHeader, +.ndfSectionHeader { + display: flex; + gap: 1rem; + align-items: flex-start; + justify-content: space-between; +} + +.assemblyLayer { + padding-top: 1rem; + margin-top: 1rem; + border-top: 1px solid var(--pto-line); +} + +.assemblyLayer > h3 { + margin-bottom: .75rem; +} + +.compositionHeader h2, +.ndfSectionHeader h2 { + margin-bottom: .35rem; +} + +.compositionOwners { + display: flex; + flex: 0 0 auto; + flex-direction: column; + gap: .3rem; + align-items: flex-end; + font-size: .82rem; +} + +.compositionTabs { + display: flex; + flex-wrap: wrap; + gap: .45rem; + margin: 0 0 1rem; +} + +.compositionTab, +.compositionTabActive { + padding: .5rem .75rem; + border: 1px solid var(--pto-line); + border-radius: 999px; + color: var(--ifm-font-color-base); + background: var(--pto-panel); + cursor: pointer; + font-weight: 700; +} + +.compositionTabActive { + border-color: var(--pto-accent); + color: var(--pto-accent); + background: color-mix(in srgb, var(--pto-accent) 9%, var(--pto-panel)); +} + +.compositionTab:focus-visible, +.compositionTabActive:focus-visible { + outline: 2px solid var(--pto-accent); + outline-offset: 2px; +} + +.compositionPanel { + border-top: 1px solid var(--pto-line); +} + +.compositionLead { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(14rem, .35fr); + gap: 1rem; + align-items: start; + padding: 1rem 0; +} + +.compositionLead p { + margin: 0; +} + +.compositionLead p:last-child { + padding: .65rem .75rem; + border-radius: 5px; + background: color-mix(in srgb, var(--pto-accent) 7%, var(--pto-panel)); +} + +.bundleExamples { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin-bottom: 1rem; +} + +.bundleExample { + min-width: 0; + padding: .9rem; + border: 1px solid var(--pto-line); + border-radius: 7px; + background: var(--pto-panel); +} + +.bundleExample h4, +.bundleRelationships h4 { + margin: 0 0 .75rem; +} + +.bundleRail { + padding: 0; + margin: 0; + list-style: none; +} + +.bundleRail li { + position: relative; + display: grid; + grid-template-columns: 1.8rem minmax(0, 1fr); + gap: .65rem; + padding-bottom: .8rem; +} + +.bundleRail li:not(:last-child)::after { + position: absolute; + top: 1.45rem; + bottom: .05rem; + left: .78rem; + width: 1px; + background: var(--pto-line); + content: ''; +} + +.bundleStepNumber { + position: relative; + z-index: 1; + display: grid; + width: 1.6rem; + height: 1.6rem; + place-items: center; + border: 1px solid var(--pto-accent); + border-radius: 50%; + color: var(--ifm-font-color-base); + background: var(--ifm-background-color); + font-size: .72rem; + font-weight: 800; +} + +.bundleRail code { + display: block; + overflow-wrap: anywhere; +} + +.bundleStepLinks, +.commandLinks { + display: flex; + flex-wrap: wrap; + gap: .6rem; + margin-top: .28rem; + font-size: .75rem; + font-weight: 500; +} + +.bundleStepLinks a, +.commandLinks a, +.compositionOwners a, +.semanticSourceHeader > a, +.semanticStageNav a { + display: inline-flex; + min-height: 1.6rem; + align-items: center; +} + +.commandLinks { + flex-direction: column; + gap: .2rem; +} + +.bundleRelationships { + padding: .8rem 1rem; + margin-bottom: 1rem; + border-left: 3px solid var(--pto-accent); + background: color-mix(in srgb, var(--pto-accent) 4%, var(--pto-panel)); +} + +.bundleRelationships ul { + margin-bottom: 0; +} + +.bundleDataFlow { + padding: .9rem; + margin-bottom: 1rem; + border: 1px solid var(--pto-line); + border-radius: 7px; + background: color-mix(in srgb, var(--pto-accent) 3%, var(--pto-panel)); +} + +.bundleDataFlow > h4 { + margin: 0 0 .8rem; +} + +.bundleFlowTrack { + display: flex; + gap: .5rem; + align-items: stretch; +} + +.bundleFlowNode { + flex: 1 1 0; + min-width: 0; + padding: .7rem; + border: 1px solid var(--pto-line); + border-radius: 6px; + background: var(--pto-panel); +} + +.bundleFlowNode h5 { + margin: 0 0 .55rem; + color: var(--pto-accent); + font-size: .8rem; + text-transform: uppercase; +} + +.bundleFlowCommand + .bundleFlowCommand { + padding-top: .55rem; + margin-top: .55rem; + border-top: 1px solid var(--pto-line); +} + +.bundleFlowCommand p { + margin: .35rem 0; + font-size: .8rem; +} + +.bundleFlowCommand ul { + padding-left: 1rem; + margin: .35rem 0 0; + font-size: .75rem; +} + +.bundleFlowCommand small { + display: block; + color: var(--pto-muted); +} + +.bundleFlowArrow { + display: grid; + place-items: center; + color: var(--pto-accent); + font-size: 1.25rem; +} + +.compositionTable { + min-width: 68rem; +} + +.compositionTable td:nth-child(2) small { + display: block; + margin-top: .35rem; + color: var(--pto-muted); +} + +.requirement { + display: inline-block; + padding: .15rem .42rem; + border-radius: 999px; + font-size: .72rem; + font-weight: 800; + letter-spacing: .02em; + text-transform: uppercase; +} + +.requirement_required { + color: var(--pto-status-positive); + background: color-mix(in srgb, var(--pto-status-positive) 12%, transparent); +} + +.requirement_optional { + color: var(--ifm-font-color-base); + background: color-mix(in srgb, var(--pto-accent) 11%, transparent); +} + +.requirement_conditional { + color: var(--ifm-font-color-base); + background: color-mix(in srgb, var(--pto-status-caution) 14%, transparent); +} + +.requirement_forbidden { + color: var(--pto-status-danger); + background: color-mix(in srgb, var(--pto-status-danger) 10%, transparent); +} + +.repeatable { + display: inline-block; + padding: .15rem .42rem; + margin-left: .3rem; + border: 1px dashed var(--pto-line); + border-radius: 999px; + color: var(--ifm-font-color-base); + font-size: .72rem; + font-weight: 700; +} + +.encodingScopeNote { + padding: .7rem .85rem; + border-left: 3px solid var(--pto-accent); + background: color-mix(in srgb, var(--pto-accent) 5%, var(--pto-panel)); +} + +.semanticExecution { + scroll-margin-top: calc(var(--ifm-navbar-height) + 1rem); +} + +.semanticStageNav { + display: flex; + flex-wrap: wrap; + gap: .35rem; + margin: 1rem 0; +} + +.semanticStageNav a { + padding: .32rem .55rem; + border: 1px solid var(--pto-line); + border-radius: 999px; + color: var(--ifm-font-color-base); + font-size: .78rem; +} + +.semanticStages { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 1.25rem; +} + +.semanticStage { + min-width: 0; + scroll-margin-top: calc(var(--ifm-navbar-height) + 1rem); + padding-top: 1rem; + border-top: 1px solid var(--pto-line); +} + +.semanticStage > header h3 { + margin-bottom: .3rem; +} + +.semanticFacts { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); + gap: .65rem; + margin: .75rem 0; +} + +.semanticFact { + padding: .7rem .8rem; + border: 1px solid var(--pto-line); + border-radius: 6px; + background: var(--pto-panel); +} + +.semanticFact h4 { + margin: 0 0 .45rem; + font-size: .82rem; +} + +.semanticFact ul { + padding-left: 1rem; + margin: 0; + font-size: .84rem; +} + +.semanticFact_checks, +.semanticFact_faults { + border-left: 3px solid var(--pto-status-caution); +} + +.semanticFact_writes, +.semanticFact_commit { + border-left: 3px solid var(--pto-status-positive); +} + +.semanticGap { + padding: .7rem .85rem; + margin-top: .65rem; + border-left: 3px solid var(--pto-status-caution); + background: color-mix(in srgb, var(--pto-status-caution) 7%, var(--pto-panel)); +} + +.semanticGap p { + margin: .25rem 0 0; +} + +.semanticOwner, +.semanticShared { + min-width: 0; + overflow: hidden; + padding: .85rem; + margin-top: .75rem; + border: 1px solid var(--pto-line); + border-radius: 7px; +} + +.semanticShared > summary { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: .3rem .5rem; + cursor: pointer; +} + +.semanticShared > summary span { + color: var(--pto-muted); + font-size: .72rem; + font-weight: 700; + text-transform: uppercase; +} + +.semanticShared > summary small { + grid-column: 1 / -1; + color: var(--pto-muted); +} + +.semanticShared[open] > summary { + padding-bottom: .7rem; + margin-bottom: .7rem; + border-bottom: 1px solid var(--pto-line); +} + +.semanticOwner { + border-left: 4px solid var(--pto-accent); +} + +.semanticShared { + border-left: 4px solid color-mix(in srgb, var(--pto-muted) 60%, var(--pto-line)); + background: color-mix(in srgb, var(--pto-muted) 3%, var(--pto-panel)); +} + +.semanticSourceHeader { + display: flex; + gap: 1rem; + align-items: flex-start; + justify-content: space-between; +} + +.semanticSourceHeader > div { + min-width: 0; +} + +.semanticSourceHeader span { + color: var(--pto-muted); + font-size: .7rem; + font-weight: 800; + letter-spacing: .06em; + text-transform: uppercase; +} + +.semanticSourceHeader h4 { + margin: .18rem 0 .25rem; +} + +.semanticSourceHeader p { + margin-bottom: .65rem; +} + +.semanticProvenance, +.recordProvenance { + margin-top: .7rem; + border-top: 1px solid var(--pto-line); +} + +.semanticProvenance summary, +.recordProvenance summary { + padding: .55rem 0; + cursor: pointer; + font-weight: 700; +} + +.semanticProvenance dl, +.recordProvenance dl { + display: grid; + grid-template-columns: minmax(8rem, auto) minmax(0, 1fr); + gap: .35rem .75rem; +} + +.semanticProvenance dd, +.recordProvenance dd { + min-width: 0; + margin: 0; + overflow-wrap: anywhere; +} + +.semanticIdentityPath { + display: flex; + min-width: 0; + gap: .45rem; + align-items: center; + justify-content: space-between; +} + +.semanticIdentityPath ol { + display: flex; + min-width: 0; + flex-wrap: wrap; + gap: .28rem; + padding: 0; + margin: 0; + list-style: none; +} + +.semanticIdentityPath li { + display: grid; + grid-template-columns: auto auto; + gap: .28rem; + align-items: baseline; + padding: .18rem .42rem; + border: 1px solid var(--pto-line); + border-radius: 5px; + color: var(--ifm-font-color-base); + background: color-mix(in srgb, var(--pto-accent) 4%, var(--pto-panel)); + font-family: var(--ifm-font-family-monospace); +} + +.semanticIdentityPath li > span { + color: var(--pto-muted); + font-family: var(--ifm-font-family-base); + font-size: .6rem; + font-weight: 700; + text-transform: uppercase; +} + +.semanticIdentityPath li > strong { + font-size: .72rem; + overflow-wrap: anywhere; + white-space: nowrap; +} + +.identityFacet_owner, +.identityFacet_decision { + border-left: 3px solid var(--pto-accent) !important; +} + +.copyIdentity { + flex: 0 0 auto; + min-width: 3rem; + min-height: 1.75rem; + padding: .28rem .45rem; + border: 1px solid var(--pto-line); + border-radius: 5px; + color: var(--ifm-font-color-base); + background: transparent; + cursor: pointer; + font-size: .7rem; +} + +.evidenceIdentityAction { + display: flex; + justify-content: flex-end; + margin-top: .45rem; +} + +.copyIdentity:focus-visible, +.dragHandle:focus-visible, +.ndfOrderControls button:focus-visible { + outline: 2px solid var(--pto-accent); + outline-offset: 2px; +} + +.ndfCardHeader { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + gap: .7rem; + align-items: start; +} + +.ndfCardHeader h3 { + margin: .55rem 0 0; + font-size: 1rem; +} + +.dragHandle { + padding: .35rem .45rem; + border: 1px solid var(--pto-line); + border-radius: 5px; + color: var(--pto-muted); + background: transparent; + cursor: grab; + touch-action: none; + white-space: nowrap; +} + +.dragHandle:active { + cursor: grabbing; +} + +.ndfStatus { + color: var(--pto-muted); + font-size: .75rem; + white-space: nowrap; +} + +.ndfBody { + padding: .8rem 0; + margin: 0; + white-space: pre-wrap; +} + +.ndfOrderControls { + display: flex; + gap: .4rem; +} + +.ndfOrderControls button { + padding: .3rem .5rem; + border: 1px solid var(--pto-line); + border-radius: 5px; + color: var(--ifm-font-color-base); + background: var(--pto-panel); + cursor: pointer; +} + +.ndfOrderControls button:disabled { + cursor: not-allowed; + opacity: .5; +} + +.sourceLedgerDetails > summary, +.walkthroughDetails > summary { + padding: .65rem 0; + cursor: pointer; + font-weight: 700; +} + +.sourceLedgerDetails[open] > summary, +.walkthroughDetails[open] > summary { + margin-bottom: .75rem; + border-bottom: 1px solid var(--pto-line); +} + +.parameterList { + display: grid; + grid-template-columns: minmax(7rem, auto) minmax(18rem, 1fr); + gap: .45rem .7rem; + margin: 0; +} + +.parameterList dt { + font-weight: 700; +} + +.parameterList dd { + margin: 0; +} + +.parameterList dd span { + display: block; + margin-top: .2rem; + color: var(--pto-muted); +} + +.evidenceDetails { + display: grid; + grid-template-columns: minmax(7rem, auto) minmax(0, 1fr); + gap: .35rem .75rem; + margin: .75rem 0; +} + +.evidenceDetails dt { + color: var(--pto-muted); + font-weight: 700; +} + +.evidenceDetails dd { + margin: 0; } .evidenceSource { @@ -510,6 +1612,41 @@ white-space: pre-wrap; } +.clauseEmbedded:first-child { + border-top: 0; +} + +.clauseEmbedded article { + padding: .9rem 0; +} + +.clauseHeader { + display: flex; + gap: .75rem 1rem; + align-items: baseline; + justify-content: space-between; +} + +.clauseHeader h3 { + margin: 0; + font-family: var(--ifm-font-family-monospace); + font-size: .92rem; +} + +.clauseHeader span { + color: var(--pto-muted); + font-size: .78rem; + white-space: nowrap; +} + +.clauseEmbedded p { + padding: .85rem 1rem; + margin: .75rem 0; + border-left: 3px solid var(--pto-accent); + background: color-mix(in srgb, var(--pto-accent) 4%, transparent); + line-height: 1.7; +} + .metadataList { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -732,11 +1869,20 @@ @media (max-width: 996px) { .grid, + .readingLead, + .compositionLead, .graphLayout { grid-template-columns: minmax(0, 1fr); flex-direction: column; } + .assemblyPanel { + padding-top: 1.25rem; + padding-left: 0; + border-top: 1px solid var(--pto-line); + border-left: 0; + } + .identity { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -748,6 +1894,22 @@ .plot { display: none; } + + .bundleExamples { + grid-template-columns: 1fr; + } + + .bundleFlowTrack { + flex-direction: column; + } + + .bundleFlowArrow { + transform: rotate(90deg); + } + + .contractSummaryGrid { + grid-template-columns: 1fr; + } } @media (max-width: 620px) { @@ -759,7 +1921,18 @@ flex-direction: column; } + .compositionHeader, + .ndfSectionHeader, + .semanticSourceHeader { + flex-direction: column; + } + + .compositionOwners { + align-items: flex-start; + } + .identity, + .ledgerGrid, .steps, .metadataList { grid-template-columns: 1fr; @@ -774,11 +1947,51 @@ grid-template-columns: 1fr; } + .semanticIdentityPath { + align-items: flex-start; + flex-direction: column; + } + + .ndfCardHeader { + grid-template-columns: auto minmax(0, 1fr); + } + + .ndfStatus { + grid-column: 2; + } + + .semanticProvenance dl, + .recordProvenance dl { + grid-template-columns: 1fr; + } + .searchRow, .searchResult { grid-template-columns: 1fr; } + .parameterList { + grid-template-columns: 1fr; + } + + .genericBundleHeading { + align-items: flex-start; + flex-direction: column; + } + + .genericBundleSequence li { + grid-template-columns: 1.8rem minmax(0, 1fr); + } + + .genericBundleSequence em { + grid-column: 2; + justify-self: start; + } + + .parameterList dd { + padding-bottom: .35rem; + } + .evidencePagination { justify-content: space-between; } @@ -786,6 +1999,21 @@ .resultActions { text-align: left; } + + .clauseHeader { + align-items: flex-start; + flex-direction: column; + } + + .sourceLineNumber { + flex-basis: 3.35rem; + padding-right: .75rem; + } + + .sourceLineText { + padding-right: .75rem; + padding-left: .75rem; + } } @media (prefers-reduced-motion: reduce) { diff --git a/docs/site/src/components/ReaderGuide.tsx b/docs/site/src/components/ReaderGuide.tsx index ed718212a..a0c0e78dd 100644 --- a/docs/site/src/components/ReaderGuide.tsx +++ b/docs/site/src/components/ReaderGuide.tsx @@ -7,6 +7,12 @@ import type { } from '@site/src/types/pto'; import styles from './PtoWorkbench.module.css'; +function readerFacingText(value: string): string { + return value + .replace(/non-normative/gi, 'illustrative') + .replace(/非规范性?/g, '示例性'); +} + function Inline({content}: {content: PtoReaderInline[]}): React.JSX.Element { return ( <> @@ -14,7 +20,7 @@ function Inline({content}: {content: PtoReaderInline[]}): React.JSX.Element { const key = `${item.kind}-${index}`; switch (item.kind) { case 'text': - return {item.text}; + return {readerFacingText(item.text)}; case 'code': return {item.text}; case 'strong': @@ -58,16 +64,16 @@ function renderSingleNode(node: PtoReaderNode, key: string): React.ReactNode { } } -function BlockBody({block}: {block: PtoReaderGuideBlock}): React.JSX.Element { +export function ReaderNodes({nodes}: {nodes: PtoReaderNode[]}): React.JSX.Element { const rendered: React.ReactNode[] = []; let index = 0; - while (index < block.nodes.length) { - const node = block.nodes[index]; + while (index < nodes.length) { + const node = nodes[index]; if (node.kind === 'list-item') { const ordered = node.ordered; const items: Extract[] = []; - while (index < block.nodes.length) { - const candidate = block.nodes[index]; + while (index < nodes.length) { + const candidate = nodes[index]; if (candidate.kind !== 'list-item' || candidate.ordered !== ordered) break; items.push(candidate); index += 1; @@ -82,8 +88,8 @@ function BlockBody({block}: {block: PtoReaderGuideBlock}): React.JSX.Element { } if (node.kind === 'table-row') { const rows: Extract[] = []; - while (index < block.nodes.length && block.nodes[index].kind === 'table-row') { - rows.push(block.nodes[index] as Extract); + while (index < nodes.length && nodes[index].kind === 'table-row') { + rows.push(nodes[index] as Extract); index += 1; } const [header, ...body] = rows; @@ -108,18 +114,22 @@ function BlockBody({block}: {block: PtoReaderGuideBlock}): React.JSX.Element { return <>{rendered}; } +function BlockBody({block}: {block: PtoReaderGuideBlock}): React.JSX.Element { + return ; +} + const ZH_ROLE_LABELS: Record = { purpose: '用途', - mechanism: '工作机制', + mechanism: '执行过程', 'inputs-outputs': '输入与输出', effects: '架构效果', constraints: '约束与非法情形', - example: '非规范性示例', + example: '示例演示', 'purpose-scope': '目的与范围', 'concepts-state': '概念与架构状态', 'rules-interactions': '规则与交互', boundaries: '边界与未定义范围', - 'example-usage': '非规范性使用示例', + 'example-usage': '使用示例', 'related-owners-navigation': '相关规范所有者', }; @@ -137,22 +147,13 @@ export default function ReaderGuide({
      - - {chinese ? '读者指南 · 非规范性说明' : 'Reader guide · non-normative explanation'} -

      {chinese - ? mnemonic ? '理解这条指令' : '理解这一架构条目' - : mnemonic ? 'Understand this instruction' : 'Understand this architecture entry'} + ? mnemonic ? '行为' : '架构行为' + : mnemonic ? 'Behavior' : 'Architecture behavior'}

      - {guide.contentLocale}
      -

      - {chinese - ? '本指南帮助阅读与实现,不定义指令语义。发生差异时,下面链接的 ASL/NDF 所有者始终优先。' - : 'This guide supports reading and implementation; it does not define instruction semantics. The linked ASL/NDF owners below always take precedence.'} -

      {fallback && (
      {guide.blocks.length > 0 @@ -177,18 +178,6 @@ export default function ReaderGuide({ ))} -
      -

      {chinese ? '精确规范所有者' : 'Exact normative owners'}

      -
        - {guide.owners.map((owner) => ( -
      • - {owner.kind.toUpperCase()}{' '} - {owner.id}{' '} - {owner.path} -
      • - ))} -
      -
      ); } diff --git a/docs/site/src/components/SemanticExecution.tsx b/docs/site/src/components/SemanticExecution.tsx new file mode 100644 index 000000000..20025f6f2 --- /dev/null +++ b/docs/site/src/components/SemanticExecution.tsx @@ -0,0 +1,143 @@ +import React, {useState} from 'react'; +import type { + PtoLocalizedText, + PtoSemanticExecution, + PtoSemanticSourceRegion, +} from '@site/src/types/pto'; +import AslCode from './AslCode'; +import styles from './PtoWorkbench.module.css'; + +function localize(value: PtoLocalizedText, chinese: boolean): string { + return chinese ? value['zh-CN'] : value.en; +} + +function SourceRegion({ + region, + chinese, + owner, +}: { + region: PtoSemanticSourceRegion; + chinese: boolean; + owner: boolean; +}): React.JSX.Element { + const [sourceText, setSourceText] = useState(region.text ?? null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + function loadSource(event: React.SyntheticEvent): void { + if (!event.currentTarget.open || sourceText !== null || loading || !region.sourceAssetUrl) return; + setLoading(true); + setError(null); + void fetch(region.sourceAssetUrl) + .then((response) => { + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return response.text(); + }) + .then(setSourceText) + .catch((reason: unknown) => setError(`Exact ASL fragment could not be loaded: ${String(reason)}`)) + .finally(() => setLoading(false)); + } + if (owner && sourceText === null) throw new TypeError(`owner semantic region ${region.id} has no source text`); + const body = ( + <> +
      +
      + {owner ? ( + <> + {chinese ? 'TLOAD 所有者' : 'TLOAD owner'} +

      {localize(region.label, chinese)}

      +

      {localize(region.purpose, chinese)}

      + + ) : ( + {region.sourcePath} + )} +
      + {chinese ? '精确源代码' : 'Exact source'} ↗ +
      + {loading &&

      Loading exact ASL fragment…

      } + {error &&

      {error}

      } + {sourceText !== null && ( +
      + +
      + )} +
      + {chinese ? '来源身份' : 'Source identity'} +
      +
      {chinese ? '路径' : 'Path'}
      {region.sourcePath}
      +
      {chinese ? '源 SHA-256' : 'Source SHA-256'}
      {region.sourceSha256}
      +
      {chinese ? '片段 SHA-256' : 'Fragment SHA-256'}
      {region.fragmentSha256}
      +
      {chinese ? '行号' : 'Lines'}
      {region.startLine}–{region.endLine}
      +
      +
      + + ); + if (owner) return
      {body}
      ; + return ( +
      + + {chinese ? '展开共享 ASL:' : 'Show shared ASL: '} + {localize(region.label, chinese)} + {localize(region.purpose, chinese)} + + {body} +
      + ); +} + +export default function SemanticExecution({ + execution, + chinese, +}: { + execution: PtoSemanticExecution; + chinese: boolean; +}): React.JSX.Element { + return ( +
      +

      {chinese ? 'ASL 执行路径' : 'ASL execution path'}

      +

      + {chinese + ? '每一阶段都直接显示 TLOAD owning ASL;共享语义显示其实际执行模块的精确片段。页面不维护第二份伪代码。' + : 'Every stage shows the TLOAD owning ASL directly. Shared behavior shows exact fragments from the modules that execute it; the page maintains no second pseudocode copy.'} +

      + +
      + {execution.stages.map((stage) => ( +
      +
      +

      {localize(stage.label, chinese)}

      +

      {localize(stage.summary, chinese)}

      +
      +
      + {stage.facts.map((fact) => ( +
      +

      {localize(fact.label, chinese)}

      +
        + {fact.items.map((item, index) =>
      • {localize(item, chinese)}
      • )} +
      +
      + ))} +
      + {stage.status === 'source-gap' && stage.gap && ( + + )} + {stage.ownerRegions.map((region) => ( + + ))} + {stage.sharedRegions.map((region) => ( + + ))} +
      + ))} +
      +
      + ); +} diff --git a/docs/site/src/components/SemanticIdPath.tsx b/docs/site/src/components/SemanticIdPath.tsx new file mode 100644 index 000000000..4fdd081e3 --- /dev/null +++ b/docs/site/src/components/SemanticIdPath.tsx @@ -0,0 +1,65 @@ +import React, {useState} from 'react'; +import type {PtoSemanticIdentity} from '@site/src/types/pto'; +import styles from './PtoWorkbench.module.css'; + +const ROLE_LABELS: Record = { + namespace: 'namespace', + surface: 'surface', + owner: 'owner', + category: 'category', + case: 'case', + decision: 'decision record', +}; + +export function CopyIdentityButton({ + identity, + chinese = false, +}: { + identity: PtoSemanticIdentity; + chinese?: boolean; +}): React.JSX.Element { + const [copied, setCopied] = useState(false); + function copy(): void { + void navigator.clipboard.writeText(identity.fullId).then(() => { + setCopied(true); + window.setTimeout(() => setCopied(false), 1800); + }); + } + return ( + <> + + {copied ? `${identity.fullId} copied` : ''} + + ); +} + +export default function SemanticIdPath({ + identity, + chinese = false, + copyable = true, +}: { + identity: PtoSemanticIdentity; + chinese?: boolean; + copyable?: boolean; +}): React.JSX.Element { + return ( +
      +
        + {identity.facets.slice(-4).map((facet, index) => ( +
      1. + {ROLE_LABELS[facet.role]} + {facet.label} +
      2. + ))} +
      + {copyable && } +
      + ); +} diff --git a/docs/site/src/components/SourceLedger.tsx b/docs/site/src/components/SourceLedger.tsx new file mode 100644 index 000000000..9e1d6b799 --- /dev/null +++ b/docs/site/src/components/SourceLedger.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import type { + PtoDocumentationIdentity, + PtoReaderGuide, + PtoReleaseIdentity, + PtoSourceIdentity, +} from '@site/src/types/pto'; +import styles from './PtoWorkbench.module.css'; + +function LedgerItem({label, children}: {label: string; children: React.ReactNode}): React.JSX.Element { + return ( +
      +
      {label}
      +
      {children}
      +
      + ); +} + +export default function SourceLedger({ + release, + source, + documentation, + guide, +}: { + release: PtoReleaseIdentity; + source: PtoSourceIdentity; + documentation: PtoDocumentationIdentity; + guide: PtoReaderGuide; +}): React.JSX.Element { + const chinese = guide.locale === 'zh-CN'; + const releaseLabel = release.releaseEligible + ? (chinese ? '已验证发布' : 'Verified release') + : (chinese ? '候选发布' : 'Release candidate'); + + return ( +
      +

      {chinese ? '来源与发布信息' : 'Sources and release identity'}

      +
      + + {chinese + ? '展开 commit、路径、hash、版本和规范所有者' + : 'Show commit, paths, hashes, version, and canonical owners'} + +
      + + {release.architectureVersion} · {releaseLabel} + + {release.commit} + + {source.path} + + {source.sha256} + + {documentation.path}{' · '} + {chinese ? '已融合到当前页面' : 'embedded in this page'} + + + {documentation.sha256} + +
      +
      +

      {chinese ? '精确所有者' : 'Exact owners'}

      +
        + {guide.owners.map((owner) => ( +
      • + {owner.kind.toUpperCase()}{' '} + {owner.id}{' '} + {owner.path} +
      • + ))} +
      +
      +
      +
      + ); +} diff --git a/docs/site/src/components/SpecNavigation.tsx b/docs/site/src/components/SpecNavigation.tsx new file mode 100644 index 000000000..5bfa46f38 --- /dev/null +++ b/docs/site/src/components/SpecNavigation.tsx @@ -0,0 +1,152 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import {useLocation} from '@docusaurus/router'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import styles from './PortalShell.module.css'; + +type NavigationItem = { + id: string; + label: string; + path: string; + active: (pathname: string, search: string) => boolean; +}; + +function routeActive(pathname: string, path: string): boolean { + return pathname === path || (path !== '/' && pathname.startsWith(path)); +} + +function instructionSurface(search: string): string | null { + return new URLSearchParams(search).get('surface'); +} + +export default function SpecNavigation({currentPageLabel}: {currentPageLabel: string}): React.JSX.Element { + const location = useLocation(); + const {i18n} = useDocusaurusContext(); + const chinese = i18n.currentLocale === 'zh-CN'; + const localize = (path: string): string => { + if (i18n.currentLocale === i18n.defaultLocale) return path; + return `/${i18n.currentLocale}${path}`; + }; + const home = localize('/'); + const architecture = localize('/architecture/'); + const scalar = localize('/instructions/?surface=scalar'); + const block = localize('/instructions/?surface=block'); + const tile = localize('/instructions/?surface=tile'); + const allInstructions = localize('/instructions/'); + const ndf = localize('/ndf/'); + const ndfExplorer = localize('/explore/ndf/'); + const decisions = localize('/reference/governance/adr-process/'); + const search = localize('/search/'); + const query = location.search; + + const primary: NavigationItem[] = [ + { + id: 'home', + label: chinese ? '首页' : 'Home', + path: home, + active: (pathname) => pathname === home, + }, + { + id: 'architecture', + label: chinese ? '架构' : 'Architecture', + path: architecture, + active: (pathname) => routeActive(pathname, architecture) || pathname.includes('/units/PTO-ARCH-'), + }, + ]; + const instructions: NavigationItem[] = [ + { + id: 'all-instructions', + label: chinese ? '全部指令' : 'All instructions', + path: allInstructions, + active: (pathname, currentSearch) => pathname.endsWith('/instructions/') && + !['scalar', 'block', 'tile'].includes(instructionSurface(currentSearch) ?? ''), + }, + { + id: 'scalar', + label: 'Scalar', + path: scalar, + active: (pathname, currentSearch) => pathname.includes('/instructions/scalar/') || + (pathname.endsWith('/instructions/') && instructionSurface(currentSearch) === 'scalar'), + }, + { + id: 'block', + label: 'Block', + path: block, + active: (pathname, currentSearch) => pathname.includes('/instructions/block/') || + (pathname.endsWith('/instructions/') && instructionSurface(currentSearch) === 'block'), + }, + { + id: 'tile', + label: 'Tile', + path: tile, + active: (pathname, currentSearch) => pathname.includes('/instructions/tile/') || + (pathname.endsWith('/instructions/') && instructionSurface(currentSearch) === 'tile'), + }, + ]; + const records: NavigationItem[] = [ + { + id: 'ndf', + label: 'NDF', + path: ndf, + active: (pathname) => pathname.includes('/ndf/') && !pathname.includes('/explore/ndf/'), + }, + { + id: 'ndf-explorer', + label: chinese ? 'NDF 关系图' : 'NDF graph', + path: ndfExplorer, + active: (pathname) => pathname.includes('/explore/ndf/'), + }, + { + id: 'decisions', + label: chinese ? 'Decision / ADR' : 'Decisions / ADR', + path: decisions, + active: (pathname) => pathname.includes('/reference/governance/') || + pathname.includes('/reference/status/decisions/'), + }, + { + id: 'search', + label: chinese ? '搜索' : 'Search', + path: search, + active: (pathname, currentSearch) => pathname.endsWith('/search/') && !currentSearch.includes('surface='), + }, + ]; + + const renderItems = (items: NavigationItem[]) => items.map((item) => { + const active = item.active(location.pathname, query); + return ( +
    • + + {item.label} + +
    • + ); + }); + + return ( + + ); +} diff --git a/docs/site/src/components/TloadStateTransitionDemo.tsx b/docs/site/src/components/TloadStateTransitionDemo.tsx index 24665def1..a55f5e222 100644 --- a/docs/site/src/components/TloadStateTransitionDemo.tsx +++ b/docs/site/src/components/TloadStateTransitionDemo.tsx @@ -8,12 +8,7 @@ const steps = [ {title: 'Published result', detail: 'Show the source-defined success path or preserved prior state.'}, ]; -export interface TloadStateTransitionDemoProps { - sourceUrl?: string; - evidenceIds?: string[]; -} - -export default function TloadStateTransitionDemo({sourceUrl, evidenceIds = []}: TloadStateTransitionDemoProps): React.JSX.Element { +export default function TloadStateTransitionDemo(): React.JSX.Element { const [active, setActive] = useState(0); const [playing, setPlaying] = useState(false); @@ -38,30 +33,26 @@ export default function TloadStateTransitionDemo({sourceUrl, evidenceIds = []}: return (
      - Non-normative demonstration

      TLOAD state-transition walkthrough

      -

      This visualization is an implementation aid. The embedded ASL and linked NDF clauses remain the contract.

      -

      - Source citations: InstructionContractDestinationShapeLegal_TLOAD,{' '} - InstructionContractGMAddress_TLOAD, and InstructionContractZeroMaskNoEffect_TLOAD. - {sourceUrl && <> Open their exact released source ↗} -

      - {evidenceIds.length > 0 &&

      Executable evidence: {evidenceIds.slice(0, 4).map((id, index) => {index > 0 ? ', ' : ''}{id})}{evidenceIds.length > 4 ? ` and ${evidenceIds.length - 4} more` : ''}.

      } -
      - {steps.map((step, index) => ( -
      - Step {index + 1} - {step.title} - {step.detail} -
      - ))} -
      -
      - - - - Step {active + 1} of {steps.length} -
      +
      + Open optional interactive walkthrough +

      Step through input capture, preflight, access, and publication. The exact ASL definition is embedded immediately above.

      +
      + {steps.map((step, index) => ( +
      + Step {index + 1} + {step.title} + {step.detail} +
      + ))} +
      +
      + + + + Step {active + 1} of {steps.length} +
      +
      ); } diff --git a/docs/site/src/components/UnitWorkbenchView.tsx b/docs/site/src/components/UnitWorkbenchView.tsx index 764af1e3e..456824f37 100644 --- a/docs/site/src/components/UnitWorkbenchView.tsx +++ b/docs/site/src/components/UnitWorkbenchView.tsx @@ -1,17 +1,21 @@ import React from 'react'; import type {PtoUnitWorkbenchData} from '@site/src/types/pto'; import ASLViewer from './ASLViewer'; -import DocumentationIdentity from './DocumentationIdentity'; +import AssemblerSymbols from './AssemblerSymbols'; import EncodingBitfield from './EncodingBitfield'; import EvidenceIndex from './EvidenceIndex'; import GeneratedMetadata from './GeneratedMetadata'; +import InstructionOverview from './InstructionOverview'; +import InstructionComposition from './InstructionComposition'; +import InstructionContractSummary from './InstructionContractSummary'; import NdfClause from './NdfClause'; import ReaderGuide from './ReaderGuide'; -import SourceIdentity from './SourceIdentity'; +import SemanticExecution from './SemanticExecution'; +import SourceLedger from './SourceLedger'; import TloadStateTransitionDemo from './TloadStateTransitionDemo'; -import {LanguageFallbackNotice, releaseStatus} from './releasePresentation'; +import {LanguageFallbackNotice} from './releasePresentation'; import styles from './PtoWorkbench.module.css'; -import {firstText, list, record} from './data'; +import {firstText, record} from './data'; export interface UnitPresentation { title: string; @@ -42,7 +46,6 @@ export default function UnitWorkbenchView({unitData}: {unitData: PtoUnitWorkbenc const data = record(unitData); const metadata = record(data.metadata) as PtoUnitWorkbenchData['metadata']; const unit = record(data.unit) as PtoUnitWorkbenchData['unit']; - const tests = list(data.tests).map(record); const presentation = unitPresentation(unitData); return ( @@ -50,34 +53,73 @@ export default function UnitWorkbenchView({unitData}: {unitData: PtoUnitWorkbenc
      - ASL unit workbench · {releaseStatus(unitData.release)}

      {presentation.title}

      {presentation.summary &&

      {presentation.summary}

      }
      {presentation.identity && {presentation.identity}}
      - - + {!unitData.composition && ( + + )} + {unitData.composition && ( + typeof value === 'string') + : []} + highLevelAssembly={unitData.highLevelAssembly!} + /> + )} + {presentation.mnemonic && ( + + )} + {unitData.encoding && unitData.encoding.catalogForms.length > 0 && ( + + )} + {!unitData.semanticExecution && ( + + )} + {unitData.semanticExecution ? ( + + ) : ( + + )} -
      -
      - {unitData.encoding && unitData.encoding.catalogForms.length > 0 && ( - - )} - - {presentation.mnemonic === 'TLOAD' && ( - firstText(test, ['id'])).filter(Boolean)} - /> - )} - -
      - -
      + {presentation.mnemonic === 'TLOAD' && } + + + + ); } diff --git a/docs/site/src/components/encodingModel.test.ts b/docs/site/src/components/encodingModel.test.ts index b7dd71a1a..357a54f7a 100644 --- a/docs/site/src/components/encodingModel.test.ts +++ b/docs/site/src/components/encodingModel.test.ts @@ -4,6 +4,7 @@ const {resolve} = require('node:path'); const test = require('node:test'); const { formatFixedValue, + formatConstantToken, parseEncodingForm, } = require('./encodingModel.ts'); @@ -103,6 +104,21 @@ test('B.FPATR retains names for mask-fixed operand pieces', () => { } }); +test('unnamed fixed selectors render their concrete bit pattern', () => { + const form = catalogForms().find((candidate) => candidate.mnemonic === 'ADD'); + assert.ok(form); + const segments = parseEncodingForm(form).flatMap((word: {fields: unknown[]}) => word.fields); + const fixedSelectors = segments.filter( + (segment: {fixed: boolean; fieldName?: string}) => segment.fixed && !segment.fieldName, + ); + assert.ok(fixedSelectors.length > 0); + for (const segment of fixedSelectors as Array<{name: string; width: number; bits: string}>) { + const {lsb} = range(segment.bits); + assert.equal(segment.name, formatConstantToken(BigInt(form.encoding[0].match), lsb, segment.width)); + assert.match(segment.name, segment.width === 1 ? /^[01]$/ : /^\d+'b[01]+$/); + } +}); + test('scaled immediates may leave source-defined value bits implicit', () => { const forms = catalogForms(); for (const mnemonic of ['FENTRY', 'FEXIT', 'FRET.RA', 'FRET.STK']) { diff --git a/docs/site/src/components/encodingModel.ts b/docs/site/src/components/encodingModel.ts index 6c3b4ce95..6bc0f4ac8 100644 --- a/docs/site/src/components/encodingModel.ts +++ b/docs/site/src/components/encodingModel.ts @@ -2,6 +2,7 @@ export interface EncodingSegment { bits: string; fieldName?: string; fixed: boolean; + kind: 'field' | 'constant' | 'unspecified'; name: string; pieceId?: string; value?: string; @@ -66,6 +67,16 @@ export function formatFixedValue(match: bigint, localLsb: number, width: number) return `0x${value.toString(16).padStart(Math.ceil(width / 4), '0')}`; } +export function formatFixedBits(match: bigint, localLsb: number, width: number): string { + const value = (match >> BigInt(localLsb)) & ((1n << BigInt(width)) - 1n); + return value.toString(2).padStart(width, '0'); +} + +export function formatConstantToken(match: bigint, localLsb: number, width: number): string { + const bits = formatFixedBits(match, localLsb, width); + return width === 1 ? bits : `${width}'b${bits}`; +} + export function parseEncodingForm(formValue: unknown): EncodingWordModel[] { const form = object(formValue, 'form'); const totalWidth = integer(form.length_bits, 'form.length_bits', {positive: true}); @@ -181,11 +192,15 @@ export function parseEncodingForm(formValue: unknown): EncodingWordModel[] { localLsb -= 1; } const width = localMsb - localLsb + 1; + const unnamedFixedBits = identity.fixed && !identity.fieldName + ? formatConstantToken(word.match, localLsb, width) + : null; fields.push({ bits: bitRange(word.offset + localMsb, word.offset + localLsb), ...(identity.fieldName ? {fieldName: identity.fieldName} : {}), fixed: identity.fixed, - name: identity.name, + kind: identity.fieldName ? 'field' : identity.fixed ? 'constant' : 'unspecified', + name: unnamedFixedBits ?? identity.name, ...(identity.pieceId ? {pieceId: identity.pieceId} : {}), ...(identity.fixed ? {value: formatFixedValue(word.match, localLsb, width)} : {}), width, diff --git a/docs/site/src/components/releasePresentation.tsx b/docs/site/src/components/releasePresentation.tsx index e70bea975..434176a70 100644 --- a/docs/site/src/components/releasePresentation.tsx +++ b/docs/site/src/components/releasePresentation.tsx @@ -5,7 +5,7 @@ import type {PtoReaderGuide, PtoReleaseIdentity} from '@site/src/types/pto'; export function releaseStatus(release: PtoReleaseIdentity): string { return release.releaseEligible ? 'Latest verified release' - : 'Unpublished release preview'; + : 'Release candidate'; } export function useLocalizedPath(path: string): string { diff --git a/docs/site/src/css/custom.css b/docs/site/src/css/custom.css index d47716d6c..b9b632340 100644 --- a/docs/site/src/css/custom.css +++ b/docs/site/src/css/custom.css @@ -1,46 +1,75 @@ :root { - --ifm-color-primary: #126f9b; - --ifm-color-primary-dark: #0f648b; - --ifm-color-primary-darker: #0e5e83; - --ifm-color-primary-darkest: #0c4e6c; - --ifm-color-primary-light: #157aaa; - --ifm-color-primary-lighter: #1680b2; - --ifm-color-primary-lightest: #1990c9; + --ifm-color-primary: #9e1429; + --ifm-color-primary-dark: #8d1225; + --ifm-color-primary-darker: #841122; + --ifm-color-primary-darkest: #6d0e1c; + --ifm-color-primary-light: #af162d; + --ifm-color-primary-lighter: #b8172f; + --ifm-color-primary-lightest: #cf1a36; + --ifm-background-color: #faf8f5; + --ifm-background-surface-color: #ffffff; --ifm-font-family-base: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --ifm-font-family-monospace: "SFMono-Regular", Consolas, "Liberation Mono", monospace; --ifm-heading-font-weight: 650; --ifm-navbar-height: 3.75rem; --ifm-global-radius: 4px; - --pto-workbench: #071728; - --pto-hero-text: #f5fbff; - --pto-hero-muted: #adc0d0; - --pto-accent-soft: #67d3f2; - --pto-action: #0f648b; - --pto-action-hover: #0c4e6c; - --pto-border: #d9e0e6; - --pto-border-strong: #244662; - --pto-text-muted: #546474; - --pto-surface-subtle: #f4f7f9; - --pto-surface-focus: #f2f8fb; + --pto-workbench: #151114; + --pto-hero-text: #f8f4ef; + --pto-hero-muted: #c7beb5; + --pto-accent-soft: #7a5a00; + --pto-action: #c7000b; + --pto-action-hover: #a80c1b; + --pto-border: #ded8d0; + --pto-border-strong: #8d5d61; + --pto-text-muted: #5f5a54; + --pto-surface-subtle: #f2eee8; + --pto-surface-focus: #f9eaeb; + --pto-nav-active: #8d1225; + --pto-brand-red: #c7000b; + --pto-highlight: #7a5a00; + --pto-highlight-muted: #654a00; + --pto-focus-ring: #9a7200; + --pto-status-positive: #0b6e4f; + --pto-status-caution: #8a6a20; + --pto-status-danger: #9e1429; } [data-theme='dark'] { - --ifm-color-primary: #58c5e7; - --ifm-color-primary-dark: #3ebce2; - --ifm-color-primary-darker: #30b7df; - --ifm-color-primary-darkest: #1e96bb; - --ifm-color-primary-light: #72cee9; - --ifm-color-primary-lighter: #80d2eb; - --ifm-color-primary-lightest: #a8e0f0; - --ifm-background-color: #0e1822; - --ifm-background-surface-color: #13212d; - --pto-border: #293c4b; - --pto-text-muted: #a5b5c2; - --pto-surface-subtle: #101d28; - --pto-surface-focus: #172b39; + --ifm-color-primary: #ff4d5a; + --ifm-color-primary-dark: #ff3f4d; + --ifm-color-primary-darker: #ff3544; + --ifm-color-primary-darkest: #ff2637; + --ifm-color-primary-light: #ff6671; + --ifm-color-primary-lighter: #ff7c85; + --ifm-color-primary-lightest: #ff9ca3; + --ifm-background-color: #0b0c0f; + --ifm-background-surface-color: #15171c; + --ifm-font-color-base: #f4f1ec; + --ifm-link-color: #ff9ca3; + --ifm-code-color: #ffe071; + --pto-workbench: #090a0d; + --pto-hero-text: #f4f1ec; + --pto-hero-muted: #b9b4ac; + --pto-accent-soft: #ffd34f; + --pto-action: #c7000b; + --pto-action-hover: #a80c1b; + --pto-border: #302f33; + --pto-border-strong: #5a383a; + --pto-text-muted: #b9b4ac; + --pto-surface-subtle: #101216; + --pto-surface-focus: #241719; + --pto-nav-active: #ff8d96; + --pto-brand-red: #c7000b; + --pto-highlight: #ffd34f; + --pto-highlight-muted: #b99118; + --pto-focus-ring: #ffd34f; + --pto-status-positive: #8fc3a8; + --pto-status-caution: #ffd34f; + --pto-status-danger: #ff8d96; } .navbar { + background: color-mix(in srgb, var(--ifm-background-surface-color) 94%, black); border-bottom: 1px solid var(--pto-border); box-shadow: none; } @@ -58,15 +87,31 @@ } .footer--dark { - --ifm-footer-background-color: #07131f; - border-top: 1px solid #23384a; + --ifm-footer-background-color: #090a0d; + border-top: 1px solid var(--pto-border-strong); } code { border-radius: 3px; } +:not(pre) > code { + color: var(--pto-highlight); +} + +.theme-doc-markdown p a, +.theme-doc-markdown li a { + text-decoration: underline; + text-underline-offset: 0.16em; +} + :focus-visible { - outline: 3px solid var(--ifm-color-primary); + outline: 3px solid var(--pto-focus-ring); outline-offset: 2px; } + +@media (forced-colors: active) { + :focus-visible { + outline-color: Highlight; + } +} diff --git a/docs/site/src/pages/index.module.css b/docs/site/src/pages/index.module.css index fdc4acd60..d8883db7a 100644 --- a/docs/site/src/pages/index.module.css +++ b/docs/site/src/pages/index.module.css @@ -4,7 +4,7 @@ padding: 4.75rem 0 3.75rem; color: var(--pto-hero-text); background: - linear-gradient(115deg, rgba(10, 28, 52, 0.98), rgba(12, 42, 68, 0.96)), + linear-gradient(115deg, rgba(9, 10, 13, 0.99), rgba(34, 17, 20, 0.97)), var(--pto-workbench); border-bottom: 1px solid var(--pto-border-strong); } @@ -15,8 +15,8 @@ pointer-events: none; content: ''; background-image: - linear-gradient(rgba(93, 201, 235, 0.045) 1px, transparent 1px), - linear-gradient(90deg, rgba(93, 201, 235, 0.045) 1px, transparent 1px); + linear-gradient(rgba(241, 91, 97, 0.045) 1px, transparent 1px), + linear-gradient(90deg, rgba(217, 184, 92, 0.04) 1px, transparent 1px); background-size: 32px 32px; mask-image: linear-gradient(to bottom, black, transparent 90%); } @@ -105,13 +105,13 @@ .heroActions :global(.button--outline) { color: white; - border-color: rgba(194, 234, 248, 0.55); + border-color: color-mix(in srgb, var(--pto-highlight) 72%, transparent); } .heroActions :global(.button--outline):hover { color: white; border-color: var(--pto-accent-soft); - background: rgba(61, 181, 220, 0.12); + background: color-mix(in srgb, var(--pto-highlight) 12%, transparent); } .sourceBoundary { @@ -119,8 +119,8 @@ gap: 0.54rem; padding: 1.15rem 1.25rem; color: var(--pto-hero-muted); - background: rgba(2, 13, 27, 0.42); - border: 1px solid rgba(133, 188, 214, 0.25); + background: rgba(8, 8, 10, 0.58); + border: 1px solid color-mix(in srgb, var(--pto-highlight) 26%, transparent); border-radius: 4px; } @@ -134,7 +134,7 @@ } .sourceBoundary code { - color: #c2eaf8; + color: var(--pto-accent-soft); background: transparent; border: 0; } @@ -157,12 +157,12 @@ background: white; border: 1px solid transparent; border-radius: 5px; - box-shadow: 0 18px 55px rgba(0, 7, 18, 0.28); + box-shadow: 0 18px 55px rgba(0, 0, 0, 0.34); } .search:focus-within { border-color: var(--pto-accent-soft); - box-shadow: 0 0 0 3px rgba(61, 181, 220, 0.22), 0 18px 55px rgba(0, 7, 18, 0.28); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--pto-highlight) 34%, transparent), 0 18px 55px rgba(0, 0, 0, 0.34); } .searchPrompt { @@ -176,7 +176,7 @@ .search input { min-width: 0; padding: 1.15rem 0.85rem; - color: #132234; + color: #20181a; background: transparent; border: 0; font: inherit; diff --git a/docs/site/src/pages/index.tsx b/docs/site/src/pages/index.tsx index d1efb88e5..d5dd8a9f1 100644 --- a/docs/site/src/pages/index.tsx +++ b/docs/site/src/pages/index.tsx @@ -1,12 +1,12 @@ import type {ReactNode} from 'react'; import clsx from 'clsx'; import Head from '@docusaurus/Head'; -import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import Translate, {translate} from '@docusaurus/Translate'; import {usePluginData} from '@docusaurus/useGlobalData'; import type {PtoReleaseIdentity} from '@site/src/types/pto'; import {useLocalizedPath} from '@site/src/components/releasePresentation'; +import PortalShell from '@site/src/components/PortalShell'; import styles from './index.module.css'; @@ -54,7 +54,7 @@ const surfaceIntroductions: SurfaceIntroduction[] = [ AGU · ALU · AMO · BRU · FSU · SYS ), - to: '/search/?kind=asl&surface=scalar', + to: '/instructions/?surface=scalar', action: Browse Scalar units, }, { @@ -71,7 +71,7 @@ const surfaceIntroductions: SurfaceIntroduction[] = [ Lifecycle · Execution · Attributes · Operands · Encoding ), - to: '/search/?kind=asl&surface=block', + to: '/instructions/?surface=block', action: Browse Block units, }, { @@ -88,7 +88,7 @@ const surfaceIntroductions: SurfaceIntroduction[] = [ Elementwise · Reduce/expand · Layout · Matrix · Memory/data movement ), - to: '/search/?kind=asl&surface=tile', + to: '/instructions/?surface=tile', action: Browse Tile units, }, ]; @@ -134,13 +134,14 @@ function Home(): ReactNode { ) as PtoPluginData; const releaseUrl = `${REPOSITORY_URL}/releases/tag/${release.tag}`; const architectureRoute = useLocalizedPath( - '/units/PTO-ARCH-OVERVIEW-ARCHITECTURE/', + '/architecture/', ); return ( - + @@ -153,7 +154,7 @@ function Home(): ReactNode { {release.releaseEligible ? ( Latest verified release ) : ( - Unpublished release preview + Release candidate )} {release.tag} @@ -174,7 +175,7 @@ function Home(): ReactNode {

      PTO ISA / FORMAL SPECIFICATION

      - PTO Architecture + Architecture

      @@ -300,7 +301,7 @@ function Home(): ReactNode { NDF identities are embedded in their owning ASL sources. The explorer connects clauses to owners, tests, and ADR history without inventing new semantics.

      - + Explore NDF relationships → @@ -331,7 +332,7 @@ function Home(): ReactNode {
    - + ); } diff --git a/docs/site/src/routes/Architecture.tsx b/docs/site/src/routes/Architecture.tsx new file mode 100644 index 000000000..830a8727f --- /dev/null +++ b/docs/site/src/routes/Architecture.tsx @@ -0,0 +1,191 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import type { + PtoArchitectureGuide, + PtoArchitectureOwnerProjection, +} from '@site/src/types/pto'; +import PortalShell from '@site/src/components/PortalShell'; +import {ReaderNodes} from '@site/src/components/ReaderGuide'; +import styles from '@site/src/components/ArchitectureOverview.module.css'; + +function OwnerActions({owner, chinese}: { + owner: PtoArchitectureOwnerProjection; + chinese: boolean; +}): React.JSX.Element { + return ( +
    + {chinese ? '阅读 owner workbench' : 'Read owner workbench'} + {chinese ? '查看原始 ASL ↗' : 'Open original ASL ↗'} +
    + ); +} + +export default function Architecture({ + architecture, +}: { + architecture: PtoArchitectureGuide; +}): React.JSX.Element { + const chinese = architecture.locale === 'zh-CN'; + const title = chinese ? '架构' : 'Architecture'; + const sources = [ + architecture.entry, + ...architecture.topics.flatMap((topic) => [topic.primary, ...topic.related]), + ].filter((owner, index, all) => all.findIndex((candidate) => candidate.id === owner.id) === index); + + return ( + +
    +
    +
    +

    {chinese ? '从心智模型进入精确 owner' : 'From mental model to exact owners'}

    +

    {title}

    +

    + {chinese + ? '先建立 Scalar、Block/bundle、Tile、状态与内存之间的阅读关系,再进入对应 ASL/NDF owner。这里不创建第二套语义。' + : 'Build the reading relationship between Scalar, Block/bundle, Tile, state, and memory first; then enter the exact ASL/NDF owner. This page does not create a second semantic source.'} +

    +
    +
    + {chinese ? '发布' : 'Release'} + {architecture.release.tag} +
    +
    + +
    +
    +

    {chinese ? '第一步' : 'First'}

    +

    {chinese ? '架构心智模型' : 'Architecture mental model'}

    +
    +
    + {architecture.entry.blocks.slice(0, 3).map((block) => ( +
    + +
    + ))} + +
    +
      + {architecture.topics.map((topic, index) => ( +
    1. + + {String(index + 1).padStart(2, '0')} + {topic.label} + {topic.primary.id} + +
    2. + ))} +
    +
    + +
    +
    +

    {chinese ? '阅读关系' : 'Reading relationships'}

    +

    {chinese ? '主题与规范所有者' : 'Topics and specification owners'}

    +
    +
    + + + + + + + + + + + + {architecture.topics.map((topic) => ( + + + + + + + ))} + +
    {chinese ? '架构主题到当前 owner 的 source map' : 'Source map from architecture topics to current owners'}
    {chinese ? '主题' : 'Topic'}{chinese ? '主要 owner' : 'Primary owner'}{chinese ? '交叉链接' : 'Cross-links'}{chinese ? '源状态' : 'Source status'}
    {topic.label}{topic.primary.id}{topic.related.length}{topic.sourceBoundary === null ? (chinese ? '当前 owner 已定位' : 'Current owners located') : (chinese ? '显示 owner 声明的边界' : 'Owner-declared boundary shown')}
    +
    +
    + +
    + {architecture.topics.map((topic) => ( +
    +
    +
    +

    {chinese ? '典型阅读场景' : 'Typical reading scenario'}

    +

    {topic.label}

    +
    + +
    +
    + {topic.primary.id} +
    + {topic.sourceBoundary !== null && ( + + )} +
    + {topic.primary.blocks.map((block) => ( +
    + +
    + ))} + +
    +
    +

    {chinese ? '继续阅读这些 owner' : 'Continue with these owners'}

    +
      + {topic.related.map((owner) => ( +
    • +
      + {owner.label} + {owner.id} +
      +
      + {chinese ? 'Workbench' : 'Workbench'} + ASL ↗ +
      +
    • + ))} +
    +
    +
    + ))} +
    + +
    +
    + {chinese ? '显示 commit、路径、hash 与全部 owner' : 'Show commit, paths, hashes, and all owners'} +
    +
    {chinese ? '发布版本' : 'Publication'}
    {architecture.release.publicationVersion}
    +
    Commit
    {architecture.release.commit}
    +
    +
      + {sources.map((owner) => ( +
    • + {owner.id} + {owner.sourcePath} + sha256:{owner.sourceSha256} +
    • + ))} +
    +
    +
    +
    +
    + ); +} diff --git a/docs/site/src/routes/InstructionIndex.tsx b/docs/site/src/routes/InstructionIndex.tsx new file mode 100644 index 000000000..e84942deb --- /dev/null +++ b/docs/site/src/routes/InstructionIndex.tsx @@ -0,0 +1,84 @@ +import React, {useMemo, useState} from 'react'; +import Link from '@docusaurus/Link'; +import {useHistory, useLocation} from '@docusaurus/router'; +import type {PtoInstructionIndexData} from '@site/src/types/pto'; +import PortalShell from '@site/src/components/PortalShell'; +import styles from '@site/src/components/CatalogIndex.module.css'; + +type Surface = 'all' | 'scalar' | 'block' | 'tile'; + +function surfaceFromSearch(search: string): Surface { + const surface = new URLSearchParams(search).get('surface'); + return surface === 'scalar' || surface === 'block' || surface === 'tile' ? surface : 'all'; +} + +export default function InstructionIndex({index}: {index: PtoInstructionIndexData}): React.JSX.Element { + const history = useHistory(); + const location = useLocation(); + const chinese = location.pathname.startsWith('/zh-CN/'); + const surface = surfaceFromSearch(location.search); + const [query, setQuery] = useState(''); + const selectSurface = (nextSurface: Surface): void => { + const params = new URLSearchParams(location.search); + if (nextSurface === 'all') { + params.delete('surface'); + } else { + params.set('surface', nextSurface); + } + const search = params.toString(); + history.push(`${location.pathname}${search ? `?${search}` : ''}`); + }; + const entries = useMemo(() => { + const normalized = query.trim().toLocaleLowerCase(); + return index.entries.filter((entry) => + (surface === 'all' || entry.surface === surface) && + (!normalized || [entry.mnemonic, entry.id, entry.sourcePath, ...entry.classification] + .join(' ').toLocaleLowerCase().includes(normalized)), + ); + }, [index.entries, query, surface]); + const title = chinese ? '指令索引' : 'Instruction index'; + return ( + +
    +
    +
    +
    {chinese ? '单一 canonical workbench' : 'One canonical workbench'}
    +

    {title}

    +

    {chinese + ? '浏览全部已发布 Scalar、Block 与 Tile 指令;每个条目只进入一个融合 Markdown、ASL、NDF 和证据的页面。' + : 'Browse every released Scalar, Block, and Tile instruction. Each entry opens one page that combines Markdown, ASL, NDF, and evidence.'}

    +
    +
    {index.entries.length}
    +
    +
    + setQuery(event.target.value)} + placeholder={chinese ? '搜索 mnemonic、ID、family 或源路径' : 'Search mnemonic, ID, family, or source path'} + aria-label={chinese ? '搜索指令' : 'Search instructions'} + /> +
    + {(['all', 'scalar', 'block', 'tile'] as const).map((value) => ( + + ))} +
    +
    +

    {entries.length} {chinese ? '条指令' : 'instructions'}

    +
    + {entries.map((entry) => ( +
    +
    {entry.id}
    +

    {entry.mnemonic}

    +
    {entry.surface} · {entry.classification.join(' / ')}
    + {entry.summary &&

    {entry.summary}

    } + {entry.sourcePath} +
    + ))} +
    +
    +
    + ); +} diff --git a/docs/site/src/routes/NdfCatalog.tsx b/docs/site/src/routes/NdfCatalog.tsx new file mode 100644 index 000000000..d39d55cfa --- /dev/null +++ b/docs/site/src/routes/NdfCatalog.tsx @@ -0,0 +1,69 @@ +import React, {useMemo, useState} from 'react'; +import Link from '@docusaurus/Link'; +import {useLocation} from '@docusaurus/router'; +import type {PtoNdfCatalogData} from '@site/src/types/pto'; +import PortalShell from '@site/src/components/PortalShell'; +import styles from '@site/src/components/CatalogIndex.module.css'; + +export default function NdfCatalog({catalog}: {catalog: PtoNdfCatalogData}): React.JSX.Element { + const location = useLocation(); + const chinese = location.pathname.startsWith('/zh-CN/'); + const [query, setQuery] = useState(''); + const entries = useMemo(() => { + const normalized = query.trim().toLocaleLowerCase(); + return catalog.entries.filter((entry) => !normalized || [ + entry.id, + entry.kind, + entry.level, + entry.layer, + entry.status, + entry.text ?? '', + entry.sourcePath, + ...entry.owners.map((owner) => owner.mnemonic ?? owner.id), + ].join(' ').toLocaleLowerCase().includes(normalized)); + }, [catalog.entries, query]); + const title = chinese ? 'NDF 索引' : 'NDF index'; + return ( + +
    +
    +
    +
    {chinese ? '原始条款与 owning unit' : 'Original clauses and owning units'}
    +

    {title}

    +

    {chinese + ? '每个稳定 NDF ID 都有可读详情页、canonical source identity,以及对应 instruction/unit workbench 链接。' + : 'Every stable NDF ID has a readable detail page, canonical source identity, and links to its instruction or unit workbenches.'}

    +
    +
    {catalog.entries.length}
    +
    +
    + setQuery(event.target.value)} + placeholder={chinese ? '搜索 ID、正文、owner 或源路径' : 'Search ID, body, owner, or source path'} + aria-label={chinese ? '搜索 NDF' : 'Search NDF'} + /> +
    {entries.length} / {catalog.entries.length}
    +
    +
    + {entries.map((entry) => ( +
    +
    {entry.id}
    +

    {entry.kind}

    +
    {entry.level} · {entry.layer} · {entry.status}
    +

    {entry.text ?? (chinese + ? '该稳定身份由 instruction contract 拥有;链接直接打开 canonical unit workbench。' + : 'This stable identity is owned by an instruction contract; the link opens its canonical unit workbench directly.')}

    +
      + {entry.owners.slice(0, 4).map((owner) => ( +
    • {owner.mnemonic ?? owner.id}
    • + ))} +
    +
    + ))} +
    +
    +
    + ); +} diff --git a/docs/site/src/routes/NdfDetail.tsx b/docs/site/src/routes/NdfDetail.tsx new file mode 100644 index 000000000..78c684083 --- /dev/null +++ b/docs/site/src/routes/NdfDetail.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import {useLocation} from '@docusaurus/router'; +import type {PtoNdfDetailData} from '@site/src/types/pto'; +import PortalShell from '@site/src/components/PortalShell'; +import NdfClause from '@site/src/components/NdfClause'; +import EvidenceIndex from '@site/src/components/EvidenceIndex'; +import styles from '@site/src/components/CatalogIndex.module.css'; + +export default function NdfDetail({detail}: {detail: PtoNdfDetailData}): React.JSX.Element { + const chinese = useLocation().pathname.startsWith('/zh-CN/'); + return ( + +
    +
    +
    +
    NDF · {detail.clause.kind} · {detail.clause.status}
    +

    {detail.clause.id}

    +

    {chinese ? '以下正文直接来自 canonical NDF owner。' : 'The body below comes directly from the canonical NDF owner.'}

    +
    +
    {detail.owners.length} {chinese ? '个 owner' : 'owners'}
    +
    + +
    +

    {chinese ? '对应 instruction / unit 页面' : 'Instruction and unit pages'}

    +
      + {detail.owners.map((owner) => ( +
    • + {owner.mnemonic ?? owner.id} + {owner.id} + {owner.sourcePath} +
    • + ))} +
    +
    +
    +

    {chinese ? '本地关系邻域' : 'Local relationship neighborhood'}

    + {detail.relationships.length > 0 ? ( +
      + {detail.relationships.map((relationship) => ( +
    • + {relationship.kind} · {relationship.direction}{' '} + {relationship.external + ? {relationship.node.label} + : {relationship.node.label}} + {relationship.node.id} +
    • + ))} +
    + ) : ( +

    {chinese ? '此条款没有可显示的本地关系。' : 'No local relationships are available for this clause.'}

    + )} +

    {chinese ? '打开完整 NDF 关系浏览器' : 'Open the complete NDF relationship explorer'}

    +
    + +
    +
    + {chinese ? '显示 canonical path 与 hash' : 'Show canonical path and hashes'} +

    {detail.clause.sourcePath}:{detail.clause.startLine}

    +

    Source SHA-256: {detail.clause.sourceSha256}

    +

    Clause SHA-256: {detail.clause.clauseSha256}

    +
    +
    +
    +
    + ); +} diff --git a/docs/site/src/routes/NdfExplorer.tsx b/docs/site/src/routes/NdfExplorer.tsx index 7ac5a3b6f..681bc5d4f 100644 --- a/docs/site/src/routes/NdfExplorer.tsx +++ b/docs/site/src/routes/NdfExplorer.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import Layout from '@theme/Layout'; import type {PtoNdfGraphData} from '@site/src/types/pto'; import NdfGlobalExplorer from '@site/src/components/NdfGlobalExplorer'; import styles from '@site/src/components/PtoWorkbench.module.css'; import {list, record} from '@site/src/components/data'; import {LanguageFallbackNotice, releaseStatus} from '@site/src/components/releasePresentation'; +import PortalShell from '@site/src/components/PortalShell'; export interface NdfExplorerRouteProps { graph: PtoNdfGraphData; @@ -15,7 +15,7 @@ export default function NdfExplorer({graph}: NdfExplorerRouteProps): React.JSX.E const nodeCount = list(data.nodes).length; const edgeCount = list(data.edges).length; return ( - +
    @@ -30,6 +30,6 @@ export default function NdfExplorer({graph}: NdfExplorerRouteProps): React.JSX.E
    - + ); } diff --git a/docs/site/src/routes/NdfIndexPage.tsx b/docs/site/src/routes/NdfIndexPage.tsx index e52b5d4a4..def2e7a7f 100644 --- a/docs/site/src/routes/NdfIndexPage.tsx +++ b/docs/site/src/routes/NdfIndexPage.tsx @@ -1,6 +1,5 @@ import React from 'react'; import Link from '@docusaurus/Link'; -import Layout from '@theme/Layout'; import type {PtoNdfIndexPageData} from '@site/src/types/pto'; import styles from '@site/src/components/PtoWorkbench.module.css'; import { @@ -8,6 +7,7 @@ import { releaseStatus, useLocalizedPath, } from '@site/src/components/releasePresentation'; +import PortalShell from '@site/src/components/PortalShell'; export default function NdfIndexPage({ index, @@ -18,7 +18,7 @@ export default function NdfIndexPage({ const previous = useLocalizedPath(`/explore/ndf/index/${index.page - 1}/`); const next = useLocalizedPath(`/explore/ndf/index/${index.page + 1}/`); return ( -
    @@ -64,6 +64,6 @@ export default function NdfIndexPage({ ))}
    -
    + ); } diff --git a/docs/site/src/routes/Search.tsx b/docs/site/src/routes/Search.tsx index 49b16d2ef..aacfe2000 100644 --- a/docs/site/src/routes/Search.tsx +++ b/docs/site/src/routes/Search.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import Layout from '@theme/Layout'; import type {PtoSearchData} from '@site/src/types/pto'; import SearchWorkbench from '@site/src/components/SearchWorkbench'; import styles from '@site/src/components/PtoWorkbench.module.css'; import {LanguageFallbackNotice, releaseStatus} from '@site/src/components/releasePresentation'; +import PortalShell from '@site/src/components/PortalShell'; export interface SearchRouteProps { search: PtoSearchData; @@ -11,7 +11,7 @@ export interface SearchRouteProps { export default function Search({search}: SearchRouteProps): React.JSX.Element { return ( - +
    @@ -24,6 +24,6 @@ export default function Search({search}: SearchRouteProps): React.JSX.Element {
    -
    + ); } diff --git a/docs/site/src/routes/UnitWorkbench.tsx b/docs/site/src/routes/UnitWorkbench.tsx index d5530047c..6138440b2 100644 --- a/docs/site/src/routes/UnitWorkbench.tsx +++ b/docs/site/src/routes/UnitWorkbench.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import Layout from '@theme/Layout'; import type {PtoUnitWorkbenchData} from '@site/src/types/pto'; import UnitWorkbenchView, {unitPresentation} from '@site/src/components/UnitWorkbenchView'; +import PortalShell from '@site/src/components/PortalShell'; export interface UnitWorkbenchRouteProps { unitData: PtoUnitWorkbenchData; @@ -11,8 +11,11 @@ export default function UnitWorkbench({unitData}: UnitWorkbenchRouteProps): Reac const presentation = unitPresentation(unitData); const description = `ASL, NDF, and evidence for ${presentation.title}.`; return ( - + - + ); } diff --git a/docs/site/src/theme/MDXComponents/index.tsx b/docs/site/src/theme/MDXComponents/index.tsx new file mode 100644 index 000000000..ef1e6a1a5 --- /dev/null +++ b/docs/site/src/theme/MDXComponents/index.tsx @@ -0,0 +1,11 @@ +import React, {type ComponentProps} from 'react'; +import OriginalMDXComponents from '@theme-original/MDXComponents'; + +function AccessibleTable(props: ComponentProps<'table'>) { + return ; +} + +export default { + ...OriginalMDXComponents, + table: AccessibleTable, +}; diff --git a/docs/site/src/theme/aslPrism.test.ts b/docs/site/src/theme/aslPrism.test.ts new file mode 100644 index 000000000..2f72913b0 --- /dev/null +++ b/docs/site/src/theme/aslPrism.test.ts @@ -0,0 +1,42 @@ +const aslAssert = require('node:assert/strict'); +const aslTest = require('node:test'); +const {Prism: AslPrism} = require('prism-react-renderer'); +const {registerAslPrism} = require('./aslPrism.ts'); + +function tokenText(token: unknown): string { + if (typeof token === 'string') return token; + if (Array.isArray(token)) return token.map(tokenText).join(''); + return tokenText((token as {content: unknown}).content); +} + +function tokenTypes(source: string): string[] { + function types(token: unknown): string[] { + if (typeof token === 'string') return []; + if (Array.isArray(token)) return token.flatMap(types); + const prismToken = token as {type: string; content: unknown}; + return [prismToken.type, ...types(prismToken.content)]; + } + return AslPrism.tokenize(source, AslPrism.languages.asl).flatMap(types); +} + +aslTest('ASL grammar highlights structure without changing source text', () => { + registerAslPrism(); + const source = [ + '// decoded operation', + 'readonly func DecodeADD(encoded: bits(2)) => boolean', + 'begin', + " let legal = encoded != '11';", + ' return legal && TRUE;', + 'end;', + ].join('\n'); + const tokens = AslPrism.tokenize(source, AslPrism.languages.asl); + const flattened = tokens.map(tokenText).join(''); + const types = tokenTypes(source); + + aslAssert.equal(flattened, source); + aslAssert.ok(types.includes('comment')); + aslAssert.ok(types.includes('keyword')); + aslAssert.ok(types.includes('function')); + aslAssert.ok(types.includes('boolean')); + aslAssert.ok(types.includes('operator')); +}); diff --git a/docs/site/src/theme/aslPrism.ts b/docs/site/src/theme/aslPrism.ts new file mode 100644 index 000000000..e6c439866 --- /dev/null +++ b/docs/site/src/theme/aslPrism.ts @@ -0,0 +1,24 @@ +import {Prism} from 'prism-react-renderer'; + +let registered = false; + +export function registerAslPrism(): void { + if (registered && Prism.languages.asl !== undefined) return; + Prism.languages.asl = { + comment: /\/\/.*$/m, + string: { + pattern: /'(?:\\.|[^'\\\r\n])*'|"(?:\\.|[^"\\\r\n])*"/, + greedy: true, + }, + keyword: /\b(?:array|as|assert|begin|bits|case|catch|config|constant|do|downto|else|elsif|end|enum|enumeration|for|func|if|impdef|in|integer|let|looplimit|of|otherwise|pure|readonly|real|record|repeat|return|then|throw|to|try|type|typeof|until|var|when|where|while)\b/, + boolean: /\b(?:TRUE|FALSE)\b/, + number: /\b(?:0[xX][\dA-Fa-f](?:_?[\dA-Fa-f])*|0[bB][01](?:_?[01])*|\d(?:_?\d)*)\b/, + builtin: /\b(?:Zeros|Ones|UInt|SInt|ZeroExtend|SignExtend|LSL|LSR|ASR|ROR)\b/, + function: /\b[A-Za-z_][A-Za-z\d_]*(?=\s*(?:\{|\())/, + operator: /=>|==|!=|<=|>=|&&|\|\||<<|>>|::|[-+*/%&|^~!<>=]/, + punctuation: /[{}[\];(),.:]/, + }; + registered = true; +} + +registerAslPrism(); diff --git a/docs/site/src/types/pto.ts b/docs/site/src/types/pto.ts index b22fb32c5..076c49975 100644 --- a/docs/site/src/types/pto.ts +++ b/docs/site/src/types/pto.ts @@ -63,6 +63,18 @@ export type PtoReaderNode = | {kind: 'table-row'; cells: PtoReaderInline[][]} | {kind: 'code-block'; language: string | null; text: string}; +export interface PtoSemanticIdentityFacet { + role: 'namespace' | 'surface' | 'owner' | 'category' | 'case' | 'decision'; + label: string; +} + +export interface PtoSemanticIdentity { + fullId: string; + kind: 'ndf' | 'avs' | 'adr'; + anchor: string; + facets: PtoSemanticIdentityFacet[]; +} + export interface PtoReaderGuideBlock { id: string; role: PtoReaderGuideRole; @@ -86,6 +98,45 @@ export interface PtoReaderGuide { owners: PtoReaderGuideOwnerLink[]; } +export interface PtoArchitectureOwnerProjection { + id: string; + label: string; + route: string; + referenceRoute: string; + sourcePath: string; + sourceSha256: string; + sourceUrl: string; + guideStatus: PtoReaderGuideStatus; + guideSha256: string; + contentLocale: string; + blocks: PtoReaderGuideBlock[]; +} + +export interface PtoArchitectureBoundGuideBlock { + ownerId: string; + sourcePath: string; + sourceSha256: string; + guideSha256: string; + block: PtoReaderGuideBlock; +} + +export interface PtoArchitectureTopicProjection { + id: string; + label: string; + scenario: PtoArchitectureBoundGuideBlock; + sourceBoundary: PtoArchitectureBoundGuideBlock | null; + primary: PtoArchitectureOwnerProjection; + related: PtoArchitectureOwnerProjection[]; +} + +export interface PtoArchitectureGuide { + schema: 'pto.site-architecture-guide.v1'; + locale: string; + release: PtoReleaseIdentity; + entry: PtoArchitectureOwnerProjection; + topics: PtoArchitectureTopicProjection[]; +} + export interface PtoNdfClause { id: string; kind: string; @@ -96,6 +147,11 @@ export interface PtoNdfClause { sourcePath: string; startLine: number; endLine: number; + sourceSha256: string; + clauseSha256: string; + githubUrl: string; + affectedUnits: string[]; + identity: PtoSemanticIdentity; } export interface PtoTestEvidence { @@ -111,6 +167,10 @@ export interface PtoTestEvidence { sourceText?: string; sourceAssetUrl: string; githubUrl: string; + identity: PtoSemanticIdentity; + ownerId: string; + ownerMnemonic: string | null; + surface: string; } export interface PtoAdrRecord { @@ -122,7 +182,10 @@ export interface PtoAdrRecord { affectedNdf: string[]; affectedUnits: string[]; targetReleases: string[]; + decisionAssetUrl: string; githubUrl: string; + sourceSha256: string; + identity: PtoSemanticIdentity; } export interface PtoArtifactEvidence { @@ -143,6 +206,110 @@ export interface PtoUnitEncoding { tileOperation?: Record; } +export interface PtoAssemblerSymbol { + field: string; + width: string; + signedness: string; + role: string; + zeroMeaning: string; +} + +export interface PtoLocalizedText { + en: string; + 'zh-CN': string; +} + +export interface PtoInstructionCompositionParameter { + name: string; + meaning: PtoLocalizedText; + omission?: PtoLocalizedText; +} + +export interface PtoInstructionCompositionReference { + id: string; + route: string; + sourcePath: string; + sourceUrl: string; +} + +export interface PtoInstructionCompositionCommand { + mnemonic: string; + minOccurrences: number; + maxOccurrences: number; + repeatable: boolean; + requirement: 'required' | 'optional' | 'conditional' | 'forbidden'; + role: PtoLocalizedText; + parameters: PtoInstructionCompositionParameter[]; + reference?: PtoInstructionCompositionReference; +} + +export interface PtoInstructionCompositionVariant { + id: string; + label: PtoLocalizedText; + summary: PtoLocalizedText; + canonicalCommandCount: PtoLocalizedText; + canonicalMinCommands: number; + canonicalMaxCommands: number; + minimumSequence: string[]; + completeSequence: string[]; + relationships: PtoLocalizedText[]; + commands: PtoInstructionCompositionCommand[]; +} + +export interface PtoInstructionComposition { + owner: string; + variants: PtoInstructionCompositionVariant[]; +} + +export interface PtoSemanticSourceRegion { + id: string; + label: PtoLocalizedText; + purpose: PtoLocalizedText; + sourcePath: string; + sourceUrl: string; + sourceSha256: string; + fragmentSha256: string; + startLine: number; + endLine: number; + text?: string; + sourceAssetUrl?: string; +} + +export interface PtoSemanticExecutionStage { + id: string; + label: PtoLocalizedText; + summary: PtoLocalizedText; + status: 'complete' | 'source-gap'; + gap?: PtoLocalizedText; + facts: Array<{ + kind: 'inputs' | 'checks' | 'faults' | 'reads' | 'writes' | 'commit'; + label: PtoLocalizedText; + items: PtoLocalizedText[]; + }>; + ownerRegions: PtoSemanticSourceRegion[]; + sharedRegions: PtoSemanticSourceRegion[]; +} + +export interface PtoSemanticExecution { + owner: string; + stages: PtoSemanticExecutionStage[]; +} + +export interface PtoHighLevelAssemblyBinding { + display: string; + source: string; + role: string; +} + +export interface PtoHighLevelAssembly { + form: string; + parameters: PtoHighLevelAssemblyBinding[]; + inputs: PtoHighLevelAssemblyBinding[]; + outputs: PtoHighLevelAssemblyBinding[]; + attributes: PtoHighLevelAssemblyBinding[]; + basis: string[]; +} + export interface PtoUnitWorkbenchData { release: PtoReleaseIdentity; source: PtoSourceIdentity; @@ -155,6 +322,10 @@ export interface PtoUnitWorkbenchData { adrs: PtoAdrRecord[]; evidence: PtoArtifactEvidence[]; encoding: PtoUnitEncoding; + assemblerSymbols: PtoAssemblerSymbol[]; + composition: PtoInstructionComposition | null; + semanticExecution: PtoSemanticExecution | null; + highLevelAssembly: PtoHighLevelAssembly | null; } /** Compatibility name for the first TLOAD workbench implementation. */ @@ -217,6 +388,67 @@ export interface PtoNdfIndexPageData { entries: PtoNdfIndexEntry[]; } +export interface PtoInstructionIndexEntry { + id: string; + mnemonic: string; + surface: 'scalar' | 'block' | 'tile'; + classification: string[]; + route: string; + summary: string; + sourcePath: string; + sourceSha256: string; +} + +export interface PtoInstructionIndexData { + release: PtoReleaseIdentity; + entries: PtoInstructionIndexEntry[]; +} + +export interface PtoNdfOwnerRoute { + id: string; + mnemonic: string | null; + surface: string; + route: string; + sourcePath: string; +} + +export interface PtoNdfCatalogEntry { + id: string; + entryKind: 'clause' | 'instruction-contract'; + kind: string; + level: string; + layer: string; + status: string; + text: string | null; + route: string; + sourcePath: string; + sourceSha256: string; + clauseSha256: string; + owners: PtoNdfOwnerRoute[]; +} + +export interface PtoNdfCatalogData { + release: PtoReleaseIdentity; + entries: PtoNdfCatalogEntry[]; +} + +export interface PtoNdfDetailData { + release: PtoReleaseIdentity; + clause: PtoNdfClause; + owners: PtoNdfOwnerRoute[]; + adrs: PtoAdrRecord[]; + tests: PtoTestEvidence[]; + evidence: PtoArtifactEvidence[]; + relationships: Array<{ + kind: PtoGraphEdgeKind; + direction: 'incoming' | 'outgoing'; + node: PtoGraphNode; + href: string; + external: boolean; + }>; + relationshipFallbackRoute: string; +} + export type PtoSearchEntryKind = 'adr' | 'ndf' | 'asl' | 'avs'; export interface PtoSearchEntry { diff --git a/docs/site/tests/site.spec.ts b/docs/site/tests/site.spec.ts index 5eea53a6f..1ecc49762 100644 --- a/docs/site/tests/site.spec.ts +++ b/docs/site/tests/site.spec.ts @@ -1,9 +1,22 @@ import {expect, test} from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; +import {readFileSync} from 'node:fs'; +import {resolve} from 'node:path'; import {isCommonMarkFenceClose} from '../plugins/pto-content/index'; const tloadRoute = '/instructions/tile/memory-and-data-movement/regular/TLOAD/'; +const traceability = JSON.parse(readFileSync( + resolve(process.cwd(), '../../spec/evidence/release-traceability-readiness.json'), + 'utf8', +)) as {units: Array<{mnemonic: string | null; surface: 'arch' | 'scalar' | 'block' | 'tile'}>}; +const instructionUnits = traceability.units.filter((unit) => unit.mnemonic !== null); +const instructionSurfaceTotals = Object.fromEntries( + ['scalar', 'block', 'tile'].map((surface) => [ + surface, + instructionUnits.filter((unit) => unit.surface === surface).length, + ]), +) as {scalar: number; block: number; tile: number}; test('reader-guide code fences use CommonMark closing rules', () => { expect(isCommonMarkFenceClose('```', '```')).toBe(true); @@ -17,19 +30,16 @@ test('latest release landing page teaches the architecture before implementation page, }) => { await page.goto('/'); - await expect(page.getByRole('heading', {level: 1})).toContainText('PTO Architecture'); + await expect(page.getByRole('heading', {level: 1})).toContainText('Architecture'); await expect(page.getByText(/^v\d+\.\d+\.\d+\.\d+$/, {exact: true})).toBeVisible(); await expect(page.getByRole('heading', {name: 'Scalar', level: 3})).toBeVisible(); await expect(page.getByRole('heading', {name: 'Block', level: 3})).toBeVisible(); await expect(page.getByRole('heading', {name: 'Tile', level: 3})).toBeVisible(); await expect(page.getByRole('heading', {name: /ADR explains why/})).toBeVisible(); const scalarCard = page.getByRole('heading', {name: 'Scalar', level: 3}).locator('xpath=ancestor::article[1]'); - const scalarInventory = await scalarCard.getByText(/ASL units/).innerText(); - const scalarUnitCount = scalarInventory.match(/(\d+)\s+ASL\s+units/i)?.[1]; - expect(scalarUnitCount).toBeTruthy(); await scalarCard.getByRole('link', {name: /Browse Scalar units/}).click(); - await expect(page).toHaveURL(/kind=asl&surface=scalar/); - await expect(page.getByRole('status')).toContainText(`${scalarUnitCount} matching identities`); + await expect(page).toHaveURL(/\/instructions\/\?surface=scalar/); + await expect(page.getByRole('status')).toContainText(`${instructionSurfaceTotals.scalar} instructions`); await page.goto('/'); await page.getByRole('searchbox').fill('TLOAD'); await page.getByRole('button', {name: 'Search specification'}).click(); @@ -39,6 +49,85 @@ test('latest release landing page teaches the architecture before implementation await expect(page).toHaveURL(tloadRoute); }); +test('instruction coverage matrix closes the released bilingual inventory', async ({page}) => { + await page.goto('/'); + const matrices = await page.evaluate(async () => Promise.all([ + fetch('/pto-instruction-coverage.json').then((response) => response.json()), + fetch('/zh-CN/pto-instruction-coverage.json').then((response) => response.json()), + ])); + for (const matrix of matrices) { + expect(matrix.schema).toBe('pto.site-instruction-coverage.v1'); + expect(matrix.instruction_count).toBe(instructionUnits.length); + expect(matrix.surface_totals).toEqual(instructionSurfaceTotals); + expect(matrix.unexplained_omissions).toBe(0); + expect(matrix.entries).toHaveLength(instructionUnits.length); + expect(matrix.entries.filter((entry: {surface: string; bundle_source: string}) => + entry.surface === 'tile' && entry.bundle_source === 'owner-metadata')).toHaveLength( + instructionSurfaceTotals.tile, + ); + } + expect(matrices[0].entries.every((entry: {route: string}) => !entry.route.startsWith('/zh-CN/'))).toBe(true); + expect(matrices[1].entries.every((entry: {route: string}) => entry.route.startsWith('/zh-CN/'))).toBe(true); +}); + +test('instruction and NDF indexes route every identity to one canonical page', async ({page}) => { + await page.goto('/instructions/'); + await expect(page.getByRole('heading', {name: 'Instruction index', level: 1})).toBeVisible(); + await expect(page.getByRole('status')).toContainText(`${instructionUnits.length} instructions`); + await page.getByRole('button', {name: 'Tile', exact: true}).click(); + await expect(page.getByRole('status')).toContainText(`${instructionSurfaceTotals.tile} instructions`); + await page.getByRole('searchbox', {name: 'Search instructions'}).fill('TLOAD'); + await page.getByRole('link', {name: 'TLOAD', exact: true}).click(); + await expect(page).toHaveURL(tloadRoute); + + await page.goto('/ndf/'); + await expect(page.getByRole('heading', {name: 'NDF index', level: 1})).toBeVisible(); + await page.getByRole('searchbox', {name: 'Search NDF'}).fill('PTO-TLOAD-MEMORY-001'); + const ndfCard = page.locator('#index-pto-tload-memory-001'); + await expect(ndfCard).toBeVisible(); + await ndfCard.getByRole('link', {name: 'contract', exact: true}).click(); + await expect(page).toHaveURL('/ndf/PTO-TLOAD-MEMORY-001/'); + await expect(page.getByText(/byte row stride/).first()).toBeVisible(); + const ownerPages = page.getByRole('region', {name: 'Instruction and unit pages'}); + await expect(ownerPages.getByRole('link', {name: 'TLOAD', exact: true})).toHaveAttribute( + 'href', + tloadRoute, + ); + + await page.goto('/zh-CN/instructions/'); + await expect(page.getByRole('heading', {name: '指令索引', level: 1})).toBeVisible(); + await page.goto('/zh-CN/ndf/PTO-TLOAD-MEMORY-001/'); + await expect(page).toHaveURL('/zh-CN/ndf/PTO-TLOAD-MEMORY-001/'); +}); + +test('instruction rail and facet buttons share the URL-selected surface', async ({page}, testInfo) => { + test.skip(testInfo.project.name !== 'desktop-chromium', 'The persistent navigation rail is a desktop surface.'); + + await page.goto('/instructions/?surface=scalar'); + const rail = page.locator('aside[aria-label="Left specification navigation"]'); + const filters = page.getByRole('region', {name: 'Instruction filters'}); + await expect(rail.locator('[data-navigation-id="scalar"]')).toHaveAttribute('data-section-current', 'true'); + await expect(filters.getByRole('button', {name: 'Scalar', exact: true})).toHaveAttribute('aria-pressed', 'true'); + await expect(page.getByRole('status')).toContainText(`${instructionSurfaceTotals.scalar} instructions`); + + await rail.locator('[data-navigation-id="block"]').click(); + await expect(page).toHaveURL('/instructions/?surface=block'); + await expect(rail.locator('[data-navigation-id="block"]')).toHaveAttribute('data-section-current', 'true'); + await expect(filters.getByRole('button', {name: 'Block', exact: true})).toHaveAttribute('aria-pressed', 'true'); + await expect(page.getByRole('status')).toContainText(`${instructionSurfaceTotals.block} instructions`); + + await filters.getByRole('button', {name: 'Tile', exact: true}).click(); + await expect(page).toHaveURL('/instructions/?surface=tile'); + await expect(rail.locator('[data-navigation-id="tile"]')).toHaveAttribute('data-section-current', 'true'); + await expect(filters.getByRole('button', {name: 'Tile', exact: true})).toHaveAttribute('aria-pressed', 'true'); + await expect(page.getByRole('status')).toContainText(`${instructionSurfaceTotals.tile} instructions`); + + await page.goBack(); + await expect(page).toHaveURL('/instructions/?surface=block'); + await expect(rail.locator('[data-navigation-id="block"]')).toHaveAttribute('data-section-current', 'true'); + await expect(filters.getByRole('button', {name: 'Block', exact: true})).toHaveAttribute('aria-pressed', 'true'); +}); + test('representative mnemonic, model, and architecture units use the generic workbench', async ({ page, }, testInfo) => { @@ -48,13 +137,27 @@ test('representative mnemonic, model, and architecture units use the generic wor page.locator('header code').filter({hasText: /^PTO-SCALAR-ADD$/}), ).toBeVisible(); await expect(page.getByRole('heading', {name: 'Encoding', level: 2})).toBeVisible(); - await expect(page.getByText('docs/scalar/alu/ADD.md', {exact: true})).toBeVisible(); - const readerGuide = page.getByRole('region', {name: 'Understand this instruction'}); - await expect(readerGuide.getByText('Reader guide · non-normative explanation', {exact: true})).toBeVisible(); + await expect(page.locator('header').getByText(/ADD applies the selected right-source transformation/)).toBeVisible(); + await expect(page.getByText('At a glance', {exact: true})).toHaveCount(0); + await expect(page.getByRole('heading', {name: 'Assembly syntax', level: 2})).toBeVisible(); + await expect( + page.getByRole('heading', {name: 'Assembly syntax', level: 2}).locator('..').getByText( + 'add SrcL, SrcR<{.sw,.uw,.neg}><<, ->{t, u, Rd}', + {exact: true}, + ), + ).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Assembler symbols', level: 2})).toBeVisible(); + const symbols = page.getByRole('table', {name: 'Assembly fields and architectural roles'}); + await expect(symbols.getByText('SrcRType', {exact: true})).toBeVisible(); + await expect(symbols.getByText('right-source transformation selector', {exact: true})).toBeVisible(); + const readerGuide = page.getByRole('region', {name: 'Behavior'}); await expect(readerGuide.getByRole('heading', {name: 'What ADD does'})).toBeVisible(); await expect(readerGuide.getByText(/being migrated and independently reviewed/)).toHaveCount(0); - await expect(readerGuide.getByRole('heading', {name: 'Exact normative owners'})).toBeVisible(); - await expect(readerGuide.getByRole('link', {name: 'PTO-SCALAR-ADD'})).toHaveAttribute( + const sourceLedger = page.getByRole('region', {name: 'Sources and release identity'}); + await sourceLedger.getByText(/Show commit, paths, hashes, version/).click(); + await expect(sourceLedger.getByText('docs/scalar/alu/ADD.md', {exact: true})).toBeVisible(); + await expect(sourceLedger.getByRole('heading', {name: 'Exact owners'})).toBeVisible(); + await expect(sourceLedger.getByRole('link', {name: 'PTO-SCALAR-ADD'})).toHaveAttribute( 'href', /github\.com\/PTO-ISA\/pto-spec\/blob\/[0-9a-f]{40}\/asl\/scalar\/alu\/ADD\.asl/, ); @@ -62,13 +165,24 @@ test('representative mnemonic, model, and architecture units use the generic wor const encodingPosition = await page.getByRole('heading', {name: 'Encoding', level: 2}).evaluate( (element) => element.getBoundingClientRect().top, ); - expect(guidePosition).toBeLessThan(encodingPosition); + const aslPosition = await page.getByRole('heading', {name: 'ASL pseudocode', level: 2}).evaluate( + (element) => element.getBoundingClientRect().top, + ); + const sourcePosition = await sourceLedger.evaluate((element) => element.getBoundingClientRect().top); + expect(encodingPosition).toBeLessThan(aslPosition); + expect(aslPosition).toBeLessThan(guidePosition); + expect(sourcePosition).toBeGreaterThan(encodingPosition); + await expect(page.getByRole('heading', {name: 'Decode source binding', level: 3})).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Operation source binding', level: 3})).toBeVisible(); + await expect(page.getByLabel(/decode source binding ASL source/)).toContainText('InstructionContractOperation_ADD'); + await expect(page.getByLabel(/operation ASL source/)).toContainText('InstructionContractHandler_ADD'); + await expect(page.getByText(/non-normative|unpublished release preview/i)).toHaveCount(0); const addEncoding = page.getByRole('heading', {name: 'Encoding', level: 2}).locator('..'); await expect(addEncoding.getByRole('img', {name: /WaveDrom encoding diagram/})).toBeVisible(); await expect(addEncoding.locator('[data-wavedrom-source="catalog-encoding-json"]')).toBeVisible(); await addEncoding.getByText('WaveJSON source', {exact: true}).click(); await expect(addEncoding.locator('pre')).toContainText('"bits": 32'); - await expect(addEncoding.locator('pre')).toContainText('"name": "Fixed selector"'); + await expect(addEncoding.locator('pre')).toContainText('"name": "7\'b0000101"'); await page.goto('/instructions/scalar/bru/C.CMP.EQI/'); const compactEncoding = page.getByRole('heading', {name: 'Encoding', level: 2}).locator('..'); @@ -90,8 +204,8 @@ test('representative mnemonic, model, and architecture units use the generic wor const blockTables = page.getByText('Encoding fields as an accessible table', {exact: true}); await blockTables.nth(0).click(); await blockTables.nth(1).click(); - await expect(page.getByRole('table').nth(0).getByText('0x0000000f', {exact: true})).toBeVisible(); - await expect(page.getByRole('table').nth(1).getByText('0x00000001', {exact: true})).toBeVisible(); + await expect(page.getByRole('table').nth(0).getByRole('row', {name: "Constant 31:0 32'b00000000000000000000000000001111"})).toBeVisible(); + await expect(page.getByRole('table').nth(1).getByRole('row', {name: "Constant 63:32 32'b00000000000000000000000000000001"})).toBeVisible(); const blockWaveJson = page.getByText('WaveJSON source', {exact: true}); await expect(blockWaveJson).toHaveCount(2); await blockWaveJson.nth(1).click(); @@ -127,8 +241,10 @@ test('representative mnemonic, model, and architecture units use the generic wor ]) { await page.goto(`/units/${unit.id}/`); await expect(page.getByRole('heading', {name: unit.id, level: 1})).toBeVisible(); + const unitLedger = page.getByRole('region', {name: 'Sources and release identity'}); + await unitLedger.getByText(/Show commit, paths, hashes, version/).click(); await expect( - page.getByRole('link', {name: unit.source, exact: true}), + unitLedger.getByRole('link', {name: unit.source, exact: true}), ).toBeVisible(); await expect(page.getByRole('heading', {name: 'Encoding', level: 2})).toHaveCount(0); await expect(page.getByRole('button', {name: 'Next'})).toHaveCount(0); @@ -136,6 +252,7 @@ test('representative mnemonic, model, and architecture units use the generic wor await page.goto('/units/PTO-BLOCK-MODEL-OPERANDS-SUBVIEW-DESCRIPTOR/'); + await page.getByText('Executable evidence', {exact: true}).click(); await expect(page.getByRole('navigation', {name: 'Executable evidence pages'})).toBeVisible(); await expect(page.getByText(/Page 1 of \d+/)).toBeVisible(); const nextEvidencePage = page.getByRole('navigation', {name: 'Executable evidence pages'}).getByRole('button', {name: 'Next'}); @@ -148,6 +265,129 @@ test('representative mnemonic, model, and architecture units use the generic wor await expect(page.getByText(/Page 2 of \d+/)).toBeVisible(); }); +test('custom routes expose the stable desktop rail and accessible mobile navigation', async ({page}, testInfo) => { + const routes = [ + {path: '/', current: 'home'}, + {path: '/architecture/', current: 'architecture'}, + {path: tloadRoute, current: 'tile'}, + {path: '/explore/ndf/', current: 'ndf-explorer'}, + ]; + for (const route of routes) { + await page.goto(route.path); + if (testInfo.project.name === 'desktop-chromium') { + const rail = page.locator('aside[aria-label="Left specification navigation"]'); + await expect(rail).toBeVisible(); + await expect(rail.getByRole('navigation', {name: 'Specification navigation'})).toBeVisible(); + await expect(rail.locator(`[data-navigation-id="${route.current}"]`)).toHaveAttribute('data-section-current', 'true'); + await expect(rail.locator('[data-current-page="true"]')).toHaveAttribute('aria-current', 'page'); + await expect(rail.locator('[data-current-page="true"]')).toHaveAttribute('href', route.path); + await expect(rail.locator('[data-navigation-id="architecture"]')).toBeVisible(); + expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true); + } else { + const drawer = page.locator('details').filter({has: page.getByText('Browse specification', {exact: true})}).first(); + await expect(drawer.getByText('Browse specification', {exact: true})).toBeVisible(); + await drawer.getByText('Browse specification', {exact: true}).focus(); + await drawer.getByText('Browse specification', {exact: true}).press('Enter'); + await expect(drawer).toHaveAttribute('open', ''); + await expect(drawer.getByRole('navigation', {name: 'Specification navigation'})).toBeVisible(); + await expect(drawer.locator(`[data-navigation-id="${route.current}"]`)).toHaveAttribute('data-section-current', 'true'); + await expect(drawer.locator('[data-current-page="true"]')).toHaveAttribute('aria-current', 'page'); + expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true); + } + } +}); + +test('Architecture landing is source-backed and exposes every required mental-model topic', async ({page}) => { + await page.goto('/architecture/'); + await expect(page.getByRole('heading', {name: 'Architecture', level: 1})).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Architecture mental model'})).toBeVisible(); + for (const topic of [ + 'Programming and execution model', + 'Architectural state', + 'Registers and Tile storage', + 'Memory model', + 'Types and shape model', + 'Faults, exceptions, and diagnostics', + 'Version and compatibility', + ]) { + await expect(page.getByRole('heading', {name: topic, exact: true})).toBeVisible(); + } + await expect(page.locator('[data-source-owner][data-guide-sha256]').first()).toBeVisible(); + await expect(page.getByText('Current owner-declared boundary', {exact: true})).toHaveCount(4); + await expect(page.locator('[role="note"][data-source-sha256][data-guide-sha256]')).toHaveCount(4); + await expect(page.getByText('PTO-ARCH-PROGRAMMING-MODEL-EXECUTION-CONTEXT', {exact: true}).first()).toBeVisible(); + await expect(page.getByText('PTO-ARCH-MEMORY-MODEL-ORDERING', {exact: true}).first()).toBeVisible(); + await expect(page.getByRole('link', {name: 'Open original ASL ↗'}).first()).toHaveAttribute( + 'href', + /github\.com\/PTO-ISA\/pto-spec\/blob\/[0-9a-f]{40}\/asl\/arch\//, + ); + const provenance = page.getByRole('region', {name: 'Sources and release identity'}); + await expect(provenance.locator('details')).not.toHaveAttribute('open', ''); +}); + +test('legacy unit references redirect to canonical workbenches while project records remain browsable', async ({page}, testInfo) => { + await page.goto('/reference/scalar/alu/ADD/'); + await expect(page).toHaveURL('/instructions/scalar/alu/ADD/'); + await expect(page.getByRole('heading', {name: 'ADD', level: 1})).toBeVisible(); + if (testInfo.project.name === 'desktop-chromium') { + await page.goto('/reference/governance/adr-process/'); + const sidebar = page.getByRole('navigation', {name: 'Docs sidebar'}); + await expect(sidebar).toHaveCount(1); + await expect(sidebar).toBeVisible(); + await expect(sidebar.getByText('Decisions and architecture records', {exact: true})).toBeVisible(); + await page.goto('/zh-CN/reference/arch/overview/architecture/'); + await expect(page).toHaveURL('/zh-CN/units/PTO-ARCH-OVERVIEW-ARCHITECTURE/'); + } +}); + +test('source-declared bundle template covers representative Tile families', async ({page}) => { + await page.goto('/instructions/tile/elementwise-tile-tile/arithmetic/TADD/'); + await expect(page.getByRole('heading', {name: '2. Complete Bundle Assembly'})).toBeVisible(); + const taddBundle = page.locator('[class*="genericBundleSequence"]'); + await expect(taddBundle.getByText(/BSTART\.VEC TADD/)).toBeVisible(); + await expect(taddBundle.getByText(/B\.IOT SrcLeft, SrcRight/)).toBeVisible(); + await expect(taddBundle.getByText('Repeatable', {exact: true}).first()).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Instruction contract'})).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Constraints, checks, and faults'})).toBeVisible(); + await expect(page.getByRole('heading', {name: 'State reads, writes, and result'})).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Decode source binding'})).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Operation source binding'})).toBeVisible(); + + await page.goto('/instructions/tile/memory-and-data-movement/regular/TSTORE/'); + for (const variant of ['Local', 'Shared']) { + await expect(page.getByRole('heading', {name: variant, exact: true})).toBeVisible(); + } + const sharedVariant = page.getByRole('heading', {name: 'Shared', exact: true}).locator('..'); + await expect(sharedVariant.getByText(/one source B\.IOS/).first()).toBeVisible(); + await expect(sharedVariant.getByText(/any nonzero consumer PE_MASK/).first()).toBeVisible(); + await expect(sharedVariant.getByText(/optional B\.SUBVIEW/).first()).toBeVisible(); + await expect(page.getByRole('region', {name: 'Instruction contract'})).toContainText( + 'B.SUBVIEW is the explicit source range mechanism', + ); + await expect(page.getByText('Bundle source gap', {exact: true})).toHaveCount(0); + + await page.goto('/instructions/tile/matrix-and-matrix-vector/matrix-matrix/TMATMUL/'); + await expect(page.getByText( + 'TMATMUL SrcTile0, SrcTile1 -> DstTile', + {exact: true}, + )).toBeVisible(); + await page.getByText('Show High Level Assembly operands mapped to the owning ASL', {exact: true}).click(); + const highLevelBindings = page.getByRole('table', {name: 'Source bindings for High Level Assembly operands'}); + await expect(highLevelBindings.getByRole('row', {name: /Parameter DataTypeA left data type BSTART\.TMATMUL AType/})).toBeVisible(); + await expect(highLevelBindings.getByRole('row', {name: /Output DstTile destination contract\.operands\[0\]\.destination0/})).toBeVisible(); + await expect(page.getByText('Repeatable', {exact: true}).first()).toBeVisible(); + await expect(page.getByText('Mutually exclusive', {exact: true}).first()).toBeVisible(); + + await page.goto('/zh-CN/instructions/tile/elementwise-tile-tile/arithmetic/TADD/'); + await expect(page.getByText( + 'TADD SrcTile0, SrcTile1 -> DstTile', + {exact: true}, + )).toBeVisible(); + await expect(page.getByRole('heading', {name: '2. 完整 Bundle Assembly'})).toBeVisible(); + await expect(page.getByText('可重复', {exact: true}).first()).toBeVisible(); + await expect(page).toHaveURL(/\/zh-CN\/instructions\/tile\/elementwise-tile-tile\/arithmetic\/TADD\//); +}); + test('TLOAD workbench preserves source identity and evidence interaction', async ({ page, }, testInfo) => { @@ -155,32 +395,138 @@ test('TLOAD workbench preserves source identity and evidence interaction', async await expect(page.getByRole('heading', {name: 'TLOAD', level: 1})).toBeVisible(); await expect( page.getByRole('link', { - name: 'asl/tile/memory-and-data-movement/regular/TLOAD.asl', - exact: true, + name: 'TLOAD owning ASL', }), ).toBeVisible(); - await expect(page.getByText(/PTO-TLOAD-MEMORY-001 — contract/)).toBeVisible(); + const composition = page.getByRole('region', {name: 'Assembly syntax'}).first(); + await expect(composition).toBeVisible(); + await expect(composition.getByText(/TLOAD is not one standalone encoding/)).toBeVisible(); + await expect(composition.getByRole('heading', {name: '1. High Level Assembly'})).toBeVisible(); + await expect(composition.getByRole('heading', {name: '2. Complete Bundle Assembly'})).toBeVisible(); + await expect(composition.getByRole('heading', {name: '3. Bundle operand and parameter semantics'})).toBeVisible(); + await expect(composition.getByRole('heading', {name: 'Minimum legal bundle'})).toBeVisible(); + const minimumBundle = composition.getByRole('region', {name: 'Minimum legal bundle'}); + await expect(minimumBundle.getByText('BSTART.TLOAD U8', {exact: true})).toBeVisible(); + await expect(minimumBundle.getByText('B.IOT mask=1111, , ->T<1>', {exact: true})).toBeVisible(); + await expect(minimumBundle.getByText('BSTOP', {exact: true})).toBeVisible(); + await expect(composition.getByText('3-command minimum; 8-command complete form')).toBeVisible(); + const biorRow = composition.getByRole('row', {name: /B\.IOR/}); + await expect(biorRow).toContainText('RegSrc0'); + await expect(biorRow).toContainText('GM base byte address'); + await expect(biorRow).toContainText('RegSrc1'); + await expect(biorRow).toContainText('byte distance between adjacent row starts'); + await expect(biorRow.getByRole('link', {name: 'Instruction page'})).toHaveAttribute('href', /instructions\/block\/operands\/B\.IOR/); + await composition.getByRole('tab', {name: 'Ordinary Shared destination'}).click(); + await expect(composition.getByText('6-command minimum; 8-command complete form')).toBeVisible(); + const sharedDimension = composition.getByRole('row', {name: /B\.DIM/}); + await expect(sharedDimension.getByText('Required', {exact: true})).toBeVisible(); + await expect(sharedDimension).toContainText('exactly 3'); + await expect(sharedDimension).toContainText('LB2 / Col'); + await expect(composition.getByRole('region', {name: 'Minimum legal bundle'}).getByText('B.IOS mask=0001, ->S7<1>', {exact: true})).toBeVisible(); + await composition.getByRole('tab', {name: 'Local CUBE conversion'}).click(); + const bundleAssemblyLayer = composition.getByRole('region', {name: '2. Complete Bundle Assembly'}); + await expect(bundleAssemblyLayer.getByText(/physical CUBE CELL tail/)).toBeVisible(); + await expect(bundleAssemblyLayer.getByText(/not directional padding/)).toBeVisible(); + await expect(bundleAssemblyLayer.getByText(/LB2 is mutually exclusive/)).toBeVisible(); + const dataFlow = composition.getByRole('region', {name: 'Two-dimensional data flow'}); + for (const label of ['GM address', '2D shape', 'Format and type', 'Bundle checks', 'Destination Tile']) { + await expect(dataFlow.getByRole('heading', {name: label})).toBeVisible(); + } + + const bundlePosition = await composition.evaluate((element) => element.getBoundingClientRect().top); + const encoding = page.getByRole('heading', {name: 'Entry instruction encoding: BSTART'}).locator('..'); + const encodingPosition = await encoding.evaluate((element) => element.getBoundingClientRect().top); + expect(bundlePosition).toBeLessThan(encodingPosition); + await expect(encoding.getByText(/encodes only the bundle-entry BSTART command/)).toBeVisible(); + + const execution = page.getByRole('region', {name: 'ASL execution path'}); + await expect(execution).toBeVisible(); + await expect(execution.getByRole('heading', {name: '1. Bundle structure', level: 3})).toBeVisible(); + await expect(execution.getByRole('heading', {name: '5. Complete-footprint preflight', level: 3})).toBeVisible(); + await expect(execution.getByRole('heading', {name: '7. Publish, commit, or no effect', level: 3})).toBeVisible(); + for (const label of ['Inputs', 'Checks', 'Failure', 'Read state', 'Constraints', 'Faults', 'State writes', 'Commit / result']) { + await expect(execution.getByRole('heading', {name: label}).first()).toBeVisible(); + } + await expect(execution.getByText(/current owner exposes instruction selection/i)).toBeVisible(); + await expect(execution.getByText(/current owner binds the generic Tile handler/i)).toBeVisible(); + await expect(execution.getByLabel(/TLOAD\.asl lines/).filter({hasText: 'InstructionContractGMAddress_TLOAD'})).toBeVisible(); + const localMemorySource = execution.getByText(/Show shared ASL: Local preflight and load/); + await localMemorySource.click(); + await expect(execution.getByLabel(/load-store\.asl lines/)).toContainText('ProbeTileMemoryAccess'); + const sharedMemorySource = execution.getByText(/Show shared ASL: Shared preflight and atomic update/); + await sharedMemorySource.click(); + await expect(execution.getByLabel(/shared-movement\.asl lines/)).toContainText('AtomicUpdateSharedTile'); + await expect(execution.getByRole('link', {name: /Exact source/}).first()).toHaveAttribute('href', /#L\d+-L\d+$/); + + const memoryClause = page.locator('#ndf-pto-tload-memory-001'); + await expect(memoryClause).toBeVisible(); + await expect(memoryClause.getByText(/TLOAD MUST use B\.IOR RegSrc0 as the per-PE GM base/)).toBeVisible(); + await expect(memoryClause.getByLabel(/NDF identity PTO-TLOAD-MEMORY-001/).getByText('TLOAD', {exact: true})).toBeVisible(); + await expect(memoryClause.getByRole('button', {name: /Copy complete stable ID PTO-TLOAD-MEMORY-001/})).toBeVisible(); + await expect(memoryClause.getByText('asl/tile/memory-and-data-movement/regular/TLOAD.asl', {exact: true})).toBeHidden(); + const canonicalOrder = await page.locator('li[id^="ndf-"]').evaluateAll((items) => items.map((item) => item.id)); + await memoryClause.getByRole('button', {name: 'Move down'}).click(); + const movedOrder = await page.locator('li[id^="ndf-"]').evaluateAll((items) => items.map((item) => item.id)); + expect(movedOrder).toEqual([...canonicalOrder].reverse()); + await page.getByRole('button', {name: 'Restore default order'}).click(); + await expect.poll(() => page.locator('li[id^="ndf-"]').evaluateAll((items) => items.map((item) => item.id))).toEqual(canonicalOrder); + await memoryClause.getByRole('button', {name: 'Move down'}).click(); + await page.reload(); + await expect.poll(() => page.locator('li[id^="ndf-"]').evaluateAll((items) => items.map((item) => item.id))).toEqual(canonicalOrder); + const provenance = page.locator('#ndf-pto-tload-memory-001').getByText('Sources and references', {exact: true}); + await provenance.click(); + await expect(page.locator('#ndf-pto-tload-memory-001').getByText('PTO-TLOAD-MEMORY-001', {exact: true})).toBeVisible(); + await expect(page.locator('#ndf-pto-tload-memory-001').getByRole('link', {name: /Open exact canonical source/})).toHaveAttribute('href', /#L\d+-L\d+$/); + await expect(page.getByText(/\d+ matching entries/)).toBeVisible(); - await expect(page.getByText(/Commit-scoped evidence/)).toBeVisible(); + const commitEvidenceGroup = page.getByText('Commit-scoped evidence', {exact: true}); + await expect(commitEvidenceGroup).toBeVisible(); + await commitEvidenceGroup.click(); await expect( - page.getByText('PTO-EVIDENCE-ARCHITECTURE-READINESS', {exact: true}), + page.getByText('PTO-EVIDENCE-ARCHITECTURE-READINESS', {exact: true}).filter({visible: true}).first(), ).toBeVisible(); const evidenceSearch = page.getByRole('searchbox', { name: 'Search evidence by identity or path', }); await evidenceSearch.fill('stride'); + const executableEvidenceGroup = page.getByText('Executable evidence', {exact: true}); + await executableEvidenceGroup.click(); await expect(page.getByText(/matching entr/)).not.toHaveText('24 matching entries'); - await expect( - page.getByText('PTO-AVS-BLOCK-TLOAD-STRIDE-001', {exact: true}), - ).toBeVisible(); - const strideIdentity = page.getByText('PTO-AVS-BLOCK-TLOAD-STRIDE-001', {exact: true}); - const strideEntry = strideIdentity.locator('xpath=ancestor::details[1]'); - await strideIdentity.click(); + const strideEntry = page.locator('#avs-pto-avs-block-tload-stride-001').locator('details').first(); + await expect(strideEntry.getByLabel(/AVS identity PTO-AVS-BLOCK-TLOAD-STRIDE-001/)).toBeVisible(); + await expect(strideEntry.getByText('BSTART.TLOAD', {exact: true})).toBeVisible(); + await expect(strideEntry.getByText('EXECUTION', {exact: true})).toBeVisible(); + const strideSummary = strideEntry.locator('summary').first(); + if (testInfo.project.name === 'mobile-chromium') { + await strideSummary.focus(); + await strideSummary.press('Enter'); + } else { + await strideSummary.click(); + } await strideEntry.getByText('Show exact test source', {exact: true}).click(); await expect( page.getByLabel('Exact test source for PTO-AVS-BLOCK-TLOAD-STRIDE-001'), ).toContainText('PTO-TEST'); + await evidenceSearch.fill('PTO-AVS-BLOCK-B-IOS-CAPACITY-003'); + const capacityEntry = page.locator('#avs-pto-avs-block-b-ios-capacity-003'); + await expect(capacityEntry.getByLabel(/AVS identity PTO-AVS-BLOCK-B-IOS-CAPACITY-003/).getByText('B.IOS', {exact: true})).toBeVisible(); + await expect(capacityEntry.getByText('BOUNDARY', {exact: true})).toBeVisible(); + await capacityEntry.locator('summary').first().click(); + await expect(capacityEntry.getByRole('button', {name: /Copy complete stable ID PTO-AVS-BLOCK-B-IOS-CAPACITY-003/})).toBeVisible(); + + await evidenceSearch.fill('ADR-0074'); + const decisionEntry = page.locator('#adr-adr-0074').locator('details').first(); + await expect(decisionEntry).toHaveAttribute('open', ''); + await expect(decisionEntry.getByRole('heading', {name: 'Decision record'})).toBeVisible(); + await expect(decisionEntry.getByRole('heading', {name: 'Decision', exact: true})).toBeVisible(); + await expect(decisionEntry.getByRole('heading', {name: 'Compatibility and supersession'})).toBeVisible(); + await expect(decisionEntry.getByText(/RegSrc1.*row_stride_bytes/)).toBeVisible(); + await decisionEntry.getByText('Sources and references', {exact: true}).click(); + await expect(decisionEntry.getByRole('link', {name: /Open exact decision source/})).toHaveAttribute( + 'href', + /github\.com\/PTO-ISA\/pto-spec\/blob\/[0-9a-f]{40}\/docs\/status\/decisions\/0074-tload-tstore-gm-byte-row-stride\.md/, + ); const collapseAll = page.getByRole('button', {name: 'Collapse groups'}); if (testInfo.project.name === 'mobile-chromium') { await collapseAll.focus(); @@ -198,8 +544,12 @@ test('TLOAD workbench preserves source identity and evidence interaction', async } await expect(page.locator('details[class*="group"][open]')).not.toHaveCount(0); + await page.getByText('Open optional interactive walkthrough', {exact: true}).click(); await page.getByRole('button', {name: 'Next'}).click(); await expect(page.getByText('Step 2 of 4', {exact: true})).toBeVisible(); + const sourceLedger = page.getByRole('region', {name: 'Sources and release identity'}); + await expect(sourceLedger.getByText(/Show commit, paths, hashes, version/)).toBeVisible(); + await expect(sourceLedger.getByText('asl/tile/memory-and-data-movement/regular/TLOAD.asl', {exact: true}).first()).toBeHidden(); }); test('NDF explorer keeps an indexed fallback and exact-source navigation', async ({ @@ -236,14 +586,35 @@ test('NDF explorer keeps an indexed fallback and exact-source navigation', async test('Simplified Chinese framework route is generated', async ({page}) => { await page.goto('/zh-CN/'); await expect(page.getByRole('heading', {level: 1})).toContainText( - 'PTO 架构', + '架构', ); + const chineseDrawer = page.locator('details').filter({has: page.getByText('浏览规范', {exact: true})}).first(); + if (await chineseDrawer.isVisible()) { + await chineseDrawer.getByText('浏览规范', {exact: true}).click(); + await expect(chineseDrawer.getByRole('navigation', {name: '规范导航'}).getByText('架构', {exact: true})).toBeVisible(); + } else { + await expect(page.locator('aside[aria-label="左侧规范导航"]').getByText('架构', {exact: true})).toBeVisible(); + } + await page.goto('/zh-CN/architecture/'); + await expect(page.getByRole('heading', {name: '架构', level: 1})).toBeVisible(); + await expect(page.getByRole('heading', {name: '架构心智模型'})).toBeVisible(); + await expect(page.getByRole('heading', {name: '编程与执行模型', exact: true})).toBeVisible(); + await expect(page.getByRole('heading', {name: '架构状态', exact: true})).toBeVisible(); + await expect(page.getByRole('heading', {name: '寄存器与 Tile 存储', exact: true})).toBeVisible(); + await expect(page.getByRole('heading', {name: '内存模型', exact: true})).toBeVisible(); + await page.goto('/zh-CN/'); await page.getByRole('searchbox').fill('TLOAD'); await page.getByRole('button', {name: '搜索规范'}).click(); await expect(page).toHaveURL(/\/zh-CN\/search\/\?q=TLOAD/); await expect(page.getByText(/页面框架已切换为简体中文/)).toBeVisible(); await page.getByRole('link', {name: 'TLOAD', exact: true}).click(); await expect(page).toHaveURL(`/zh-CN${tloadRoute}`); + await expect(page.getByRole('heading', {name: '汇编格式', exact: true})).toBeVisible(); + await expect(page.getByRole('heading', {name: '2. 完整 Bundle Assembly'})).toBeVisible(); + await expect(page.getByRole('heading', {name: '3. Bundle 操作数与参数语义'})).toBeVisible(); + await expect(page.getByRole('tab', {name: '普通 Local 目标'})).toBeVisible(); + await expect(page.getByRole('region', {name: '二维数据流'}).getByText(/逐 PE 的 GM 基地址/).first()).toBeVisible(); + await expect(page.getByRole('heading', {name: 'ASL 执行路径'})).toBeVisible(); }); test.describe('no JavaScript fallback', () => { @@ -252,9 +623,13 @@ test.describe('no JavaScript fallback', () => { test('released ASL and NDF remain readable', async ({page}) => { await page.goto(tloadRoute); await expect(page.getByRole('heading', {name: 'TLOAD', level: 1})).toBeVisible(); - await expect(page.getByText('Reader guide · non-normative explanation', {exact: true})).toBeVisible(); - await expect(page.getByText('// NDF-BEGIN: PTO-TLOAD-MEMORY-001')).toBeVisible(); - await expect(page.getByText(/PTO-TLOAD-CUBE-001 — contract/)).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Assembly syntax', level: 2})).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Behavior', level: 2})).toBeVisible(); + await expect(page.getByRole('heading', {name: 'ASL execution path', level: 2})).toBeVisible(); + await expect(page.getByLabel(/TLOAD\.asl lines/).filter({hasText: 'InstructionContractGMAddress_TLOAD'})).toBeVisible(); + const cubeClause = page.locator('#ndf-pto-tload-cube-001'); + await expect(cubeClause).toBeVisible(); + await expect(cubeClause.getByText(/CUBE TLOAD MUST derive persistent CELL geometry/)).toBeVisible(); }); test('catalog encoding remains readable as a native table', async ({page}, testInfo) => { @@ -270,13 +645,35 @@ test.describe('no JavaScript fallback', () => { const table = page.getByRole('table', {name: /Generated encoding fields/}); await expect(table).toBeVisible(); await expect(table.getByText('31:27', {exact: true})).toBeVisible(); - await expect(table.getByText('Fixed selector', {exact: true}).first()).toBeVisible(); + await expect(table.getByText("7'b0000101", {exact: true})).toBeVisible(); }); }); test('critical routes have no serious WCAG violations', async ({page}) => { - for (const route of ['/', tloadRoute, '/explore/ndf/?q=PTO-TLOAD-MEMORY-001']) { + test.setTimeout(60_000); + for (const route of [ + '/', + '/architecture/', + '/zh-CN/architecture/', + '/instructions/', + '/ndf/', + '/ndf/PTO-TLOAD-MEMORY-001/', + tloadRoute, + '/explore/ndf/?q=PTO-TLOAD-MEMORY-001', + '/reference/governance/adr-process/', + ]) { await page.goto(route); + if (route === '/reference/governance/adr-process/') { + const markdownTable = page.getByRole('table').first(); + await expect(markdownTable).toHaveAttribute('tabindex', '0'); + await markdownTable.focus(); + await expect(markdownTable).toBeFocused(); + } + const mobileNavigation = page.locator('summary').filter({hasText: /Browse specification|浏览规范/}).first(); + if (await mobileNavigation.isVisible()) { + await mobileNavigation.focus(); + await mobileNavigation.press('Enter'); + } const result = await new AxeBuilder({page}) .withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']) .analyze(); @@ -287,11 +684,18 @@ test('critical routes have no serious WCAG violations', async ({page}) => { } }); -test('dark and reduced-motion modes preserve layout and stop autoplay', async ({page}) => { +test('dark and reduced-motion modes preserve layout and stop autoplay', async ({page}, testInfo) => { await page.emulateMedia({colorScheme: 'dark', reducedMotion: 'reduce'}); await page.goto(tloadRoute); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); - await page.getByRole('button', {name: 'Play'}).click(); + await page.getByText('Open optional interactive walkthrough', {exact: true}).click(); + const play = page.getByRole('button', {name: 'Play'}); + if (testInfo.project.name === 'mobile-chromium') { + await play.focus(); + await play.press('Enter'); + } else { + await play.click(); + } await page.waitForTimeout(1100); await expect(page.getByText('Step 1 of 4', {exact: true})).toBeVisible(); const overflow = await page.evaluate(() => ({ diff --git a/docs/site/tsconfig.json b/docs/site/tsconfig.json index 81fec081f..c15833458 100644 --- a/docs/site/tsconfig.json +++ b/docs/site/tsconfig.json @@ -2,7 +2,8 @@ "extends": "@docusaurus/tsconfig", "compilerOptions": { "baseUrl": ".", - "jsx": "react-jsx" + "jsx": "react-jsx", + "allowImportingTsExtensions": true }, "include": [ "src/**/*", diff --git a/docs/status/decisions/0103-private-cube-vector-and-cell-rearrangement.md b/docs/status/decisions/0103-private-cube-vector-and-cell-rearrangement.md index d4adb5fa7..a0f822be7 100644 --- a/docs/status/decisions/0103-private-cube-vector-and-cell-rearrangement.md +++ b/docs/status/decisions/0103-private-cube-vector-and-cell-rearrangement.md @@ -13,7 +13,7 @@ "accepted": "2026-08-25", "rejected": null, "superseded": null, - "baseline": "23c577a468f443638e0353905f6303ed6b3ae2ed", + "baseline": "b2c6ada1c80611d5eb56aee1d40d0bb1cfa82196", "target_releases": [ "unassigned" ], diff --git a/docs/tile/layout-and-rearrangement/layout/TCONCAT.md b/docs/tile/layout-and-rearrangement/layout/TCONCAT.md index c8d304b82..96b1f2ff4 100644 --- a/docs/tile/layout-and-rearrangement/layout/TCONCAT.md +++ b/docs/tile/layout-and-rearrangement/layout/TCONCAT.md @@ -110,7 +110,8 @@ end; BSTART.SFU TCONCAT, DataType B.DATR (optional) B.DIM LB0 -B.DIM (LB1/LB2 for 2D) +B.DIM LB1 (optional) +B.DIM LB2 (optional) B.IOT BSTOP ``` @@ -188,4 +189,4 @@ end; ## Examples -- BSTART.SFU TCONCAT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; BSTOP +- BSTART.SFU TCONCAT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; BSTOP diff --git a/docs/tile/layout-and-rearrangement/layout/TEXTRACT.md b/docs/tile/layout-and-rearrangement/layout/TEXTRACT.md index 6cacddebb..1c4c02fb4 100644 --- a/docs/tile/layout-and-rearrangement/layout/TEXTRACT.md +++ b/docs/tile/layout-and-rearrangement/layout/TEXTRACT.md @@ -112,7 +112,8 @@ end; BSTART.SFU TEXTRACT, DataType B.DATR (optional) B.DIM LB0 -B.DIM (LB1/LB2 for 2D) +B.DIM LB1 (optional) +B.DIM LB2 (optional) B.IOT B.IOR BSTOP @@ -195,4 +196,4 @@ end; ## Examples -- BSTART.SFU TEXTRACT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; B.IOR; BSTOP +- BSTART.SFU TEXTRACT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; B.IOR; BSTOP diff --git a/docs/tile/layout-and-rearrangement/layout/TINSERT.md b/docs/tile/layout-and-rearrangement/layout/TINSERT.md index 7d426a94a..f61d0de9c 100644 --- a/docs/tile/layout-and-rearrangement/layout/TINSERT.md +++ b/docs/tile/layout-and-rearrangement/layout/TINSERT.md @@ -108,7 +108,8 @@ end; BSTART.SFU TINSERT, DataType B.DATR (optional) B.DIM LB0 -B.DIM (LB1/LB2 for 2D) +B.DIM LB1 (optional) +B.DIM LB2 (optional) B.IOT B.IOR BSTOP @@ -200,4 +201,4 @@ end; ## Examples -- BSTART.SFU TINSERT, DataType; B.DATR (optional); B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; B.IOR; BSTOP +- BSTART.SFU TINSERT, DataType; B.DATR (optional); B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; B.IOR; BSTOP diff --git a/docs/tile/layout-and-rearrangement/layout/TMOV.md b/docs/tile/layout-and-rearrangement/layout/TMOV.md index 91b2a72f1..62a53e58b 100644 --- a/docs/tile/layout-and-rearrangement/layout/TMOV.md +++ b/docs/tile/layout-and-rearrangement/layout/TMOV.md @@ -104,7 +104,8 @@ end; ```asm BSTART.TLSU TMOV, DataType B.DIM LB0 -B.DIM (LB1/LB2 for 2D) +B.DIM LB1 (optional) +B.DIM LB2 (optional) B.IOT BSTOP ``` @@ -168,4 +169,4 @@ end; ## Examples -- BSTART.TLSU TMOV, DataType; B.DIM LB0; B.DIM (LB1/LB2 for 2D); B.IOT; BSTOP +- BSTART.TLSU TMOV, DataType; B.DIM LB0; B.DIM LB1 (optional); B.DIM LB2 (optional); B.IOT; BSTOP diff --git a/scripts/check-site-artifact b/scripts/check-site-artifact index 67eff03f6..6a5369e1e 100755 --- a/scripts/check-site-artifact +++ b/scripts/check-site-artifact @@ -345,10 +345,127 @@ def validate_unit_routes() -> tuple[int, str, int]: asset = require_file(f"evidence/test-sources/{test['sha256']}.asl") if file_sha256(asset) != test["sha256"]: raise ValueError(f"lazy test source hash mismatch for {test['id']}") + fragments = sorted((OUTPUT / "evidence/asl-fragments").glob("*.asl")) + if not fragments: + raise ValueError("site artifact has no source-aligned shared ASL fragments") + for fragment in fragments: + if fragment.stem != file_sha256(fragment): + raise ValueError(f"shared ASL fragment digest mismatch: {fragment.name}") return len(units), largest_route, largest_html_bytes +def validate_instruction_coverage() -> int: + traceability = json.loads( + (ROOT / "spec/evidence/release-traceability-readiness.json").read_text( + encoding="utf-8" + ) + ) + instructions = sorted( + (unit for unit in traceability["units"] if unit["mnemonic"] is not None), + key=lambda unit: unit["id"], + ) + expected_ids = [unit["id"] for unit in instructions] + expected_surface_totals = { + surface: sum(unit["surface"] == surface for unit in instructions) + for surface in ("scalar", "block", "tile") + } + required_fields = { + "id", "mnemonic", "surface", "route", "layout_contract", "source", + "source_sha256", "encoding", "syntax", "assembler_symbols", + "decode_source", "operation_source", "behavior", "bundle_source", + "constraints_faults", "state_reads_writes_result", "bundle_status", + "bundle_lines", "bundle_rules", + "decisions", "ndf_owners", "evidence", "source_records", "examples", + "locale", "content_locale", "locale_status", "specialized_projection", + "explained_gaps", "unexplained_gaps", + } + for locale, relative in ( + ("en", "pto-instruction-coverage.json"), + ("zh-CN", "zh-CN/pto-instruction-coverage.json"), + ): + matrix = json.loads(require_file(relative).read_text(encoding="utf-8")) + if matrix.get("schema") != "pto.site-instruction-coverage.v1": + raise ValueError(f"{relative} has an unknown schema") + if matrix.get("locale") != locale or matrix.get("source_commit") != COMMIT: + raise ValueError(f"{relative} has stale locale or commit identity") + if matrix.get("layout_contract") != "UnitWorkbenchView.arm-style.v1": + raise ValueError(f"{relative} has the wrong page-layout contract") + if matrix.get("instruction_count") != len(instructions): + raise ValueError(f"{relative} does not cover every released instruction") + if matrix.get("surface_totals") != expected_surface_totals: + raise ValueError(f"{relative} has stale instruction surface totals") + if matrix.get("unexplained_omissions") != 0: + raise ValueError(f"{relative} retains unexplained omissions") + entries = matrix.get("entries") + if not isinstance(entries, list) or [entry.get("id") for entry in entries] != expected_ids: + raise ValueError(f"{relative} is not the canonical instruction inventory") + for unit, entry in zip(instructions, entries, strict=True): + if set(entry) != required_fields: + raise ValueError(f"{relative} {unit['id']} has a malformed coverage row") + expected_route = canonical_unit_route(unit, locale) + if ( + entry["mnemonic"] != unit["mnemonic"] or + entry["surface"] != unit["surface"] or + entry["route"] != expected_route or + entry["source"] != unit["source"] or + entry["source_sha256"] != traceability["sources"][unit["source"]] or + entry["layout_contract"] != "UnitWorkbenchView.arm-style.v1" or + entry["locale"] != locale or + entry["unexplained_gaps"] != [] + ): + raise ValueError(f"{relative} {unit['id']} is stale") + if entry["syntax"] != "complete" or entry["assembler_symbols"] != "complete": + raise ValueError(f"{relative} {unit['id']} lacks syntax or symbol coverage") + if entry["decode_source"] not in {"complete", "source-binding", "missing"}: + raise ValueError(f"{relative} {unit['id']} has invalid decode status") + if entry["operation_source"] not in {"complete", "source-binding", "missing"}: + raise ValueError(f"{relative} {unit['id']} has invalid operation status") + if (entry["decode_source"] != "complete" or entry["operation_source"] != "complete") and not entry["explained_gaps"]: + raise ValueError(f"{relative} {unit['id']} has an unexplained source gap") + if unit["surface"] == "tile" and ( + entry["bundle_source"] != "owner-metadata" or entry["bundle_lines"] < 1 + ): + raise ValueError(f"{relative} {unit['id']} lacks owner-backed bundle coverage") + if unit["surface"] == "tile" and entry["bundle_status"] not in {"complete", "source-gap"}: + raise ValueError(f"{relative} {unit['id']} has invalid bundle status") + if entry["bundle_status"] == "source-gap" and not entry["explained_gaps"]: + raise ValueError(f"{relative} {unit['id']} has an unexplained bundle gap") + page_relative = f"{expected_route.lstrip('/')}index.html" + require_text( + page_relative, + ( + "Assembly syntax", + "Assembler symbols", + "Behavior", + "NDF clauses", + "Evidence index", + "Sources and release identity", + ) if locale == "en" else ( + "汇编格式", + "汇编符号", + "行为", + "NDF 条款", + "Evidence index", + "来源与发布信息", + ), + ) + if unit["surface"] == "tile": + require_text( + page_relative, + ( + "High Level Assembly", + "Complete Bundle Assembly", + "Show High Level Assembly operands mapped to the owning ASL", + ) if locale == "en" else ( + "高层汇编", + "完整 Bundle Assembly", + "显示高层汇编 operand 到 owning ASL 的映射", + ), + ) + return len(instructions) + + def main() -> int: try: architecture_version, publication_version, expected_nodes, expected_edges = ( @@ -374,9 +491,41 @@ def main() -> int: "asl/tile/memory-and-data-movement/regular/TLOAD.asl", "PTO-TLOAD-MEMORY-001", "PTO-TLOAD-CUBE-001", - "Non-normative demonstration", + "TLOAD state-transition walkthrough", + ), + ) + require_text( + "instructions/tile/memory-and-data-movement/regular/TSTORE/index.html", + ("Local CUBE", "M162ND", "PTO-TSTORE-CUBE-001"), + ) + require_text( + "instructions/tile/matrix-and-matrix-vector/matrix-matrix/TMATMUL/index.html", + ( + "TMATMUL <LB0:M, LB1:N, LB2:K, DataTypeA, DataTypeB> " + "SrcTile0, SrcTile1 -> DstTile", + "Show High Level Assembly operands mapped to the owning ASL", ), ) + require_text( + "instructions/index.html", + ("Instruction index", "PTO-TILE-TLOAD", "PTO-SCALAR-ADD"), + ) + require_text( + "zh-CN/instructions/index.html", + ("指令索引", "PTO-TILE-TLOAD", "PTO-SCALAR-ADD"), + ) + require_text( + "ndf/index.html", + ("NDF index", "PTO-TLOAD-MEMORY-001"), + ) + require_text( + "ndf/PTO-TLOAD-MEMORY-001/index.html", + ("PTO-TLOAD-MEMORY-001", "byte row stride", "PTO-TILE-TLOAD"), + ) + require_text( + "zh-CN/ndf/PTO-TLOAD-MEMORY-001/index.html", + ("PTO-TLOAD-MEMORY-001", "PTO-TILE-TLOAD"), + ) require_text( "search/index.html", ("Specification search", str(expected_nodes)), @@ -391,7 +540,27 @@ def main() -> int: ) require_text( "zh-CN/index.html", - ("PTO 架构", "先 Scalar,再 Block,最后 Tile"), + ("架构", "先 Scalar,再 Block,最后 Tile"), + ) + require_text( + "architecture/index.html", + ( + "Architecture mental model", + "Programming and execution model", + "PTO-ARCH-PROGRAMMING-MODEL-EXECUTION-CONTEXT", + "PTO-ARCH-MEMORY-MODEL-ORDERING", + "Current owner-declared boundary", + ), + ) + require_text( + "zh-CN/architecture/index.html", + ( + "架构心智模型", + "编程与执行模型", + "PTO-ARCH-PROGRAMMING-MODEL-EXECUTION-CONTEXT", + "PTO-ARCH-MEMORY-MODEL-ORDERING", + "当前 owner 声明的边界", + ), ) expected_index_pages = math.ceil(expected_nodes / 250) static_index_pages = sorted( @@ -420,6 +589,14 @@ def main() -> int: raise ValueError( f"legacy redirect {source} does not target {destination}" ) + require_text( + "reference/scalar/alu/ADD/index.html", + ("/instructions/scalar/alu/ADD/",), + ) + require_text( + "zh-CN/reference/arch/overview/architecture/index.html", + ("/zh-CN/units/PTO-ARCH-OVERVIEW-ARCHITECTURE/",), + ) require_file(".nojekyll") publication_path = require_file("pto-site-publication.json") publication = json.loads(publication_path.read_text(encoding="utf-8")) @@ -499,6 +676,7 @@ def main() -> int: main_javascript = direct_javascript(index_html) unit_count, largest_unit_route, largest_unit_html = validate_unit_routes() + instruction_count = validate_instruction_coverage() compressed_size = sum( len(gzip.compress(path.read_bytes(), compresslevel=9)) for path in main_javascript @@ -535,6 +713,7 @@ def main() -> int: f"{len(lazy_plotly)} lazy Plotly chunks, " f"{len(lazy_wavedrom)} lazy WaveDrom chunks, " f"{unit_count} bilingual unit routes, " + f"{instruction_count} bilingual instruction coverage rows, " f"largest unit {largest_unit_route} at {largest_unit_html} bytes, " f"{expected_nodes} graph nodes, {expected_edges} graph edges" ) diff --git a/scripts/check-site-lighthouse b/scripts/check-site-lighthouse index 6c24e184e..41ab49403 100755 --- a/scripts/check-site-lighthouse +++ b/scripts/check-site-lighthouse @@ -138,6 +138,18 @@ def main() -> int: try: wait_for_server(server) enforce("home desktop", run_lighthouse("/", "home-desktop", desktop=True)) + enforce( + "Architecture desktop", + run_lighthouse("/architecture/", "architecture-desktop", desktop=True), + ) + enforce( + "Instruction index desktop", + run_lighthouse("/instructions/", "instruction-index-desktop", desktop=True), + ) + enforce( + "NDF index desktop", + run_lighthouse("/ndf/", "ndf-index-desktop", desktop=True), + ) enforce( "TLOAD desktop", run_lighthouse( diff --git a/spec/evidence/adr-index.json b/spec/evidence/adr-index.json index e809b5001..61ab2110a 100644 --- a/spec/evidence/adr-index.json +++ b/spec/evidence/adr-index.json @@ -7990,7 +7990,7 @@ "authors": [ "Codex" ], - "baseline": "23c577a468f443638e0353905f6303ed6b3ae2ed", + "baseline": "b2c6ada1c80611d5eb56aee1d40d0bb1cfa82196", "created": "2026-08-25", "id": "ADR-0103", "implementation_issue": "https://github.com/PTO-ISA/pto-spec/issues/151", diff --git a/spec/evidence/architecture-readiness.json b/spec/evidence/architecture-readiness.json index ff0a870de..15b2aa6cc 100644 --- a/spec/evidence/architecture-readiness.json +++ b/spec/evidence/architecture-readiness.json @@ -20392,7 +20392,7 @@ "sources": { "adr_records": { "count": 101, - "sha256": "04ca4ac91e17f4563af44f73ef2a00a729902e4cada268ee57d5081fab1e535e" + "sha256": "75fceb1ff699249545875e68baedd983ee5c58e30dd1edf6bd1b863c51ba2337" }, "instruction_contract": { "path": "spec/evidence/instruction-contract-closure.json", @@ -20400,7 +20400,7 @@ }, "release_traceability": { "path": "spec/evidence/release-traceability-readiness.json", - "sha256": "aae6701668a444e8a4f15a1edec3d9a5892bd15962ec4d461f4551cfee068d51", + "sha256": "fe9e5e514c11ecdd8029b94852785ac670137f093736d943952523a2c70281a9", "test_count": 3707, "unit_count": 850 } diff --git a/spec/evidence/mnemonic-descriptions/coverage-summary.json b/spec/evidence/mnemonic-descriptions/coverage-summary.json index b602bb67f..fc0c4da9b 100644 --- a/spec/evidence/mnemonic-descriptions/coverage-summary.json +++ b/spec/evidence/mnemonic-descriptions/coverage-summary.json @@ -1 +1 @@ -{"archive_pages_dispositioned":697,"archive_pages_total":697,"archive_sha256":"46fc43641fe1394f27f4961734940355e83909df08e4e4a489f02ff1e4a28aa5","atomic_claims":8590,"baseline_commit":"564ac2d8d868c441a8dc5e5f4d44efa7533a911d","current_mnemonics":634,"reader_guides_complete":702,"retired_units_retained":6,"review_batches":{"accepted":16,"batches":16},"schema_version":1,"statuses":{"complete":702,"pending":4},"surfaces":{"arch":72,"block":61,"scalar":466,"tile":107},"target_pages":706,"traceability_sha256":"aae6701668a444e8a4f15a1edec3d9a5892bd15962ec4d461f4551cfee068d51","unresolved_required_definition_gaps":0,"zh_CN":{"review_batches":{"accepted":16,"batches":16},"statuses":{"complete":702,"pending":4}}} +{"archive_pages_dispositioned":697,"archive_pages_total":697,"archive_sha256":"46fc43641fe1394f27f4961734940355e83909df08e4e4a489f02ff1e4a28aa5","atomic_claims":8590,"baseline_commit":"564ac2d8d868c441a8dc5e5f4d44efa7533a911d","current_mnemonics":634,"reader_guides_complete":702,"retired_units_retained":6,"review_batches":{"accepted":16,"batches":16},"schema_version":1,"statuses":{"complete":702,"pending":4},"surfaces":{"arch":72,"block":61,"scalar":466,"tile":107},"target_pages":706,"traceability_sha256":"fe9e5e514c11ecdd8029b94852785ac670137f093736d943952523a2c70281a9","unresolved_required_definition_gaps":0,"zh_CN":{"review_batches":{"accepted":16,"batches":16},"statuses":{"complete":702,"pending":4}}} diff --git a/spec/evidence/release-gate-readiness.json b/spec/evidence/release-gate-readiness.json index b1b6ae21d..b1efb06e7 100644 --- a/spec/evidence/release-gate-readiness.json +++ b/spec/evidence/release-gate-readiness.json @@ -155,7 +155,7 @@ "scripts/print-asl-test-matrix": "1e4b29cb97ba7524e252bb506cf0dd1b8f3d4e3d1108e345d6adf85540937a3d", "scripts/project_asl_catalogs.py": "749e10b486c08e32a687364715d0f6dd640edcb91e1862d56a99a66726e7a43f", "scripts/release_event.py": "d66b2d86f6c369c2563358cfa1fe97e227863b3017c7cefcdadfeffea1810899", - "scripts/release_selection.py": "dee6d6bae70b1eb9d3b0fdcdfc07c72b8605cffdd38eca7aabd56077c1f95e71", + "scripts/release_selection.py": "ffc6a5c0df9b3cd6ed680826b77eaad8f7bb7dec67c53139372a35660a5a3e21", "scripts/release_workflow.py": "51b92256b550e41933bc0f89542ee3f70e2a1540f4a03a5eb50864029b54adeb", "scripts/report-asl-page-results": "2dde0acdd94de060dc56c409e5d232f2252a2fa605512fe4461c360ffdda3cae", "scripts/run-asl-page": "64e7e3a7eaa7db6d264665325fcdf42b1b29cc7c4d7b7e1fd9fde30655c19e7f", @@ -164,8 +164,8 @@ "scripts/setup-aslref": "85f1a33594bc74f1f8f53cf0234f39e06c4228e78b4e1dc85dece7110f10368b", "scripts/tile_taxonomy.py": "49385a4413b15eed33e87c20df0e2fb29e9b8171c68167dbea57cc813eb1f59d", "scripts/workflow_contract.py": "409df4d48033f9b0801b1452f74a9924aad0cfece9ae636f65855fd3c20b4bf4", - "spec/evidence/architecture-readiness.json": "7d955aa010db5e11f22415b0e655263fb951236b64297ba151cf8a728f4a6351", - "spec/evidence/release-traceability-readiness.json": "aae6701668a444e8a4f15a1edec3d9a5892bd15962ec4d461f4551cfee068d51", + "spec/evidence/architecture-readiness.json": "fa88cfcc2979b5bf36d1a96ae1eba2356d32fded1c5ddb871a0b170018ee8a49", + "spec/evidence/release-traceability-readiness.json": "fe9e5e514c11ecdd8029b94852785ac670137f093736d943952523a2c70281a9", "spec/release-inputs.json": "8d5135573f3aa62461efa8d9dddf184ce119678922337d6da50daae17b46c3dc", "spec/release-selection.json": "6dbf7964257044dab5e0246932f0d24727a89e3015f315b28ac7c4a8a8d3a0c6", "spec/schemas/pto-release-selection.schema.json": "421a5274f076738ea4b185b27fd43955400164c145930afb88c4a7f8a5f18f81", diff --git a/spec/evidence/release-traceability-readiness.json b/spec/evidence/release-traceability-readiness.json index 15140c2bd..362abd42b 100644 --- a/spec/evidence/release-traceability-readiness.json +++ b/spec/evidence/release-traceability-readiness.json @@ -15612,7 +15612,9 @@ "owners": [ "asl/tile/layout-and-rearrangement/layout/TPACK.asl" ], - "readiness_subjects": [], + "readiness_subjects": [ + "ADR-0103" + ], "tests": [ "PTO-AVS-TILE-TPACK-FIELDS-001" ] @@ -15623,7 +15625,9 @@ "owners": [ "asl/tile/layout-and-rearrangement/layout/TPERMUTE.asl" ], - "readiness_subjects": [], + "readiness_subjects": [ + "ADR-0103" + ], "tests": [ "PTO-AVS-TILE-TPERMUTE-BYTE-TABLE-001" ] @@ -16111,7 +16115,9 @@ "owners": [ "asl/tile/layout-and-rearrangement/layout/TSHUF.asl" ], - "readiness_subjects": [], + "readiness_subjects": [ + "ADR-0103" + ], "tests": [ "PTO-AVS-TILE-TSHUF-BFLY-001" ] @@ -16239,7 +16245,9 @@ "owners": [ "asl/tile/layout-and-rearrangement/layout/TUNPACK.asl" ], - "readiness_subjects": [], + "readiness_subjects": [ + "ADR-0103" + ], "tests": [ "PTO-AVS-TILE-TUNPACK-FIELD-001" ] @@ -17054,11 +17062,11 @@ "asl/tile/irregular-and-complex/layout/TSCATTER.asl": "4826ef15b0fd8f3d5baa19c92e5512fa061d5dabfdd094faa264b6385793104a", "asl/tile/irregular-and-complex/sorting/TMRGSORT.asl": "bab8b53c32c4f4a56c7a95919419a3309b0aefa33c9381669caf06f440e0e95a", "asl/tile/irregular-and-complex/sorting/TSORT.asl": "e6912ac54151cc977f8545b5ab33b892e9a62b291b7a0509a57b9c71678a76a6", - "asl/tile/layout-and-rearrangement/layout/TCONCAT.asl": "723aafd1f859eb06c9fbaa3fe61eaeca300754eb305f32752824dcde4d88eb0c", - "asl/tile/layout-and-rearrangement/layout/TEXTRACT.asl": "0209901a5cb12070acd90d80c5d135b9e1911741358e83e167553be6bc45251e", + "asl/tile/layout-and-rearrangement/layout/TCONCAT.asl": "6dab4943fa82ac8c94aa02797e1c4ff0dc483eade778854d22a4a8f8345f1582", + "asl/tile/layout-and-rearrangement/layout/TEXTRACT.asl": "8de8eb4a5a6d206783073c07dc88a4f02456c2491ab6ceff64d3ff7ca96812e5", "asl/tile/layout-and-rearrangement/layout/TIMG2COL.asl": "52e1c10776add9b26e0953e24d2e68f32dd2618b97752ed127ff224fb490b1a6", - "asl/tile/layout-and-rearrangement/layout/TINSERT.asl": "76938c4846c1378ac86696f43f1cc7b018d8048f9e1ca759c7b8b692304515f6", - "asl/tile/layout-and-rearrangement/layout/TMOV.asl": "a793deac4defa4b633a0b996d55f4b69c3a8448205e9e08ad37d1bba20daab5e", + "asl/tile/layout-and-rearrangement/layout/TINSERT.asl": "f1ed676c34403356355be992d9f4a3025b5dd7ae2f38859dc6f2c049830ecd50", + "asl/tile/layout-and-rearrangement/layout/TMOV.asl": "d7e55b0adf7a3ec7fdeb74b2902c222038056fddf641eb7bcc677f90e7ba8e0e", "asl/tile/layout-and-rearrangement/layout/TPACK.asl": "3744e3f592f0b88d44806845a029be372fc48a8565a8ff4702d07a3631a5a7a0", "asl/tile/layout-and-rearrangement/layout/TPERMUTE.asl": "455ba580be500adcba1b4597a270d7bb5f583eb7da95462288c5e2e60d18b19b", "asl/tile/layout-and-rearrangement/layout/TSHUF.asl": "33ff7ddba75203adb60983616329e8e294b786b70714ea622c0aee936045855c", @@ -17904,11 +17912,11 @@ "docs/tile/irregular-and-complex/layout/TSCATTER.md": "02fe19be96148e2eb6d29938dbcf392afe0a1c822d416b61480697df73af7a09", "docs/tile/irregular-and-complex/sorting/TMRGSORT.md": "c421fdda6b8211111cef97c476821b80054efac1150ad70eb5f54c9d6d4627ee", "docs/tile/irregular-and-complex/sorting/TSORT.md": "1ed4ef8ac8ee57dd40a1e2cf4a75be0af4aba9dea93d4d8160ca0790aaec80b9", - "docs/tile/layout-and-rearrangement/layout/TCONCAT.md": "632d8238a7596a5e2575eda7b0f89ebcbd39aebd8e44a4b19d70667f1febb9d1", - "docs/tile/layout-and-rearrangement/layout/TEXTRACT.md": "1931e02aeea4557c13d936b9e1fb4ee079081575aa3791cc477a333ad540d983", + "docs/tile/layout-and-rearrangement/layout/TCONCAT.md": "8e3a26700a318ff1c63345a6b9c42932c32e1de8890c16f998259b645d9e7326", + "docs/tile/layout-and-rearrangement/layout/TEXTRACT.md": "7b0713d40e714e2cfd3d5a803672386f25c7f78600a06e8fdf21ae6774e98864", "docs/tile/layout-and-rearrangement/layout/TIMG2COL.md": "187a99af407ec90e7a8c28cec5ece255f8808a867fd7119ed1b8905755e3b08e", - "docs/tile/layout-and-rearrangement/layout/TINSERT.md": "e0ee3397f3e616940a018d5b61d0e83bddede0345884b0797e615cd896d99899", - "docs/tile/layout-and-rearrangement/layout/TMOV.md": "cf641629c02f8d60215067f55b8bb247c0df33119ae6f856bf4a3eb34d469a09", + "docs/tile/layout-and-rearrangement/layout/TINSERT.md": "aae06a6a279434441d62e6ade75995e679a4c2b150f349ff00ee72306e9da188", + "docs/tile/layout-and-rearrangement/layout/TMOV.md": "98ffeae61958fb84278fbe188f70c1ffda84cfbf83da6db025ed081d4d1ec120", "docs/tile/layout-and-rearrangement/layout/TPACK.md": "d9377b324733d5dabcf4f19c96b40123f7525d61ca076bbd04ac2280a9c4e938", "docs/tile/layout-and-rearrangement/layout/TPERMUTE.md": "4bc97b9efd443a5e86c201a469c3c55f29ced5c6605edc93664caea287bdc5d6", "docs/tile/layout-and-rearrangement/layout/TSHUF.md": "1a66f2b0a0bfb27004ce0054385dba06156f64031b275d612e984bf12afd9123", @@ -18043,7 +18051,7 @@ "docs/tile/tile-scalar-and-immediate/logical/TSHLS.md": "5d8ea9e618f7edf596cbfeec6480dbfe99868eed869d5a34781324a9426a376c", "docs/tile/tile-scalar-and-immediate/logical/TSHRS.md": "53d0d493b2de2fabe000aa93cee93df34b31e390b3fec260a0d59bdb5a8525c8", "docs/tile/tile-scalar-and-immediate/logical/TXORS.md": "53d4f5911cc544fe3f97bfa7336659469086ef9075ea4a8c4929a7de1ed1a874", - "spec/evidence/adr-index.json": "41e484180c41760e06bd5cb95f056fc7e60ff3c45fb5b91a0d1eea429ad8b608", + "spec/evidence/adr-index.json": "bbba08f1daee7080b5922f0e47da406542c6b5851ebf42d5350f3c29eb3ac2eb", "spec/evidence/instruction-contract-closure.json": "dd272cb06bd5c85c51b52f81ee705133a698bd684cdede8ea87b57c04d901ba3", "tests/asl/arch/data-types/fault/arch-fault-complete-trap-envelope-001.asl": "7b876a89dc7673cceb61ed326f196fd959295c0173c58ca18ca96b7085b9adcb", "tests/asl/arch/data-types/fault/arch-static-fault-contract-001.asl": "09298fe7a9f3232db427f7d0f0b8a64d3cf22fb5374675f2e1a72145d8ef89ee", @@ -21429,16 +21437,16 @@ "tests/asl/tile/irregular-and-complex/sorting/TSORT/tile-exec-tsort-groups-001.asl": "b015d8eb7dcb05c45dab40f84f88b8e70499a25bd914f87e61cd7d98e61345f8", "tests/asl/tile/irregular-and-complex/sorting/TSORT/tile-static-tsort-contract-001.asl": "ef53888a540b9a8f7683e857b48cfd5b1242d2cb90915fb4270a459ed376ae06", "tests/asl/tile/layout-and-rearrangement/layout/TCONCAT/tile-exec-tconcat-columns-001.asl": "bf32bb345b3fc751fd63e6599a1d9ec97516c2310859af4f0ee7aae1f280e7db", - "tests/asl/tile/layout-and-rearrangement/layout/TCONCAT/tile-static-tconcat-contract-001.asl": "617bd0a0eea7a1cdddd64a7a6521fad3341420526d1dcbfd1cafb5d64d8967aa", + "tests/asl/tile/layout-and-rearrangement/layout/TCONCAT/tile-static-tconcat-contract-001.asl": "3fa2d6dc767417446439edd9e0c76acf63949cdad38203042e8f85958aa05827", "tests/asl/tile/layout-and-rearrangement/layout/TEXTRACT/tile-exec-textract-window-001.asl": "78483b95534d505b740a5e36e6606d2c14e934cf7ba56c59818aa6575147dfa1", - "tests/asl/tile/layout-and-rearrangement/layout/TEXTRACT/tile-static-textract-contract-001.asl": "d4d01ea3cd9f6b5741a296fdb7a2ace3bd83ad1a73a62b2be9811965a4d2efb5", + "tests/asl/tile/layout-and-rearrangement/layout/TEXTRACT/tile-static-textract-contract-001.asl": "0863c836aebba47d321b3dc8a593cf2283eecc4334f46ea6e14b925272296867", "tests/asl/tile/layout-and-rearrangement/layout/TIMG2COL/tile-exec-timg2col-map-001.asl": "f8cce242b5faf20b69598a6981936c1ce879fc2e3172493531d0e53969472877", "tests/asl/tile/layout-and-rearrangement/layout/TIMG2COL/tile-exec-timg2col-pad-001.asl": "6a8ddab45ad9f2fc1eb1bbc68d1881bab9dcf7eb4cc87b7045360e8aacdd4f8b", "tests/asl/tile/layout-and-rearrangement/layout/TIMG2COL/tile-static-timg2col-contract-001.asl": "08b391603dac09425db768fc17956a6842a437dfcfd3d31692916fb92fb45348", "tests/asl/tile/layout-and-rearrangement/layout/TINSERT/tile-exec-tinsert-old-001.asl": "45f40be2b243cb4e0c08c82628c728b75a8d8a86b85c25c0de122a130e6418fc", - "tests/asl/tile/layout-and-rearrangement/layout/TINSERT/tile-static-tinsert-contract-001.asl": "3f4ebb4bc6755ad03c23ba8ec736b2b55d7d451d382ae76f2bf8b4d2d56941c2", + "tests/asl/tile/layout-and-rearrangement/layout/TINSERT/tile-static-tinsert-contract-001.asl": "37817c85342bcb9c9a1a816a3834f7d11243b1bc2d3e3581dcdd063276c60e40", "tests/asl/tile/layout-and-rearrangement/layout/TMOV/tile-exec-tmov-defined-001.asl": "a90d4c20660b02903054d8046c5bc8390063b768c51e7984961209ebf1eb7ab6", - "tests/asl/tile/layout-and-rearrangement/layout/TMOV/tile-static-tmov-contract-001.asl": "56f7e3afb3b391b0edcc72da40c2afc58802998fd303e3a69bb4a49d3598a83d", + "tests/asl/tile/layout-and-rearrangement/layout/TMOV/tile-static-tmov-contract-001.asl": "7c036f2b85bfafbd92357f2a14a6ae109ad16988ffa0b0e6e17c3069ab667ce5", "tests/asl/tile/layout-and-rearrangement/layout/TPACK/tile-exec-tpack-fields-001.asl": "f3269ea2913bebdd8053337720a0a8da08e97661a112006f849efd8eff9c7e2f", "tests/asl/tile/layout-and-rearrangement/layout/TPACK/tile-static-tpack-contract-001.asl": "94f5ade767e412e858f01a8b835ff9a28f429817e6c9daa71f951574c5b7851c", "tests/asl/tile/layout-and-rearrangement/layout/TPERMUTE/tile-exec-tpermute-byte-table-001.asl": "310c6441f9c7354a8a040e9577437ff333506e4ca555591e970d78d628a75d9a", @@ -21494,7 +21502,7 @@ "tests/asl/tile/memory-and-data-movement/regular/TPREFETCH/tile-static-tprefetch-contract-001.asl": "cee5a825ea39937646910d5798314aee006b79acad1018217ced170b6d486ef9", "tests/asl/tile/memory-and-data-movement/regular/TSTORE/tile-exec-tstore-direct-001.asl": "738d80dfdc91fe0c130edcd8b0b9c34dd52abb4455e2342cd668eb7819ca43f1", "tests/asl/tile/memory-and-data-movement/regular/TSTORE/tile-fault-tstore-precise-001.asl": "cfe9daf86d20821d28491a365baafcf16e8edfc165fe7b9c15f8628e69548854", - "tests/asl/tile/memory-and-data-movement/regular/TSTORE/tile-static-tstore-contract-001.asl": "2c2fa127922c3c061ec2ba85ccd99058a7a19c2bc21cb9f91e43150ec59a23bb", + "tests/asl/tile/memory-and-data-movement/regular/TSTORE/tile-static-tstore-contract-001.asl": "e6abba7df6fa17602918d3f2417b6e228882964e0f7e20f0ae701c80894dc49f", "tests/asl/tile/model/capacity/local/tile-bound-local-capacity-isolation-002.asl": "2a9e3c183b3bc1b77c93178a3e423e19f82cebf3ece2c8e123c91fdf7b9d8da2", "tests/asl/tile/model/capacity/local/tile-static-local-contract-001.asl": "9ee6439efa04ac4278df60700bcf0b0a43fce401f8c5d9d6aac0a743ec88a534", "tests/asl/tile/model/capacity/shared/tile-bound-shared-capacity-isolation-002.asl": "ae99e06a4c7f63bf29a22f5487e435736ba04215bc05075d5e13eee7fa461257", @@ -55146,7 +55154,7 @@ "requirements": [ "PTO-INST-TILE-TCONCAT" ], - "sha256": "617bd0a0eea7a1cdddd64a7a6521fad3341420526d1dcbfd1cafb5d64d8967aa", + "sha256": "3fa2d6dc767417446439edd9e0c76acf63949cdad38203042e8f85958aa05827", "source": "asl/tile/layout-and-rearrangement/layout/TCONCAT.asl" }, { @@ -55694,7 +55702,7 @@ "requirements": [ "PTO-INST-TILE-TEXTRACT" ], - "sha256": "d4d01ea3cd9f6b5741a296fdb7a2ace3bd83ad1a73a62b2be9811965a4d2efb5", + "sha256": "0863c836aebba47d321b3dc8a593cf2283eecc4334f46ea6e14b925272296867", "source": "asl/tile/layout-and-rearrangement/layout/TEXTRACT.asl" }, { @@ -56145,7 +56153,7 @@ "requirements": [ "PTO-INST-TILE-TINSERT" ], - "sha256": "3f4ebb4bc6755ad03c23ba8ec736b2b55d7d451d382ae76f2bf8b4d2d56941c2", + "sha256": "37817c85342bcb9c9a1a816a3834f7d11243b1bc2d3e3581dcdd063276c60e40", "source": "asl/tile/layout-and-rearrangement/layout/TINSERT.asl" }, { @@ -56846,7 +56854,7 @@ "requirements": [ "PTO-INST-TILE-TMOV" ], - "sha256": "56f7e3afb3b391b0edcc72da40c2afc58802998fd303e3a69bb4a49d3598a83d", + "sha256": "7c036f2b85bfafbd92357f2a14a6ae109ad16988ffa0b0e6e17c3069ab667ce5", "source": "asl/tile/layout-and-rearrangement/layout/TMOV.asl" }, { @@ -58672,7 +58680,7 @@ "requirements": [ "PTO-INST-TILE-TSTORE" ], - "sha256": "2c2fa127922c3c061ec2ba85ccd99058a7a19c2bc21cb9f91e43150ec59a23bb", + "sha256": "e6abba7df6fa17602918d3f2417b6e228882964e0f7e20f0ae701c80894dc49f", "source": "asl/tile/memory-and-data-movement/regular/TSTORE.asl" }, { @@ -81781,7 +81789,9 @@ "ndf_clause": "PTO-INST-TILE-TPACK" }, "mnemonic": "TPACK", - "readiness_subjects": [], + "readiness_subjects": [ + "ADR-0103" + ], "semantic_tests": [ "PTO-AVS-TILE-TPACK-FIELDS-001" ], @@ -81805,7 +81815,9 @@ "ndf_clause": "PTO-INST-TILE-TPERMUTE" }, "mnemonic": "TPERMUTE", - "readiness_subjects": [], + "readiness_subjects": [ + "ADR-0103" + ], "semantic_tests": [ "PTO-AVS-TILE-TPERMUTE-BYTE-TABLE-001" ], @@ -81829,7 +81841,9 @@ "ndf_clause": "PTO-INST-TILE-TSHUF" }, "mnemonic": "TSHUF", - "readiness_subjects": [], + "readiness_subjects": [ + "ADR-0103" + ], "semantic_tests": [ "PTO-AVS-TILE-TSHUF-BFLY-001" ], @@ -81853,7 +81867,9 @@ "ndf_clause": "PTO-INST-TILE-TUNPACK" }, "mnemonic": "TUNPACK", - "readiness_subjects": [], + "readiness_subjects": [ + "ADR-0103" + ], "semantic_tests": [ "PTO-AVS-TILE-TUNPACK-FIELD-001" ], diff --git a/spec/release-manifest.json b/spec/release-manifest.json index fef501d1c..01056405b 100644 --- a/spec/release-manifest.json +++ b/spec/release-manifest.json @@ -2850,11 +2850,11 @@ }, { "path": "asl/tile/layout-and-rearrangement/layout/TCONCAT.asl", - "sha256": "723aafd1f859eb06c9fbaa3fe61eaeca300754eb305f32752824dcde4d88eb0c" + "sha256": "6dab4943fa82ac8c94aa02797e1c4ff0dc483eade778854d22a4a8f8345f1582" }, { "path": "asl/tile/layout-and-rearrangement/layout/TEXTRACT.asl", - "sha256": "0209901a5cb12070acd90d80c5d135b9e1911741358e83e167553be6bc45251e" + "sha256": "8de8eb4a5a6d206783073c07dc88a4f02456c2491ab6ceff64d3ff7ca96812e5" }, { "path": "asl/tile/layout-and-rearrangement/layout/TIMG2COL.asl", @@ -2862,11 +2862,11 @@ }, { "path": "asl/tile/layout-and-rearrangement/layout/TINSERT.asl", - "sha256": "76938c4846c1378ac86696f43f1cc7b018d8048f9e1ca759c7b8b692304515f6" + "sha256": "f1ed676c34403356355be992d9f4a3025b5dd7ae2f38859dc6f2c049830ecd50" }, { "path": "asl/tile/layout-and-rearrangement/layout/TMOV.asl", - "sha256": "a793deac4defa4b633a0b996d55f4b69c3a8448205e9e08ad37d1bba20daab5e" + "sha256": "d7e55b0adf7a3ec7fdeb74b2902c222038056fddf641eb7bcc677f90e7ba8e0e" }, { "path": "asl/tile/layout-and-rearrangement/layout/TPACK.asl", @@ -3434,7 +3434,7 @@ }, { "path": "spec/evidence/architecture-readiness.json", - "sha256": "7d955aa010db5e11f22415b0e655263fb951236b64297ba151cf8a728f4a6351" + "sha256": "fa88cfcc2979b5bf36d1a96ae1eba2356d32fded1c5ddb871a0b170018ee8a49" }, { "path": "spec/evidence/instruction-contract-closure.json", @@ -3442,15 +3442,15 @@ }, { "path": "spec/evidence/mnemonic-descriptions/coverage-summary.json", - "sha256": "3884637b9914cebbf2df591d92bdfabd1db0baa89e3a68b2b483b8dc53f0be05" + "sha256": "a8edf06dd0543884c76cf958543c971627d6f53f49770471c83c50c05adb66c2" }, { "path": "spec/evidence/release-gate-readiness.json", - "sha256": "343120536642b1327857d48765658c778c9b263cb410c9a64b9854cdd10685e1" + "sha256": "d8e80526d22f847974b486a750b0632754bef9f6b3aa89871dddd53d2665d008" }, { "path": "spec/evidence/release-traceability-readiness.json", - "sha256": "aae6701668a444e8a4f15a1edec3d9a5892bd15962ec4d461f4551cfee068d51" + "sha256": "fe9e5e514c11ecdd8029b94852785ac670137f093736d943952523a2c70281a9" }, { "path": "spec/hardware-conformance-profile.json", @@ -3496,7 +3496,7 @@ }, "tile_operations_total": 107 }, - "content_sha256": "401b7b8d7908f68439c2d3d2ba2b83fad9b568a8b896a6b2fa9620bab19143e6", + "content_sha256": "15921af1fe499dd00091f588c72e8df21315457b79e63c94e4e7f19331c00923", "elf_identity": { "alignment_bytes": 4, "descriptor_encoding": "UTF-8 JSON with lexicographically sorted keys and no insignificant whitespace", @@ -7088,12 +7088,12 @@ { "kind": "generated-documentation", "path": "docs/tile/layout-and-rearrangement/layout/TCONCAT.md", - "sha256": "632d8238a7596a5e2575eda7b0f89ebcbd39aebd8e44a4b19d70667f1febb9d1" + "sha256": "8e3a26700a318ff1c63345a6b9c42932c32e1de8890c16f998259b645d9e7326" }, { "kind": "generated-documentation", "path": "docs/tile/layout-and-rearrangement/layout/TEXTRACT.md", - "sha256": "1931e02aeea4557c13d936b9e1fb4ee079081575aa3791cc477a333ad540d983" + "sha256": "7b0713d40e714e2cfd3d5a803672386f25c7f78600a06e8fdf21ae6774e98864" }, { "kind": "generated-documentation", @@ -7103,12 +7103,12 @@ { "kind": "generated-documentation", "path": "docs/tile/layout-and-rearrangement/layout/TINSERT.md", - "sha256": "e0ee3397f3e616940a018d5b61d0e83bddede0345884b0797e615cd896d99899" + "sha256": "aae06a6a279434441d62e6ade75995e679a4c2b150f349ff00ee72306e9da188" }, { "kind": "generated-documentation", "path": "docs/tile/layout-and-rearrangement/layout/TMOV.md", - "sha256": "cf641629c02f8d60215067f55b8bb247c0df33119ae6f856bf4a3eb34d469a09" + "sha256": "98ffeae61958fb84278fbe188f70c1ffda84cfbf83da6db025ed081d4d1ec120" }, { "kind": "generated-documentation", diff --git a/tests/asl/tile/layout-and-rearrangement/layout/TCONCAT/tile-static-tconcat-contract-001.asl b/tests/asl/tile/layout-and-rearrangement/layout/TCONCAT/tile-static-tconcat-contract-001.asl index d10250cac..697fc84db 100644 --- a/tests/asl/tile/layout-and-rearrangement/layout/TCONCAT/tile-static-tconcat-contract-001.asl +++ b/tests/asl/tile/layout-and-rearrangement/layout/TCONCAT/tile-static-tconcat-contract-001.asl @@ -3,7 +3,7 @@ func main() => integer begin // classification: layout-and-rearrangement/layout - // block: BSTART.SFU TCONCAT, DataType | B.DATR (optional) | B.DIM LB0 | B.DIM (LB1/LB2 for 2D) | B.IOT | BSTOP + // block: BSTART.SFU TCONCAT, DataType | B.DATR (optional) | B.DIM LB0 | B.DIM LB1 (optional) | B.DIM LB2 (optional) | B.IOT | BSTOP assert DecodeTileOperation(TileDecode_TEPL, '000001100000') == 68; assert TileOperationOfIndex(68) == TileOperation_TCONCAT; assert TileHandlerOfIndex(68) == TileHandler_TCONCAT; diff --git a/tests/asl/tile/layout-and-rearrangement/layout/TEXTRACT/tile-static-textract-contract-001.asl b/tests/asl/tile/layout-and-rearrangement/layout/TEXTRACT/tile-static-textract-contract-001.asl index fdf5be7ed..082fb4ad2 100644 --- a/tests/asl/tile/layout-and-rearrangement/layout/TEXTRACT/tile-static-textract-contract-001.asl +++ b/tests/asl/tile/layout-and-rearrangement/layout/TEXTRACT/tile-static-textract-contract-001.asl @@ -3,7 +3,7 @@ func main() => integer begin // classification: layout-and-rearrangement/layout - // block: BSTART.SFU TEXTRACT, DataType | B.DATR (optional) | B.DIM LB0 | B.DIM (LB1/LB2 for 2D) | B.IOT | B.IOR | BSTOP + // block: BSTART.SFU TEXTRACT, DataType | B.DATR (optional) | B.DIM LB0 | B.DIM LB1 (optional) | B.DIM LB2 (optional) | B.IOT | B.IOR | BSTOP assert DecodeTileOperation(TileDecode_TEPL, '000001100010') == 69; assert TileOperationOfIndex(69) == TileOperation_TEXTRACT; assert TileHandlerOfIndex(69) == TileHandler_TEXTRACT; diff --git a/tests/asl/tile/layout-and-rearrangement/layout/TINSERT/tile-static-tinsert-contract-001.asl b/tests/asl/tile/layout-and-rearrangement/layout/TINSERT/tile-static-tinsert-contract-001.asl index f7eb5f441..c911b8a8b 100644 --- a/tests/asl/tile/layout-and-rearrangement/layout/TINSERT/tile-static-tinsert-contract-001.asl +++ b/tests/asl/tile/layout-and-rearrangement/layout/TINSERT/tile-static-tinsert-contract-001.asl @@ -3,7 +3,7 @@ func main() => integer begin // classification: layout-and-rearrangement/layout - // block: BSTART.SFU TINSERT, DataType | B.DATR (optional) | B.DIM LB0 | B.DIM (LB1/LB2 for 2D) | B.IOT | B.IOR | BSTOP + // block: BSTART.SFU TINSERT, DataType | B.DATR (optional) | B.DIM LB0 | B.DIM LB1 (optional) | B.DIM LB2 (optional) | B.IOT | B.IOR | BSTOP assert DecodeTileOperation(TileDecode_TEPL, '000001100011') == 70; assert TileOperationOfIndex(70) == TileOperation_TINSERT; assert TileHandlerOfIndex(70) == TileHandler_TINSERT; diff --git a/tests/asl/tile/layout-and-rearrangement/layout/TMOV/tile-static-tmov-contract-001.asl b/tests/asl/tile/layout-and-rearrangement/layout/TMOV/tile-static-tmov-contract-001.asl index 4fd4e01a7..f63faa299 100644 --- a/tests/asl/tile/layout-and-rearrangement/layout/TMOV/tile-static-tmov-contract-001.asl +++ b/tests/asl/tile/layout-and-rearrangement/layout/TMOV/tile-static-tmov-contract-001.asl @@ -3,7 +3,7 @@ func main() => integer begin // classification: layout-and-rearrangement/layout - // block: BSTART.TLSU TMOV, DataType | B.DIM LB0 | B.DIM (LB1/LB2 for 2D) | B.IOT | BSTOP + // block: BSTART.TLSU TMOV, DataType | B.DIM LB0 | B.DIM LB1 (optional) | B.DIM LB2 (optional) | B.IOT | BSTOP assert DecodeTileOperation(TileDecode_TLSU, '000000000010') == 83; assert TileOperationOfIndex(83) == TileOperation_TMOV; assert TileHandlerOfIndex(83) == TileHandler_TMOV; diff --git a/tests/scripts/test_site_contract.py b/tests/scripts/test_site_contract.py index e9e84ceb9..dc01cdb87 100644 --- a/tests/scripts/test_site_contract.py +++ b/tests/scripts/test_site_contract.py @@ -44,19 +44,21 @@ def test_site_configuration_preserves_release_and_locale_contracts(self) -> None ): self.assertIn(term, config) navigation = [ - config.index("label: 'PTO Architecture'"), - config.index("label: 'Scalar'"), - config.index("label: 'Block'"), - config.index("label: 'Tile'"), - config.index("label: 'ADR / NDF'"), + config.index("label: 'Architecture'"), + config.index("label: 'Instructions'"), + config.index("label: 'NDF'"), + config.index("label: 'Decisions'"), config.index("label: 'Search'"), ] self.assertEqual(navigation, sorted(navigation)) + self.assertIn("defaultMode: 'dark'", config) + self.assertIn("'arch/**'", config) + self.assertIn("legacyReferenceRoute(unit.documentation)", config) def test_homepage_is_a_human_reading_path(self) -> None: homepage = (SITE / "src/pages/index.tsx").read_text(encoding="utf-8") for term in ( - "PTO Architecture", + "Architecture", "Scalar, then Block, then Tile", "ADR explains why. NDF points to what is current.", "surface=scalar", @@ -75,6 +77,12 @@ def test_source_backed_routes_use_page_specific_data(self) -> None: self.assertIn("modules: {graph: graphDataModule}", plugin) self.assertIn("modules: {search: searchDataModule}", plugin) self.assertIn("component: '@site/src/routes/NdfIndexPage'", plugin) + self.assertIn("component: '@site/src/routes/Architecture'", plugin) + self.assertIn("modules: {architecture: architectureDataModule}", plugin) + self.assertIn("component: '@site/src/routes/InstructionIndex'", plugin) + self.assertIn("component: '@site/src/routes/NdfCatalog'", plugin) + self.assertIn("component: '@site/src/routes/NdfDetail'", plugin) + self.assertIn("pto.site-architecture-guide.v1", plugin) self.assertIn("pto.site-unit-routes.v1", plugin) self.assertIn("pto-unit-routes.json", plugin) self.assertIn("PTO-EVIDENCE-RELEASE-TRACEABILITY", plugin) @@ -82,6 +90,20 @@ def test_source_backed_routes_use_page_specific_data(self) -> None: self.assertIn("PTO_SITE_REQUIRE_RELEASE", plugin) self.assertIn("--porcelain=v1", plugin) self.assertNotIn("setGlobalData(content)", plugin) + portal_shell = (SITE / "src/components/PortalShell.tsx").read_text( + encoding="utf-8" + ) + navigation = (SITE / "src/components/SpecNavigation.tsx").read_text( + encoding="utf-8" + ) + architecture = (SITE / "src/routes/Architecture.tsx").read_text( + encoding="utf-8" + ) + self.assertIn("Left specification navigation", portal_shell) + self.assertIn("Browse specification", portal_shell) + self.assertIn("aria-current", navigation) + self.assertIn("Architecture mental model", architecture) + self.assertIn("Sources and release identity", architecture) repository_links = ( SITE / "plugins/pto-content/remark-repository-links.ts" ).read_text(encoding="utf-8") @@ -120,15 +142,92 @@ def test_reader_guides_are_typed_locale_specific_non_normative_projections(self) "", workbench, ) - self.assertLess( - workbench.index(" None: + owner = ( + ROOT / "asl/tile/memory-and-data-movement/regular/TLOAD.asl" + ).read_text(encoding="ascii") + projection = json.loads( + (SITE / "data/instruction-projections/PTO-TILE-TLOAD.json").read_text( + encoding="utf-8" + ) + ) + plugin = (SITE / "plugins/pto-content/index.ts").read_text(encoding="utf-8") + workbench = (SITE / "src/components/UnitWorkbenchView.tsx").read_text( + encoding="utf-8" + ) + ndf = (SITE / "src/components/NdfClause.tsx").read_text(encoding="utf-8") + identity = (SITE / "src/components/SemanticIdPath.tsx").read_text( + encoding="utf-8" + ) + for term in ( + "canonicalMinCommands", + "minimumSequence", + "Ordinary Local destination", + "Local CUBE conversion", + "Ordinary Shared destination", + ): + self.assertIn(term, json.dumps(projection)) + self.assertEqual(projection["schema"], "pto.site-instruction-projection.v1") + self.assertEqual(projection["ownerSource"], "asl/tile/memory-and-data-movement/regular/TLOAD.asl") + projection_text = json.dumps(projection) + self.assertIn("complete Shared logical parent", projection_text) + self.assertIn("B.ASSEMBLE", projection_text) + self.assertNotIn("selected Shared quarters", projection_text) + self.assertNotIn("selected Shared quarter", projection_text) + self.assertNotIn("PTO-PAGE-COMPOSITION", owner) + self.assertNotIn("PTO-PAGE-SEMANTICS", owner) + for term in ( + "parseInstructionComposition", + "loadSiteInstructionProjection", + "parseSemanticExecutionProjection", + "extractAslFunctionRegion", + "ownerSourceSha256", + "fragmentSha256", + "composition command has no owning unit", + "shared source is invalid or outside ASL", + ): + self.assertIn(term, plugin) + self.assertLess(workbench.index(" None: plugin = (SITE / "plugins/pto-content/index.ts").read_text(encoding="utf-8") self.assertIn("isCommonMarkFenceClose(line, fenceToken)", plugin) @@ -157,6 +256,85 @@ def test_architecture_reader_guide_links_every_owned_ndf_clause(self) -> None: self.assertIn("reader guide cannot locate exact NDF owner", plugin) self.assertIn("href: identity.sourceUrl", plugin) + def test_tile_high_level_assembly_dimensions_are_structured_and_fail_closed(self) -> None: + plugin = (SITE / "plugins/pto-content/index.ts").read_text(encoding="utf-8") + traceability = json.loads( + (ROOT / "spec/evidence/release-traceability-readiness.json").read_text( + encoding="utf-8" + ) + ) + self.assertIn("has non-structured B.DIM projection", plugin) + self.assertIn("dimensionBindings", plugin) + self.assertNotIn("line.matchAll(/LB", plugin) + + owners = [] + ambiguous_owners = [] + structured_dimension = re.compile( + r"^B\.DIM (?:" + r"LB[0-2]\s*(?:/|=)\s*[A-Za-z][A-Za-z0-9_]*,\s*" + r"LB[0-2]\s*(?:/|=)\s*[A-Za-z][A-Za-z0-9_]*,\s*" + r"LB[0-2]\s*(?:/|=)\s*[A-Za-z][A-Za-z0-9_]*(?:\s+\([^)]*\))?" + r"|LB[0-2](?:\s*=\s*|\s+)[A-Za-z][A-Za-z0-9_]*" + r"(?:\s+or\s+cooperative\s+group_M)?(?:\s+\([^)]*\))?" + r"|LB[0-2]/LB[0-2]/LB[0-2](?:\s+\(optional\))?" + r"|LB[0-2](?:\s+\(optional\))?)$" + ) + for source_path in sorted((ROOT / "asl/tile").rglob("*.asl")): + first_line = source_path.read_text(encoding="ascii").splitlines()[0] + if not first_line.startswith("// PTO-INSTRUCTION: "): + continue + metadata = json.loads(first_line.removeprefix("// PTO-INSTRUCTION: ")) + if metadata.get("mnemonic") is not None: + owners.append(metadata["id"]) + block = list(dict.fromkeys([ + *metadata.get("block", []), + *metadata.get("contract", {}).get("block_composition", []), + ])) + invalid = [ + line for line in block + if line.startswith("B.DIM") and structured_dimension.fullmatch(line) is None + ] + if invalid: + ambiguous_owners.append((metadata["mnemonic"], invalid)) + expected_owners = { + unit["id"] for unit in traceability["units"] + if unit["surface"] == "tile" and unit["mnemonic"] is not None + } + self.assertSetEqual(set(owners), expected_owners) + self.assertEqual(ambiguous_owners, []) + for bad_form in ( + "B.DIM (LB1/LB2 for 2D)", + "B.DIM LB1/LB2 for 2D", + "B.DIM dimensions depend on shape", + "B.DIM LB0 and maybe LB1", + "B.DIM use rows and columns", + "B.DIM as required", + ): + self.assertIsNone(structured_dimension.fullmatch(bad_form), bad_form) + + def test_ndf_catalog_and_detail_cover_release_traceability(self) -> None: + plugin = (SITE / "plugins/pto-content/index.ts").read_text(encoding="utf-8") + types = (SITE / "src/types/pto.ts").read_text(encoding="utf-8") + catalog = (SITE / "src/routes/NdfCatalog.tsx").read_text(encoding="utf-8") + detail = (SITE / "src/routes/NdfDetail.tsx").read_text(encoding="utf-8") + traceability = json.loads( + (ROOT / "spec/evidence/release-traceability-readiness.json").read_text( + encoding="utf-8" + ) + ) + requirement_ids = [row["id"] for row in traceability["requirements"]] + self.assertGreater(len(requirement_ids), 0) + self.assertEqual(len(requirement_ids), len(set(requirement_ids))) + self.assertIn("...instructionContracts.keys()", plugin) + self.assertNotIn("filter((id) => !instructionContractIds.has(id))", plugin) + self.assertIn("entryKind: 'instruction-contract'", plugin) + self.assertIn("the link opens its canonical unit workbench directly", catalog) + for field in ("adrs", "tests", "evidence", "relationships", "relationshipFallbackRoute"): + self.assertIn(f" {field}:", types) + self.assertIn("Local relationship neighborhood", detail) + self.assertIn(" None: explorer = (SITE / "src/components/NdfGlobalExplorer.tsx").read_text( encoding="utf-8" @@ -174,8 +352,13 @@ def test_artifact_gate_exhaustively_checks_bilingual_unit_routes(self) -> None: checker = (ROOT / "scripts/check-site-artifact").read_text(encoding="utf-8") for term in ( "validate_unit_routes", + "validate_instruction_coverage", 'pto.site-unit-routes.v1', + 'pto.site-instruction-coverage.v1', 'zh-CN/pto-unit-routes.json', + 'zh-CN/pto-instruction-coverage.json', + 'UnitWorkbenchView.arm-style.v1', + 'unexplained_omissions', 'search index does not route', 'documentation_sha256', 'expected_reader_projection',