diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index acf45202..a229aad7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -85,21 +85,6 @@ jobs: # disable isolation to allow reading spv files run: RUST_BACKTRACE=1 MIRIFLAGS=-Zmiri-disable-isolation cargo miri nextest run --target ${{ matrix.target }} --no-fail-fast - # This allows us to have a single job we can branch protect on, rather than needing - # to update the branch protection rules when the test matrix changes - test_success: - runs-on: ubuntu-latest - needs: [test, lint, test-miri] - # Hack for buggy GitHub Actions behavior with skipped checks: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks - if: ${{ always() }} - steps: - # Another hack is to actually check the status of the dependencies or else it'll fall through - - run: | - echo "Checking statuses..." - [[ "${{ needs.test.result }}" == "success" ]] || exit 1 - [[ "${{ needs.lint.result }}" == "success" ]] || exit 1 - [[ "${{ needs.test-miri.result }}" == "success" ]] || exit 1 - lint: name: Lint runs-on: ubuntu-latest @@ -126,6 +111,32 @@ jobs: - name: grammar-parser without codegen feature run: cargo check -p rspirv2-grammar-parser + release-dry-run: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: install rust-toolchain + run: echo "TARGET=$(rustc --print host-tuple)" >> "$GITHUB_ENV" + - name: cargo fetch --locked + run: cargo fetch --locked --target $TARGET + - run: cargo publish --dry-run + + # This allows us to have a single job we can branch protect on, rather than needing + # to update the branch protection rules when the test matrix changes + test_success: + runs-on: ubuntu-latest + needs: [test, lint, test-miri, release-dry-run] + # Hack for buggy GitHub Actions behavior with skipped checks: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks + if: ${{ always() }} + steps: + # Another hack is to actually check the status of the dependencies or else it'll fall through + - run: | + echo "Checking statuses..." + [[ "${{ needs.test.result }}" == "success" ]] || exit 1 + [[ "${{ needs.lint.result }}" == "success" ]] || exit 1 + [[ "${{ needs.test-miri.result }}" == "success" ]] || exit 1 + [[ "${{ needs.release-dry-run.result }}" == "success" ]] || exit 1 + defaults: run: shell: bash diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml new file mode 100644 index 00000000..d57353bf --- /dev/null +++ b/.github/workflows/release-plz.yml @@ -0,0 +1,59 @@ +name: Release-plz + +on: + push: + branches: + - main + +jobs: + + # Release unpublished packages. + release-plz-release: + name: Release-plz release + if: ${{ github.repository_owner == 'rust-gpu' }} + runs-on: ubuntu-latest + environment: release + permissions: + contents: write + pull-requests: read + id-token: write + steps: + - &checkout + name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + persist-credentials: false + submodules: true + - &install-rust + name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + # Create a PR with the new versions and changelog, preparing the next release. + release-plz-pr: + name: Release-plz PR + if: ${{ github.repository_owner == 'rust-gpu' }} + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + steps: + - *checkout + - *install-rust + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.release-plz.toml b/.release-plz.toml new file mode 100644 index 00000000..31691529 --- /dev/null +++ b/.release-plz.toml @@ -0,0 +1,19 @@ +[changelog] +body = """ +## [{{ version }}]{%- if release_link -%}({{ release_link }}){% endif %} - {{ timestamp | date(format="%Y-%m-%d") }}\n +""" + +[workspace] +changelog_update = false +git_tag_enable = false +git_release_enable = false + +[[package]] +name = "rspirv2" +changelog_update = true +changelog_include = ["rspirv2-types", "rspirv2-grammar-parser", "rspirv2-grammar", "rspirv2-tools", "rspirv2-tools-gen"] +changelog_path = "CHANGELOG.md" +git_tag_enable = true +git_tag_name = "v{{ version }}" +git_release_enable = true +git_release_name = "v{{ version }}" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4d20814c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +- Initial release of rspirv2 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..6cffd884 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,84 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/Cargo.lock b/Cargo.lock index a3664cc7..1296d9f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,7 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "autogen" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "anyhow", "rspirv2-grammar", @@ -302,7 +302,7 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "examples" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "anyhow", "clap", @@ -639,7 +639,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rspirv2" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "bitflags", "bytemuck", @@ -651,7 +651,7 @@ dependencies = [ [[package]] name = "rspirv2-grammar" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "anyhow", "rspirv2-grammar-parser", @@ -659,7 +659,7 @@ dependencies = [ [[package]] name = "rspirv2-grammar-parser" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "anyhow", "convert_case", @@ -676,7 +676,7 @@ dependencies = [ [[package]] name = "rspirv2-tools" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "anstream", "anyhow", @@ -688,7 +688,7 @@ dependencies = [ [[package]] name = "rspirv2-tools-gen" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "anstream", "anyhow", @@ -701,7 +701,7 @@ dependencies = [ [[package]] name = "rspirv2-types" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "anstyle", "anyhow", @@ -814,7 +814,7 @@ dependencies = [ [[package]] name = "spv" -version = "0.1.0" +version = "0.1.0+sdk-1.4.341" dependencies = [ "anstream", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index d5b9d293..a266ac66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,18 +13,20 @@ members = [ resolver = "3" [workspace.package] -version = "0.1.0" -authors = ["rust-gpu developers"] +version = "0.1.0+sdk-1.4.341" edition = "2024" license = "MIT OR Apache-2.0" +repository = "https://github.com/Rust-GPU/rspirv2/" +keywords = ["spirv", "vulkan", "graphics"] +categories = ["rendering::graphics-api"] [workspace.dependencies] -rspirv2 = { path = "crates/rspirv2" } -rspirv2-types = { path = "crates/rspirv2-types" } -rspirv2-grammar = { path = "crates/grammar" } -rspirv2-grammar-parser = { path = "crates/grammar-parser" } -rspirv2-tools-gen = { path = "crates/tools-gen" } -spv = { path = "tests" } +rspirv2 = { path = "crates/rspirv2", version = "0.1.0" } +rspirv2-types = { path = "crates/rspirv2-types", version = "0.1.0" } +rspirv2-grammar = { path = "crates/grammar", version = "0.1.0" } +rspirv2-grammar-parser = { path = "crates/grammar-parser", version = "0.1.0" } +rspirv2-tools-gen = { path = "crates/tools-gen", version = "0.1.0" } +spv = { path = "tests", version = "0.1.0" } anyhow = "1.0.100" serde = { version = "1.0.228", features = ["derive"] } diff --git a/README.md b/README.md index 6b7a4e9e..42963af9 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,131 @@ # rspirv2 -A strongly typed SPIR-V representation with a builder, disassembler and support for custom instruction sets. Build primarily for [rust-gpu](https://github.com/Rust-GPU/rust-gpu/). +[![Crates.io Version](https://img.shields.io/crates/v/rspirv2)](https://crates.io/crates/rspirv2) -## Design -* strongly typed: Each instruction is their own struct with pub fields for each operand (including return word and return type, where required), to ensure each are assigned the correct type of operant at compile time. (Sort of like [`rspirv::sr::Op`](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/sr/autogen_ops.rs#L67) but you can't encode them into binary) -* Bring your own builder: The `Builder` is entirely optional and only exists for convenience. Every instruction can be turned into a `Iterator` and written to any buffer you want. -* Instruction metadata: For each instruction, you can query the metadata about expected argument types, [like in rspirv](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/grammar/autogen_table.rs#L80). This type querying system is dynamic and separate from the generated structs. Needed for disassembly and `asm!` type inference in rust-gpu. -* Custom instruction sets: You can choose which instruction set extensions you operate on, whether it's `glsl_std_450`, `opencl_std_100` or a completely custom externally defined instruction set, e.g. for rust-gpu. -* Tooling: A disassembler that doesn't fail with unknown instruction sets and can support custom extensions. +SPIR-V is the binary format for Vulkan shaders (and `OpenCL` kernels). This crate provides a strongly typed SPIR-V representation for all instructions, efficient encoded instruction storage, manipulation tools, disassembler and support for custom instruction sets. Built primarily for [rust-gpu](https://github.com/Rust-GPU/rust-gpu/). + +Created and sponsored by [Vectorware Vectorware](https://www.vectorware.com/). + +```rust +// an allocator for `IdResult`s (SSA value IDs) +let mut alloc = IdResultAlloc::new(); +// a `Vec` for instructions that stores them in SPIR-V encoded form +let mut vec = InstVec::::new(); + +// add some SPIR-V instructions +// declare u32 type +let u32 = vec.push_inst(OpTypeInt { + id_result: alloc.alloc_id(), + width: LiteralInteger::new(32), + signedness: LiteralInteger::new(0), +}); +// let a: u32 = 42; +let a = vec.push_inst(OpConstant { + id_result_type: IdResultType(u32), + id_result: alloc.alloc_id(), + value: LiteralConst::from(42u32), +}); +// let b: u32 = a + a; +let b = vec.push_inst(OpIAdd { + id_result_type: IdResultType(u32), + id_result: alloc.alloc_id(), + operand_1: IdRef(a), + operand_2: IdRef(a), +}); + +// replace `b = a + a` with `b = a * a` +let mut modified = vec + .iter() + .map(|inst| match inst { + CoreInstSet::IAdd(OpIAdd { + id_result, + operand_1, + operand_2, + .. + }) => OpIMul { + id_result_type: IdResultType(u32), + id_result, + operand_1, + operand_2, + } + .into(), + inst => inst, + }) + .collect::>(); + +// append InstVec to each other +let mut vec2 = InstVec::new(); +vec2.push_inst(OpISub { + id_result_type: IdResultType(u32), + id_result: alloc.alloc_id(), + operand_1: IdRef(a), + operand_2: IdRef(b), +}); +modified.append(&mut vec2); + +// disassembly with various settings: +// `default()` for colorful terminal output +// `simple()` to remove color and padding +// `like_spirv_tools()` and `like_rspirv()` to mimic output of other disassemblers +let disassembly = format!("{}", modified.dis(DisOptions::simple())); +expect_test::expect![[r#" + %u32 = OpTypeInt 32 0 + %u32_42 = OpConstant %u32 42 + %2 = OpIMul %u32 %u32_42 %u32_42 + %3 = OpISub %u32 %u32_42 %2 +"#]] +.assert_eq(&disassembly); +``` + +## Features +* strongly typed: Each instruction has their own struct with pub fields for each operand, to make it easy to access and modify them. The `CoreInstSet` type is a plain enum of all available instructions, which you can just match on. +* efficient storage: A `Vec` consumes a lot of memory as it needs to reserve space for the largest instruction. `InstVec` store instructions efficiently in their variable-length SPIR-V encoded form, but you can't trivially replace instructions. +* iteration and slicing: When you iterate over the `InstVec`, it'll decode instructions on the fly, so it doesn't look any different to a `Vec`. It also gives you various offsets to the instructions, so you can slice it as you'd slice a string. +* bring your own builder: You're not locked into using `InstVec` to encode instructions, you can implement `trait WordWriter` to write the binary form into your own data structure. +* custom instruction sets: We codegen all instructions from the SPIR-V grammar. This makes updating to newer versions easy, and by modifying the grammar or creating your own you can make custom instruction sets. +* tooling: A disassembler that doesn't fail with unknown instruction and supports custom instruction sets as a generic. + +### Caveats +* Assumes SPIR-V instructions can be encoded, decoded and processed independently without requiring context from any other instruction. This is true for almost all instructions, except for `OpSwitch` and `ExtInstSet`. +* `OpSwitch` has a custom implementation where anyone accessing must first define whether the labels are 32 or 64bit wide. +* Extended instruction sets (`ExtInstSet`) are not properly supported. We do of course support declaring `ExtInstSet` and calling their functions, but the API will say you're calling function id 69. You'll need to resolve that yourself, e.g. if the associated `ExtInstSet` declaration is `glsl.std.450`, it's the vector `Normalize()` function from glsl. +* No SPIR-V assembler +* Contributions welcome! ### Compared to [rspirv](https://github.com/gfx-rs/rspirv/) +* rspirv has a [structured representation](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/sr/autogen_ops.rs#L67), which you can only load from a binary, but not write back into a binary. +* [rspirv's builder is a singleton cursor](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/dr/build/autogen_norm_insts.rs#L21-L63), rust-gpu needs multiple cursors, so [working around this limitation has been awkward](https://github.com/Rust-GPU/rust-gpu/blob/8b85962188181817aaaf3aee431677e078ff2a68/crates/rustc_codegen_spirv/src/builder_spirv.rs#L399-L430). In rspirv2, you can have as many `InstVec` as you want and concat them together arbitrarily. +* Accessing operands of instructions is dynamic, so to [modify SPIR-V](https://github.com/Rust-GPU/rust-gpu/blob/c2de01373cc65372a2d438a8bf1dec1465fe450d/crates/rustc_codegen_spirv/src/linker/simple_passes.rs#L353-L367) you need to cast operands to concrete types manually. +* [rspirv's instruction encode function](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/binary/assemble.rs#L4-L16) takes an `&mut Vec` as an arg, limiting in the kinds of buffers you can write to. In rspirv2 you can write to anything that implements `WordWriter`. +* Their Module has a Vec of Instructions, and each [Instruction contains a Vec of parameters](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/dr/constructs.rs#L84-L95), with no pooling or arena, leading to a lot of small individual allocations. +* rust-gpu's custom [instructions](https://github.com/Rust-GPU/rust-gpu/blob/8b85962188181817aaaf3aee431677e078ff2a68/crates/rustc_codegen_spirv/src/custom_insts.rs) and [decorations](https://github.com/Rust-GPU/rust-gpu/blob/8b85962188181817aaaf3aee431677e078ff2a68/crates/rustc_codegen_spirv/src/custom_decorations.rs) are messy and most disassemblers refuse to disassemble unknown instructions. We need tooling that supports custom instruction sets for rust-gpu internal IRs. + +## Performance + +Testing disassembler performance on an unreasonably large 87MiB SPIR-V file: +```text +# rspirv2 +$ cargo b --bin rspirv2-dis --release +$ time ./target/release/rspirv2-dis huge.spv >/dev/null + +real 0m1.363s +user 0m1.231s +sys 0m0.122s + +# official C++ spirv tooling +$ time spirv-dis ./huge.spv >/dev/null + +real 0m7.262s +user 0m6.873s +sys 0m0.333s + +# rspirv +$ time ~/.cargo/bin/rspirv-dis ./huge.spv >/dev/null -* Hasn't seen an update in [over 2 years](https://crates.io/crates/rspirv/versions), last commit 4 months ago. Still no Vulkan 1.4 support, only in an open [PRs](https://github.com/gfx-rs/rspirv/pull/262). -* [It forces you to use their builder to append instructions](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/dr/build/autogen_norm_insts.rs#L21-L63), which is also a singleton cursor. Not having multiple cursors is causing pain [within rust-gpu](https://github.com/Rust-GPU/rust-gpu/blob/8b85962188181817aaaf3aee431677e078ff2a68/crates/rustc_codegen_spirv/src/builder_spirv.rs#L399-L430) for ages. -* If you're not using the builder, you must [construct instructions without argument type checking](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/dr/build/autogen_norm_insts.rs#L21-L63) -* [Their instruction encode](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/binary/assemble.rs#L4-L16) requires you to pass a `&mut Vec`, which can limit what kinds of buffers you can write to quite significantly. -* Every [Instruction contains a non-optional Vec](https://github.com/gfx-rs/rspirv/blob/3e8814d838a49a98084ada2d1a8677e3281c6d33/rspirv/dr/constructs.rs#L84-L95), with no pooling or arena, potentially leading to a lot of heap fragmentation. -* rust-gpu's custom [instructions](https://github.com/Rust-GPU/rust-gpu/blob/8b85962188181817aaaf3aee431677e078ff2a68/crates/rustc_codegen_spirv/src/custom_insts.rs) and [decorations](https://github.com/Rust-GPU/rust-gpu/blob/8b85962188181817aaaf3aee431677e078ff2a68/crates/rustc_codegen_spirv/src/custom_decorations.rs) are messy and `spirv-dis` fails to disassemble them. We need a new system that allows custom out-of-tree instruction sets. +# RSPIRV NUMBERS ARE NOT COMPARABLE +# rspirv's output is considerably simpler and cheaper to compute, using only ids (`%3`) +# instead of trying to resolve a human readable name (`%u32`). +real 0m4.679s +user 0m4.223s +sys 0m0.423s +``` diff --git a/crates/autogen/Cargo.toml b/crates/autogen/Cargo.toml index 8631f9fe..04f1a735 100644 --- a/crates/autogen/Cargo.toml +++ b/crates/autogen/Cargo.toml @@ -2,6 +2,10 @@ name = "autogen" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true publish = false [lints] diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index ce0d7ee0..6d93fdfc 100644 --- a/crates/examples/Cargo.toml +++ b/crates/examples/Cargo.toml @@ -2,6 +2,10 @@ name = "examples" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true publish = false [lints] diff --git a/crates/grammar-parser/Cargo.toml b/crates/grammar-parser/Cargo.toml index 1a1e840b..0ee37bf9 100644 --- a/crates/grammar-parser/Cargo.toml +++ b/crates/grammar-parser/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "rspirv2-grammar-parser" +description = "Parser for SPIR-V grammar json" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true [lints] workspace = true diff --git a/crates/grammar-parser/README.md b/crates/grammar-parser/README.md index 8596f620..59d55020 100644 --- a/crates/grammar-parser/README.md +++ b/crates/grammar-parser/README.md @@ -1,3 +1,3 @@ # spirv-grammer-parser -A parser for SPIR-V grammar. +A parser for SPIR-V grammar, with optional codegen feature to generate rspirv2 representations for various instructions and operations. Part of [rspirv2](https://crates.io/crates/rspirv2). diff --git a/crates/grammar/Cargo.toml b/crates/grammar/Cargo.toml index 04696910..311b5e6d 100644 --- a/crates/grammar/Cargo.toml +++ b/crates/grammar/Cargo.toml @@ -1,7 +1,18 @@ [package] name = "rspirv2-grammar" +description = "SPIR-V grammar distribution" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true + +exclude = [ + "headers", + "!headers/include/spirv/unified1/spirv.core.grammar.json", + "!headers/include/spirv/unified1/extinst*.json", +] [lints] workspace = true diff --git a/crates/grammar/README.md b/crates/grammar/README.md index 2555a6fe..b711b56c 100644 --- a/crates/grammar/README.md +++ b/crates/grammar/README.md @@ -1,5 +1,5 @@ # spirv-grammar -Provides the SPIR-V grammar to Rust, using `spirv-grammar-parser` to parse it. You'll find prepared paths to the SPIR-V grammar JSON files in `lib.rs`. +Provides the SPIR-V grammar to Rust, using `spirv-grammar-parser` to parse it. You'll find prepared paths to the SPIR-V grammar JSON files in `lib.rs`. Part of [rspirv2](https://crates.io/crates/rspirv2). The crate assumes it is exclusively used for codegen in build scripts (or other binaries within a dev workspace), and never distributed within an actual binary. Thus, it's just providing file paths to the grammar files, not actually `include_bytes!` them. The actual grammar files are located in `./headers` as a git submodule of the [SPIR-V Headers repo](https://github.com/KhronosGroup/SPIRV-Headers/). diff --git a/crates/grammar/src/lib.rs b/crates/grammar/src/lib.rs index ec0d027a..aefa8180 100644 --- a/crates/grammar/src/lib.rs +++ b/crates/grammar/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + use rspirv2_grammar_parser::parse::{CoreGrammar, ExtInstSetGrammar, GrammarFile}; macro_rules! folder_path { diff --git a/crates/rspirv2-types/Cargo.toml b/crates/rspirv2-types/Cargo.toml index c824d8ac..d7db8dc5 100644 --- a/crates/rspirv2-types/Cargo.toml +++ b/crates/rspirv2-types/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "rspirv2-types" +description = "SPIR-V encoding, decoding, manipulation and disassembly" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true [lints] workspace = true diff --git a/crates/rspirv2-types/README.md b/crates/rspirv2-types/README.md index 6e9fc0b8..dfd59f09 100644 --- a/crates/rspirv2-types/README.md +++ b/crates/rspirv2-types/README.md @@ -1,5 +1,5 @@ # rspirv2-types -Defines types and traits to describe SPIR-V Instructions and Operands. +Defines types and traits to describe SPIR-V Instructions and Operands. Part of [rspirv2](https://crates.io/crates/rspirv2). The crate doesn't include the core grammar or any of the extended instruction sets defined by the SPIR-V grammar json, it merely defines the types to represent them. The main crate `rspirv2` reexports this crate and includes auto generated definitions of the core grammar, with some select Vulkan-specific extended instruction sets. diff --git a/crates/rspirv2-types/src/slice.rs b/crates/rspirv2-types/src/slice.rs index e6f90745..c006c46a 100644 --- a/crates/rspirv2-types/src/slice.rs +++ b/crates/rspirv2-types/src/slice.rs @@ -20,19 +20,23 @@ pub fn decode_failed(e: DecodeError) -> ! { /// /// This `InstVec` stores instructions in their binary SPIR-V form, which is variable sized, allowing us to save a lot /// of memory and improving cache locality. To make it as convenient to use as a `Vec`, instructions are -/// automatically encoded and decoded on the fly. But to remain performant, decoding of instructions must be cheap, -/// so operands should not allocate any memory and instead should borrow slices from the underlying `Vec` whenever -/// possible. +/// automatically encoded and decoded on the fly. To remain performant, decoding of instructions should be as cheap as +/// possible. Borrowing from the Vec directly hasn't been implemented yet, so some operands like [`LiteralString`] +/// allocate a string and copy out the bytes. [`LiteralConst`] uses a `SmallVec`, so the allocation shouldn't +/// happen in pretty much all cases. /// /// However, storing instructions in a variable-sized way brings the same disadvantages as UTF-8 characters in rust /// strings. You can't arbitrarily index into an `InstSlice` like you can with a regular slice, as only indices pointing /// to the beginning of an instruction are valid. You also can't easily replace instructions in the middle of the /// stream, as changing the size of an instruction requires you to move all following instructions. Although, you can -/// remove instructions by filling them with `OpNop`. +/// remove instructions by filling them with `OpNop`, but this hasn't been implemented. /// /// # Safety /// The inner slice of words is assumed to contain valid instructions of the generic `ISA` Instruction Set. May panic if /// instructions fail to decode, but will not lead to UB, allowing [`Self::from_words_unchecked`] to be safe. +/// +/// [`LiteralString`]: crate::operand::LiteralString +/// [`LiteralConst`]: crate::operand::LiteralConst #[repr(transparent)] pub struct InstSlice { _phantom: PhantomData, diff --git a/crates/rspirv2/Cargo.toml b/crates/rspirv2/Cargo.toml index 3ef0dbf4..c6827311 100644 --- a/crates/rspirv2/Cargo.toml +++ b/crates/rspirv2/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "rspirv2" +description = "SPIR-V encoding, decoding, manipulation and disassembly" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true [lints] workspace = true diff --git a/crates/tools-gen/Cargo.toml b/crates/tools-gen/Cargo.toml index f0f1c233..5db7ffc9 100644 --- a/crates/tools-gen/Cargo.toml +++ b/crates/tools-gen/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "rspirv2-tools-gen" +description = "ISA-agnostic SPIR-V tooling" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true [lints] workspace = true diff --git a/crates/tools-gen/README.md b/crates/tools-gen/README.md index 27da010f..7c628a0e 100644 --- a/crates/tools-gen/README.md +++ b/crates/tools-gen/README.md @@ -1,5 +1,3 @@ # rspirv2-tools-gen -This crate contains **ISA-generic** implementations of various cmdline tools. - -See `rspirv2-tools` for docs, which also exposes the tools as binaries with the default SPIR-V "core" ISA. +This crate contains **ISA-generic** implementations of various cmdline tools. For actual binaries using standard SPIR-V, see [`rspirv2-tools`](https://crates.io/crates/rspirv2-tools). diff --git a/crates/tools-gen/src/lib.rs b/crates/tools-gen/src/lib.rs index 1077ca8f..cb0240e5 100644 --- a/crates/tools-gen/src/lib.rs +++ b/crates/tools-gen/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + pub mod dis; #[cfg(feature = "tracy")] diff --git a/crates/tools/Cargo.toml b/crates/tools/Cargo.toml index e90665d0..de7134b7 100644 --- a/crates/tools/Cargo.toml +++ b/crates/tools/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "rspirv2-tools" +description = "SPIR-V disassembler and other tooling" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true [lints] workspace = true diff --git a/crates/tools/README.md b/crates/tools/README.md index e75057c3..549b76c2 100644 --- a/crates/tools/README.md +++ b/crates/tools/README.md @@ -1,6 +1,6 @@ # rspirv2-tools -Similar to the C++ SPIR-V tools, provides cmdline utilities for working with SPIR-V, but written in rust with `rspirv2`. +Similar to the C++ SPIR-V tools, provides cmdline utilities for working with SPIR-V, but written in rust with [rspirv2](https://crates.io/crates/rspirv2). This crate provides the following binaries: * `rspirv2-dis` diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index 3621ef1b..bca20f0e 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -1 +1,3 @@ +#![doc = include_str!("../README.md")] + pub type ToolsISA = rspirv2::core::inst_set::CoreInstSet; diff --git a/docs/vectorware_logo.png b/docs/vectorware_logo.png new file mode 100644 index 00000000..9d6c3871 Binary files /dev/null and b/docs/vectorware_logo.png differ diff --git a/tests/Cargo.toml b/tests/Cargo.toml index f1765187..a462d05e 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -2,6 +2,10 @@ name = "spv" version.workspace = true edition.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true publish = false [lints] diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 755f54b3..e7895117 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + use expect_test::ExpectFile; use std::path::PathBuf; diff --git a/tests/tests/readme.rs b/tests/tests/readme.rs new file mode 100644 index 00000000..1a5194a6 --- /dev/null +++ b/tests/tests/readme.rs @@ -0,0 +1,79 @@ +use rspirv2::core::inst::{OpConstant, OpIAdd, OpIMul, OpISub, OpTypeInt}; +use rspirv2::core::inst_set::CoreInstSet; +use rspirv2_types::binary::IdResultAlloc; +use rspirv2_types::dis::DisOptions; +use rspirv2_types::operand::{IdRef, IdResultType, LiteralConst, LiteralInteger}; +use rspirv2_types::vec::InstVec; + +/// test for readme's contents, keep in sync! +#[test] +pub fn test_readme() { + // an allocator for `IdResult`s (SSA value IDs) + let mut alloc = IdResultAlloc::new(); + // a `Vec` for instructions that stores them in SPIR-V encoded form + let mut vec = InstVec::::new(); + + // add some SPIR-V instructions + // declare u32 type + let u32 = vec.push_inst(OpTypeInt { + id_result: alloc.alloc_id(), + width: LiteralInteger::new(32), + signedness: LiteralInteger::new(0), + }); + // let a: u32 = 42; + let a = vec.push_inst(OpConstant { + id_result_type: IdResultType(u32), + id_result: alloc.alloc_id(), + value: LiteralConst::from(42u32), + }); + // let b: u32 = a + a; + let b = vec.push_inst(OpIAdd { + id_result_type: IdResultType(u32), + id_result: alloc.alloc_id(), + operand_1: IdRef(a), + operand_2: IdRef(a), + }); + + // replace `b = a + a` with `b = a * a` + let mut modified = vec + .iter() + .map(|inst| match inst { + CoreInstSet::IAdd(OpIAdd { + id_result, + operand_1, + operand_2, + .. + }) => OpIMul { + id_result_type: IdResultType(u32), + id_result, + operand_1, + operand_2, + } + .into(), + inst => inst, + }) + .collect::>(); + + // append InstVec to each other + let mut vec2 = InstVec::new(); + vec2.push_inst(OpISub { + id_result_type: IdResultType(u32), + id_result: alloc.alloc_id(), + operand_1: IdRef(a), + operand_2: IdRef(b), + }); + modified.append(&mut vec2); + + // disassembly with various settings: + // `default()` for colorful terminal output + // `simple()` to remove color and padding, for tests + // `like_spirv_tools()` and `like_rspirv()` to mimic output of other disassemblers + let disassembly = format!("{}", modified.dis(DisOptions::simple())); + expect_test::expect![[r#" + %u32 = OpTypeInt 32 0 + %u32_42 = OpConstant %u32 42 + %2 = OpIMul %u32 %u32_42 %u32_42 + %3 = OpISub %u32 %u32_42 %2 + "#]] + .assert_eq(&disassembly); +}