diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 27597b1fd0..1cb32bfb9e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -50,7 +50,7 @@ jobs: run: cargo test -p rustc_codegen_spirv --release --no-default-features --features "use-installed-tools" - name: workspace test (excluding examples & difftest) - run: cargo test --release --workspace --exclude "example-runner-*" --exclude "difftest*" --no-default-features --features "use-installed-tools" + run: cargo test --release --workspace --exclude "example-runner-*" --exclude "difftest*" --no-default-features --features use-installed-tools,include_str,clap # Examples - name: cargo check examples diff --git a/Cargo.lock b/Cargo.lock index e28edb2bc5..985d14bf2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2454,6 +2454,12 @@ dependencies = [ [[package]] name = "rustc_codegen_spirv-target-specs" version = "0.9.0" +dependencies = [ + "serde", + "serde_json", + "strum", + "thiserror 2.0.12", +] [[package]] name = "rustc_codegen_spirv-types" diff --git a/crates/rustc_codegen_spirv-target-specs/Cargo.toml b/crates/rustc_codegen_spirv-target-specs/Cargo.toml index 76e6e2c106..9294cddfb1 100644 --- a/crates/rustc_codegen_spirv-target-specs/Cargo.toml +++ b/crates/rustc_codegen_spirv-target-specs/Cargo.toml @@ -10,3 +10,12 @@ repository.workspace = true [features] include_str = [] dir_path = [] +serde = ["dep:serde"] + +[dependencies] +strum = { version = "0.26.3", features = ["derive"] } +thiserror = "2.0.12" +serde = { version = "1.0", features = ["derive"], optional = true } + +[dev-dependencies] +serde_json = "1.0" diff --git a/crates/rustc_codegen_spirv-target-specs/src/include_str.rs b/crates/rustc_codegen_spirv-target-specs/src/include_str.rs index 53fba7f788..93941e8da4 100644 --- a/crates/rustc_codegen_spirv-target-specs/src/include_str.rs +++ b/crates/rustc_codegen_spirv-target-specs/src/include_str.rs @@ -1,75 +1,64 @@ -/// Metadata for the compile targets supported by `rust-gpu` -pub const TARGET_SPECS: &[(&str, &str)] = &[ - ( - "spirv-unknown-opengl4.0.json", - include_str!("../target-specs/spirv-unknown-opengl4.0.json"), - ), - ( - "spirv-unknown-opengl4.1.json", - include_str!("../target-specs/spirv-unknown-opengl4.1.json"), - ), - ( - "spirv-unknown-opengl4.2.json", - include_str!("../target-specs/spirv-unknown-opengl4.2.json"), - ), - ( - "spirv-unknown-opengl4.3.json", - include_str!("../target-specs/spirv-unknown-opengl4.3.json"), - ), - ( - "spirv-unknown-opengl4.5.json", - include_str!("../target-specs/spirv-unknown-opengl4.5.json"), - ), - ( - "spirv-unknown-spv1.0.json", - include_str!("../target-specs/spirv-unknown-spv1.0.json"), - ), - ( - "spirv-unknown-spv1.1.json", - include_str!("../target-specs/spirv-unknown-spv1.1.json"), - ), - ( - "spirv-unknown-spv1.2.json", - include_str!("../target-specs/spirv-unknown-spv1.2.json"), - ), - ( - "spirv-unknown-spv1.3.json", - include_str!("../target-specs/spirv-unknown-spv1.3.json"), - ), - ( - "spirv-unknown-spv1.4.json", - include_str!("../target-specs/spirv-unknown-spv1.4.json"), - ), - ( - "spirv-unknown-spv1.5.json", - include_str!("../target-specs/spirv-unknown-spv1.5.json"), - ), - ( - "spirv-unknown-spv1.6.json", - include_str!("../target-specs/spirv-unknown-spv1.6.json"), - ), - ( - "spirv-unknown-vulkan1.0.json", - include_str!("../target-specs/spirv-unknown-vulkan1.0.json"), - ), - ( - "spirv-unknown-vulkan1.1.json", - include_str!("../target-specs/spirv-unknown-vulkan1.1.json"), - ), - ( - "spirv-unknown-vulkan1.1spv1.4.json", - include_str!("../target-specs/spirv-unknown-vulkan1.1spv1.4.json"), - ), - ( - "spirv-unknown-vulkan1.2.json", - include_str!("../target-specs/spirv-unknown-vulkan1.2.json"), - ), - ( - "spirv-unknown-vulkan1.3.json", - include_str!("../target-specs/spirv-unknown-vulkan1.3.json"), - ), - ( - "spirv-unknown-vulkan1.4.json", - include_str!("../target-specs/spirv-unknown-vulkan1.4.json"), - ), -]; +//! Metadata for the compile targets supported by `rust-gpu` + +use crate::SpirvTargetEnv; + +impl SpirvTargetEnv { + pub fn include_str(&self) -> &'static str { + match self { + SpirvTargetEnv::OpenGL_4_0 => { + include_str!("../target-specs/spirv-unknown-opengl4.0.json") + } + SpirvTargetEnv::OpenGL_4_1 => { + include_str!("../target-specs/spirv-unknown-opengl4.1.json") + } + SpirvTargetEnv::OpenGL_4_2 => { + include_str!("../target-specs/spirv-unknown-opengl4.2.json") + } + SpirvTargetEnv::OpenGL_4_3 => { + include_str!("../target-specs/spirv-unknown-opengl4.3.json") + } + SpirvTargetEnv::OpenGL_4_5 => { + include_str!("../target-specs/spirv-unknown-opengl4.5.json") + } + SpirvTargetEnv::Spv_1_0 => { + include_str!("../target-specs/spirv-unknown-spv1.0.json") + } + SpirvTargetEnv::Spv_1_1 => { + include_str!("../target-specs/spirv-unknown-spv1.1.json") + } + SpirvTargetEnv::Spv_1_2 => { + include_str!("../target-specs/spirv-unknown-spv1.2.json") + } + SpirvTargetEnv::Spv_1_3 => { + include_str!("../target-specs/spirv-unknown-spv1.3.json") + } + SpirvTargetEnv::Spv_1_4 => { + include_str!("../target-specs/spirv-unknown-spv1.4.json") + } + SpirvTargetEnv::Spv_1_5 => { + include_str!("../target-specs/spirv-unknown-spv1.5.json") + } + SpirvTargetEnv::Spv_1_6 => { + include_str!("../target-specs/spirv-unknown-spv1.6.json") + } + SpirvTargetEnv::Vulkan_1_0 => { + include_str!("../target-specs/spirv-unknown-vulkan1.0.json") + } + SpirvTargetEnv::Vulkan_1_1 => { + include_str!("../target-specs/spirv-unknown-vulkan1.1.json") + } + SpirvTargetEnv::Vulkan_1_1_Spv_1_4 => { + include_str!("../target-specs/spirv-unknown-vulkan1.1spv1.4.json") + } + SpirvTargetEnv::Vulkan_1_2 => { + include_str!("../target-specs/spirv-unknown-vulkan1.2.json") + } + SpirvTargetEnv::Vulkan_1_3 => { + include_str!("../target-specs/spirv-unknown-vulkan1.3.json") + } + SpirvTargetEnv::Vulkan_1_4 => { + include_str!("../target-specs/spirv-unknown-vulkan1.4.json") + } + } + } +} diff --git a/crates/rustc_codegen_spirv-target-specs/src/lib.rs b/crates/rustc_codegen_spirv-target-specs/src/lib.rs index b3033d9fee..85f0bdcaea 100644 --- a/crates/rustc_codegen_spirv-target-specs/src/lib.rs +++ b/crates/rustc_codegen_spirv-target-specs/src/lib.rs @@ -1,10 +1,174 @@ #![doc = include_str!("../README.md")] +use core::str::FromStr; +use std::fmt::{Debug, Display, Formatter}; +use strum::{Display, EnumIter, EnumString, IntoStaticStr}; +use thiserror::Error; + /// directory with all the `target-specs` jsons for our codegen backend #[cfg(feature = "dir_path")] pub const TARGET_SPEC_DIR_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/target-specs"); #[cfg(feature = "include_str")] mod include_str; -#[cfg(feature = "include_str")] -pub use include_str::TARGET_SPECS; +#[cfg(feature = "serde")] +mod serde_feature; +#[cfg(feature = "serde")] +pub use serde_feature::*; + +pub const SPIRV_ARCH: &str = "spirv"; +pub const SPIRV_VENDOR: &str = "unknown"; +pub const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-"; + +/// All target envs rust-gpu supports. The corresponding target tripple is `spirv-unknown-ENV` with `ENV` replaced by any of the values below. +#[allow(non_camel_case_types, clippy::upper_case_acronyms)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, EnumString, IntoStaticStr, EnumIter, Display)] +pub enum SpirvTargetEnv { + #[strum(to_string = "opengl4.0")] + OpenGL_4_0, + #[strum(to_string = "opengl4.1")] + OpenGL_4_1, + #[strum(to_string = "opengl4.2")] + OpenGL_4_2, + #[strum(to_string = "opengl4.3")] + OpenGL_4_3, + #[strum(to_string = "opengl4.5")] + OpenGL_4_5, + #[strum(to_string = "spv1.0")] + Spv_1_0, + #[strum(to_string = "spv1.1")] + Spv_1_1, + #[strum(to_string = "spv1.2")] + Spv_1_2, + #[strum(to_string = "spv1.3")] + Spv_1_3, + #[strum(to_string = "spv1.4")] + Spv_1_4, + #[strum(to_string = "spv1.5")] + Spv_1_5, + #[strum(to_string = "spv1.6")] + Spv_1_6, + #[strum(to_string = "vulkan1.0")] + Vulkan_1_0, + #[strum(to_string = "vulkan1.1")] + Vulkan_1_1, + #[strum(to_string = "vulkan1.1spv1.4")] + Vulkan_1_1_Spv_1_4, + #[strum(to_string = "vulkan1.2")] + Vulkan_1_2, + #[strum(to_string = "vulkan1.3")] + Vulkan_1_3, + #[strum(to_string = "vulkan1.4")] + Vulkan_1_4, +} + +#[derive(Clone, Error, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum SpirvTargetParseError { + #[error("Expected `rustc_codegen_spirv` target with prefix `{SPIRV_TARGET_PREFIX}`, got `{0}`")] + WrongPrefix(String), + #[error( + "Target `{SPIRV_TARGET_PREFIX}{0}` not supported by `rustc_codegen_spirv`, see `enum SpirvTargetEnv` for possible env values" + )] + UnknownEnv(String), +} + +impl Debug for SpirvTargetParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +impl SpirvTargetEnv { + pub fn parse_triple(target: &str) -> Result { + let env = target + .strip_prefix(SPIRV_TARGET_PREFIX) + .ok_or_else(|| SpirvTargetParseError::WrongPrefix(target.to_string()))?; + FromStr::from_str(env).map_err(|_e| SpirvTargetParseError::UnknownEnv(env.to_string())) + } + + pub fn as_str(&self) -> &'static str { + self.into() + } + + pub fn target_triple(&self) -> String { + format!("{SPIRV_TARGET_PREFIX}{}", self.as_str()) + } + + pub fn target_json_file_name(&self) -> String { + format!("{SPIRV_TARGET_PREFIX}{}.json", self.as_str()) + } + + #[cfg(feature = "dir_path")] + pub fn target_json_path(&self) -> String { + format!( + "{TARGET_SPEC_DIR_PATH}/{SPIRV_TARGET_PREFIX}{}.json", + self.as_str() + ) + } + + pub fn iter() -> impl DoubleEndedIterator { + ::iter() + } +} + +pub trait IntoSpirvTarget: Sized { + fn to_spirv_target_env(&self) -> Result; +} + +impl IntoSpirvTarget for SpirvTargetEnv { + fn to_spirv_target_env(&self) -> Result { + Ok(*self) + } +} + +impl IntoSpirvTarget for &str { + fn to_spirv_target_env(&self) -> Result { + SpirvTargetEnv::parse_triple(self) + } +} + +impl IntoSpirvTarget for String { + fn to_spirv_target_env(&self) -> Result { + SpirvTargetEnv::parse_triple(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_prefix() { + assert_eq!( + SPIRV_TARGET_PREFIX, + &format!("{SPIRV_ARCH}-{SPIRV_VENDOR}-") + ); + } + + #[test] + pub fn test_triple_parse_roundtrip() { + for target in SpirvTargetEnv::iter() { + let parsed = SpirvTargetEnv::parse_triple(&target.target_triple()).unwrap(); + assert_eq!(target, parsed); + } + } + + #[test] + #[cfg(feature = "dir_path")] + pub fn test_target_json_path() { + for target in SpirvTargetEnv::iter() { + let file = std::path::PathBuf::from(target.target_json_path()); + assert!(file.is_file(), "{}", file.display()); + } + } + + #[test] + #[cfg(all(feature = "dir_path", feature = "include_str"))] + pub fn test_target_json_content() { + for target in SpirvTargetEnv::iter() { + let content = std::fs::read_to_string(target.target_json_path()).unwrap(); + assert_eq!(content, target.include_str()); + } + } +} diff --git a/crates/rustc_codegen_spirv-target-specs/src/serde_feature.rs b/crates/rustc_codegen_spirv-target-specs/src/serde_feature.rs new file mode 100644 index 0000000000..5af3e35cc3 --- /dev/null +++ b/crates/rustc_codegen_spirv-target-specs/src/serde_feature.rs @@ -0,0 +1,88 @@ +use crate::{SpirvTargetEnv, SpirvTargetParseError}; +use serde::ser::Error; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +impl Serialize for SpirvTargetEnv { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.target_triple()) + } +} + +impl<'de> Deserialize<'de> for SpirvTargetEnv { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let target = String::deserialize(deserializer)?; + Self::parse_triple(&target).map_err(|_e| serde::de::Error::unknown_variant(&target, &[])) + } +} + +pub fn serialize_target( + target: &Option>, + serializer: S, +) -> Result +where + S: Serializer, +{ + // cannot use `transpose()` due to target being a ref, not a value + let option = match target { + None => None, + Some(Ok(e)) => Some(*e), + Some(Err(_e)) => Err(Error::custom( + "cannot serialize `target` that failed to parse", + ))?, + }; + Serialize::serialize(&option, serializer) +} + +pub fn deserialize_target<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Ok( as Deserialize>::deserialize( + deserializer, + )?) + .transpose()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{SpirvTargetEnv, SpirvTargetParseError}; + + #[test] + fn test_serde_roundtrip() { + for env in SpirvTargetEnv::iter() { + let json = serde_json::to_string(&env).unwrap(); + let deserialize: SpirvTargetEnv = serde_json::from_str(&json).unwrap(); + assert_eq!(env, deserialize); + } + } + + #[test] + fn test_serde_target_roundtrip() { + #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] + struct FakeSpirvBuilder { + #[serde( + serialize_with = "serialize_target", + deserialize_with = "deserialize_target" + )] + target: Option>, + } + + for env in SpirvTargetEnv::iter() { + let builder = FakeSpirvBuilder { + target: Some(Ok(env)), + }; + let json = serde_json::to_string(&builder).unwrap(); + let deserialize: FakeSpirvBuilder = serde_json::from_str(&json).unwrap(); + assert_eq!(builder, deserialize); + } + } +} diff --git a/crates/rustc_codegen_spirv/src/builder_spirv.rs b/crates/rustc_codegen_spirv/src/builder_spirv.rs index 4c87f0584d..b08c076a6c 100644 --- a/crates/rustc_codegen_spirv/src/builder_spirv.rs +++ b/crates/rustc_codegen_spirv/src/builder_spirv.rs @@ -5,7 +5,7 @@ use crate::builder; use crate::codegen_cx::CodegenCx; use crate::spirv_type::SpirvType; use crate::symbols::Symbols; -use crate::target::SpirvTarget; +use crate::target::TargetsExt; use crate::target_feature::TargetFeature; use rspirv::dr::{Block, Builder, Instruction, Module, Operand}; use rspirv::spirv::{ @@ -13,6 +13,7 @@ use rspirv::spirv::{ }; use rspirv::{binary::Assemble, binary::Disassemble}; use rustc_arena::DroplessArena; +use rustc_codegen_spirv_target_specs::SpirvTargetEnv; use rustc_codegen_ssa::traits::ConstCodegenMethods as _; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::sync::Lrc; @@ -431,7 +432,7 @@ impl<'tcx> BuilderSpirv<'tcx> { pub fn new( tcx: TyCtxt<'tcx>, sym: &Symbols, - target: &SpirvTarget, + target: &SpirvTargetEnv, features: &[TargetFeature], ) -> Self { let version = target.spirv_version(); diff --git a/crates/rustc_codegen_spirv/src/codegen_cx/mod.rs b/crates/rustc_codegen_spirv/src/codegen_cx/mod.rs index 9f575fba72..dd001558f5 100644 --- a/crates/rustc_codegen_spirv/src/codegen_cx/mod.rs +++ b/crates/rustc_codegen_spirv/src/codegen_cx/mod.rs @@ -8,15 +8,16 @@ use crate::builder_spirv::{BuilderCursor, BuilderSpirv, SpirvConst, SpirvValue, use crate::custom_decorations::{CustomDecoration, SrcLocDecoration, ZombieDecoration}; use crate::spirv_type::{SpirvType, SpirvTypePrinter, TypeCache}; use crate::symbols::Symbols; -use crate::target::SpirvTarget; // HACK(eddyb) avoids rewriting all of the imports (see `lib.rs` and `build.rs`). use crate::maybe_pqp_cg_ssa as rustc_codegen_ssa; +use crate::target::TargetsExt; use itertools::Itertools as _; use rspirv::dr::{Module, Operand}; use rspirv::spirv::{Decoration, LinkageType, Op, Word}; use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; +use rustc_codegen_spirv_target_specs::SpirvTargetEnv; use rustc_codegen_ssa::mir::debuginfo::{FunctionDebugContext, VariableKind}; use rustc_codegen_ssa::traits::{ AsmCodegenMethods, BackendTypes, DebugInfoCodegenMethods, GlobalAsmOperandRef, @@ -99,16 +100,7 @@ impl<'tcx> CodegenCx<'tcx> { pub fn new(tcx: TyCtxt<'tcx>, codegen_unit: &'tcx CodegenUnit<'tcx>) -> Self { // Validate the target spec, as the backend doesn't control `--target`. let target_tuple = tcx.sess.opts.target_triple.tuple(); - let target: SpirvTarget = target_tuple.parse().unwrap_or_else(|_| { - let qualifier = if !target_tuple.starts_with("spirv-") { - "non-SPIR-V " - } else { - "" - }; - tcx.dcx().fatal(format!( - "{qualifier}target `{target_tuple}` not supported by `rustc_codegen_spirv`", - )) - }); + let target = SpirvTargetEnv::parse_triple(target_tuple).unwrap(); let target_spec_mismatched_jsons = { use rustc_target::json::ToJson; diff --git a/crates/rustc_codegen_spirv/src/linker/test.rs b/crates/rustc_codegen_spirv/src/linker/test.rs index 529b4c697b..725d7ab6ff 100644 --- a/crates/rustc_codegen_spirv/src/linker/test.rs +++ b/crates/rustc_codegen_spirv/src/linker/test.rs @@ -1,5 +1,7 @@ use super::{LinkResult, link}; +use crate::target::TargetsExt; use rspirv::dr::{Loader, Module}; +use rustc_codegen_spirv_target_specs::SpirvTargetEnv; use rustc_errors::registry::Registry; use rustc_session::CompilerIO; use rustc_session::config::{Input, OutputFilenames, OutputTypes}; @@ -130,10 +132,7 @@ fn link_with_linker_opts( .unwrap(); let sopts = rustc_session::config::build_session_options(&mut early_dcx, &matches); - let target = "spirv-unknown-spv1.0" - .parse::() - .unwrap() - .rustc_target(); + let target = SpirvTargetEnv::Spv_1_0.rustc_target(); let sm_inputs = rustc_span::source_map::SourceMapInputs { file_loader: Box::new(rustc_span::source_map::RealFileLoader), path_mapping: sopts.file_path_mapping(), diff --git a/crates/rustc_codegen_spirv/src/target.rs b/crates/rustc_codegen_spirv/src/target.rs index 8a6cdaf03f..c9912877da 100644 --- a/crates/rustc_codegen_spirv/src/target.rs +++ b/crates/rustc_codegen_spirv/src/target.rs @@ -1,55 +1,70 @@ use rspirv::spirv::MemoryModel; +use rustc_codegen_spirv_target_specs::{SPIRV_ARCH, SPIRV_VENDOR, SpirvTargetEnv}; use rustc_target::spec::{Cc, LinkerFlavor, PanicStrategy, Target, TargetOptions}; -use spirv_tools::TargetEnv; -const ARCH: &str = "spirv"; - -pub struct SpirvTarget { - env: TargetEnv, - vendor: String, +pub trait TargetsExt { + fn memory_model(&self) -> MemoryModel; + fn to_spirv_tools(&self) -> spirv_tools::TargetEnv; + fn spirv_version(&self) -> (u8, u8); + fn rustc_target(&self) -> Target; } -impl SpirvTarget { - pub fn memory_model(&self) -> MemoryModel { - match self.env { - TargetEnv::Universal_1_0 - | TargetEnv::Universal_1_1 - | TargetEnv::Universal_1_2 - | TargetEnv::Universal_1_3 - | TargetEnv::Universal_1_4 - | TargetEnv::Universal_1_5 - | TargetEnv::Universal_1_6 => MemoryModel::Simple, - - TargetEnv::OpenGL_4_0 - | TargetEnv::OpenGL_4_1 - | TargetEnv::OpenGL_4_2 - | TargetEnv::OpenGL_4_3 - | TargetEnv::OpenGL_4_5 => MemoryModel::GLSL450, - - TargetEnv::OpenCL_2_1 - | TargetEnv::OpenCL_2_2 - | TargetEnv::OpenCL_1_2 - | TargetEnv::OpenCLEmbedded_1_2 - | TargetEnv::OpenCL_2_0 - | TargetEnv::OpenCLEmbedded_2_0 - | TargetEnv::OpenCLEmbedded_2_1 - | TargetEnv::OpenCLEmbedded_2_2 => MemoryModel::OpenCL, +impl TargetsExt for SpirvTargetEnv { + #[allow(clippy::match_same_arms)] + fn memory_model(&self) -> MemoryModel { + match self { + SpirvTargetEnv::Spv_1_0 + | SpirvTargetEnv::Spv_1_1 + | SpirvTargetEnv::Spv_1_2 + | SpirvTargetEnv::Spv_1_3 + | SpirvTargetEnv::Spv_1_4 + | SpirvTargetEnv::Spv_1_5 + | SpirvTargetEnv::Spv_1_6 => MemoryModel::Simple, + + SpirvTargetEnv::OpenGL_4_0 + | SpirvTargetEnv::OpenGL_4_1 + | SpirvTargetEnv::OpenGL_4_2 + | SpirvTargetEnv::OpenGL_4_3 + | SpirvTargetEnv::OpenGL_4_5 => MemoryModel::GLSL450, + + SpirvTargetEnv::Vulkan_1_0 + | SpirvTargetEnv::Vulkan_1_1 + | SpirvTargetEnv::Vulkan_1_1_Spv_1_4 + | SpirvTargetEnv::Vulkan_1_2 + | SpirvTargetEnv::Vulkan_1_3 + | SpirvTargetEnv::Vulkan_1_4 => MemoryModel::Vulkan, + } + } - TargetEnv::Vulkan_1_0 - | TargetEnv::Vulkan_1_1 - | TargetEnv::WebGPU_0 - | TargetEnv::Vulkan_1_1_Spirv_1_4 - | TargetEnv::Vulkan_1_2 - | TargetEnv::Vulkan_1_3 - | TargetEnv::Vulkan_1_4 => MemoryModel::Vulkan, + #[allow(clippy::match_same_arms)] + fn to_spirv_tools(&self) -> spirv_tools::TargetEnv { + match self { + SpirvTargetEnv::OpenGL_4_0 => spirv_tools::TargetEnv::OpenGL_4_0, + SpirvTargetEnv::OpenGL_4_1 => spirv_tools::TargetEnv::OpenGL_4_1, + SpirvTargetEnv::OpenGL_4_2 => spirv_tools::TargetEnv::OpenGL_4_2, + SpirvTargetEnv::OpenGL_4_3 => spirv_tools::TargetEnv::OpenGL_4_3, + SpirvTargetEnv::OpenGL_4_5 => spirv_tools::TargetEnv::OpenGL_4_5, + SpirvTargetEnv::Spv_1_0 => spirv_tools::TargetEnv::Universal_1_0, + SpirvTargetEnv::Spv_1_1 => spirv_tools::TargetEnv::Universal_1_1, + SpirvTargetEnv::Spv_1_2 => spirv_tools::TargetEnv::Universal_1_2, + SpirvTargetEnv::Spv_1_3 => spirv_tools::TargetEnv::Universal_1_3, + SpirvTargetEnv::Spv_1_4 => spirv_tools::TargetEnv::Universal_1_4, + SpirvTargetEnv::Spv_1_5 => spirv_tools::TargetEnv::Universal_1_5, + SpirvTargetEnv::Spv_1_6 => spirv_tools::TargetEnv::Universal_1_6, + SpirvTargetEnv::Vulkan_1_0 => spirv_tools::TargetEnv::Vulkan_1_0, + SpirvTargetEnv::Vulkan_1_1 => spirv_tools::TargetEnv::Vulkan_1_1, + SpirvTargetEnv::Vulkan_1_1_Spv_1_4 => spirv_tools::TargetEnv::Vulkan_1_1_Spirv_1_4, + SpirvTargetEnv::Vulkan_1_2 => spirv_tools::TargetEnv::Vulkan_1_2, + SpirvTargetEnv::Vulkan_1_3 => spirv_tools::TargetEnv::Vulkan_1_3, + SpirvTargetEnv::Vulkan_1_4 => spirv_tools::TargetEnv::Vulkan_1_4, } } - pub fn spirv_version(&self) -> (u8, u8) { - self.env.spirv_version() + fn spirv_version(&self) -> (u8, u8) { + self.to_spirv_tools().spirv_version() } - fn init_target_opts(&self) -> TargetOptions { + fn rustc_target(&self) -> Target { let mut o = TargetOptions::default(); o.simd_types_indirect = false; o.allows_weak_linkage = false; @@ -61,69 +76,18 @@ impl SpirvTarget { o.linker_flavor = LinkerFlavor::Unix(Cc::No); o.panic_strategy = PanicStrategy::Abort; o.os = "unknown".into(); - o.env = self.env.to_string().into(); - o.vendor = self.vendor.clone().into(); + o.env = self.as_str().into(); + o.vendor = SPIRV_VENDOR.into(); // TODO: Investigate if main_needs_argc_argv is useful (for building exes) o.main_needs_argc_argv = false; - o - } - pub fn rustc_target(&self) -> Target { Target { - llvm_target: self.to_string().into(), + llvm_target: self.target_triple().into(), metadata: Default::default(), pointer_width: 32, data_layout: "e-m:e-p:32:32:32-i64:64-n8:16:32:64".into(), - arch: ARCH.into(), - options: self.init_target_opts(), - } - } -} - -impl std::str::FromStr for SpirvTarget { - type Err = InvalidTarget; - - fn from_str(target: &str) -> Result { - let mut iter = target.split('-'); - let error = || InvalidTarget(target.into()); - - if iter.next() != Some(ARCH) { - return Err(error()); + arch: SPIRV_ARCH.into(), + options: o, } - - let vendor = iter.next().map(From::from).ok_or_else(error)?; - - let env = iter - .next() - .and_then(|env| env.parse().ok()) - .ok_or_else(error)?; - - if iter.next().is_some() { - return Err(error()); - } - - let result = Self { env, vendor }; - - if result.memory_model() == MemoryModel::OpenCL { - return Err(error()); - } - - Ok(result) - } -} - -impl std::fmt::Display for SpirvTarget { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}-{}-{}", ARCH, self.vendor, self.env) - } -} - -#[derive(Debug)] -pub struct InvalidTarget(String); - -impl std::error::Error for InvalidTarget {} -impl std::fmt::Display for InvalidTarget { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Invalid target `{}`.", self.0) } } diff --git a/crates/spirv-builder/Cargo.toml b/crates/spirv-builder/Cargo.toml index f5c1bd2206..2090187fb3 100644 --- a/crates/spirv-builder/Cargo.toml +++ b/crates/spirv-builder/Cargo.toml @@ -24,7 +24,7 @@ default = ["use-compiled-tools"] rustc_codegen_spirv = ["dep:rustc_codegen_spirv"] # Inclide target spec json files, allows constructing SpirvBuilder without # explicitly passing a path to the target spec json -include-target-specs = ["dep:rustc_codegen_spirv-target-specs"] +include-target-specs = ["rustc_codegen_spirv-target-specs/dir_path"] # See `rustc_codegen_spirv/Cargo.toml` for details on these features. # We add new "default" features to `use-installed-tools` and `use-compiled-tools` to keep # backwards compat with `default-features = false, features = "use-installed-tools"` setups @@ -38,7 +38,7 @@ clap = ["dep:clap"] [dependencies] rustc_codegen_spirv = { workspace = true, optional = true } rustc_codegen_spirv-types = { workspace = true } -rustc_codegen_spirv-target-specs = { workspace = true, features = ["dir_path"], optional = true } +rustc_codegen_spirv-target-specs = { workspace = true, features = ["serde"] } memchr = "2.4" raw-string = "0.3.5" diff --git a/crates/spirv-builder/src/lib.rs b/crates/spirv-builder/src/lib.rs index c757b59f63..4a7de6972a 100644 --- a/crates/spirv-builder/src/lib.rs +++ b/crates/spirv-builder/src/lib.rs @@ -73,6 +73,8 @@ #![doc = include_str!("../README.md")] mod depfile; +#[cfg(test)] +mod tests; #[cfg(feature = "watch")] mod watch; @@ -88,8 +90,11 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use thiserror::Error; -pub use rustc_codegen_spirv_types::Capability; -pub use rustc_codegen_spirv_types::{CompileResult, ModuleResult}; +pub use rustc_codegen_spirv_target_specs::{ + IntoSpirvTarget, SpirvTargetEnv, SpirvTargetParseError, +}; +pub use rustc_codegen_spirv_target_specs::{deserialize_target, serialize_target}; +pub use rustc_codegen_spirv_types::*; #[cfg(feature = "include-target-specs")] pub use rustc_codegen_spirv_target_specs::TARGET_SPEC_DIR_PATH; @@ -99,10 +104,8 @@ pub use rustc_codegen_spirv_target_specs::TARGET_SPEC_DIR_PATH; pub enum SpirvBuilderError { #[error("`target` must be set, for example `spirv-unknown-vulkan1.2`")] MissingTarget, - #[error("expected `{SPIRV_TARGET_PREFIX}...` target, found `{target}`")] - NonSpirvTarget { target: String }, - #[error("SPIR-V target `{SPIRV_TARGET_PREFIX}-{target_env}` is not supported")] - UnsupportedSpirvTargetEnv { target_env: String }, + #[error("Error parsing target: {0}")] + SpirvTargetParseError(#[from] SpirvTargetParseError), #[error("`path_to_crate` must be set")] MissingCratePath, #[error("crate path '{0}' does not exist")] @@ -132,8 +135,6 @@ pub enum SpirvBuilderError { CargoMetadata(#[from] cargo_metadata::Error), } -const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-"; - #[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[non_exhaustive] @@ -382,9 +383,17 @@ pub struct SpirvBuilder { /// The target triple, eg. `spirv-unknown-vulkan1.2` #[cfg_attr( feature = "clap", - clap(long, default_value = "spirv-unknown-vulkan1.2") + clap( + long, + default_value = "spirv-unknown-vulkan1.2", + value_parser = Self::parse_target + ) )] - pub target: Option, + #[serde( + serialize_with = "serialize_target", + deserialize_with = "deserialize_target" + )] + pub target: Option>, /// Cargo features specification for building the shader crate. #[cfg_attr(feature = "clap", clap(flatten))] #[serde(flatten)] @@ -453,6 +462,12 @@ pub struct SpirvBuilder { #[cfg(feature = "clap")] impl SpirvBuilder { + fn parse_target( + target: &str, + ) -> Result, clap::Error> { + Ok(SpirvTargetEnv::parse_triple(target)) + } + /// Clap value parser for `Capability`. fn parse_spirv_capability(capability: &str) -> Result { use core::str::FromStr; @@ -490,10 +505,10 @@ impl Default for SpirvBuilder { } impl SpirvBuilder { - pub fn new(path_to_crate: impl AsRef, target: impl Into) -> Self { + pub fn new(path_to_crate: impl AsRef, target: impl IntoSpirvTarget) -> Self { Self { path_to_crate: Some(path_to_crate.as_ref().to_owned()), - target: Some(target.into()), + target: Some(target.to_spirv_target_env()), ..SpirvBuilder::default() } } @@ -762,46 +777,19 @@ fn join_checking_for_separators(strings: Vec>, sep: &str) -> St fn invoke_rustc(builder: &SpirvBuilder) -> Result { let target = builder .target - .as_ref() - .ok_or(SpirvBuilderError::MissingTarget)?; + .clone() + .ok_or(SpirvBuilderError::MissingTarget)??; let path_to_crate = builder .path_to_crate .as_ref() .ok_or(SpirvBuilderError::MissingCratePath)?; - { - let target_env = target.strip_prefix(SPIRV_TARGET_PREFIX).ok_or_else(|| { - SpirvBuilderError::NonSpirvTarget { - target: target.clone(), - } - })?; - // HACK(eddyb) used only to split the full list into groups. - #[allow(clippy::match_same_arms)] - match target_env { - // HACK(eddyb) hardcoded list to avoid checking if the JSON file - // for a particular target exists (and sanitizing strings for paths). - // - // FIXME(eddyb) consider moving this list, or even `target-specs`, - // into `rustc_codegen_spirv_types`'s code/source. - "spv1.0" | "spv1.1" | "spv1.2" | "spv1.3" | "spv1.4" | "spv1.5" | "spv1.6" => {} - "opengl4.0" | "opengl4.1" | "opengl4.2" | "opengl4.3" | "opengl4.5" => {} - "vulkan1.0" | "vulkan1.1" | "vulkan1.1spv1.4" | "vulkan1.2" | "vulkan1.3" - | "vulkan1.4" => {} - - _ => { - return Err(SpirvBuilderError::UnsupportedSpirvTargetEnv { - target_env: target_env.into(), - }); - } - } - - if (builder.print_metadata == MetadataPrintout::Full) && builder.multimodule { - return Err(SpirvBuilderError::MultiModuleWithPrintMetadata); - } - if !path_to_crate.is_dir() { - return Err(SpirvBuilderError::CratePathDoesntExist( - path_to_crate.clone(), - )); - } + if (builder.print_metadata == MetadataPrintout::Full) && builder.multimodule { + return Err(SpirvBuilderError::MultiModuleWithPrintMetadata); + } + if !path_to_crate.is_dir() { + return Err(SpirvBuilderError::CratePathDoesntExist( + path_to_crate.clone(), + )); } let toolchain_rustc_version = @@ -984,8 +972,7 @@ fn invoke_rustc(builder: &SpirvBuilder) -> Result { let path; #[cfg(feature = "include-target-specs")] { - path = path_opt - .unwrap_or_else(|| PathBuf::from(format!("{TARGET_SPEC_DIR_PATH}/{target}.json"))); + path = path_opt.unwrap_or_else(|| PathBuf::from(target.target_json_path())); } #[cfg(not(feature = "include-target-specs"))] { @@ -993,7 +980,7 @@ fn invoke_rustc(builder: &SpirvBuilder) -> Result { } cargo.arg("--target").arg(path); } else { - cargo.arg("--target").arg(target); + cargo.arg("--target").arg(target.target_triple()); } if !builder.shader_crate_features.default_features { diff --git a/crates/spirv-builder/src/tests.rs b/crates/spirv-builder/src/tests.rs new file mode 100644 index 0000000000..84b45dc3d0 --- /dev/null +++ b/crates/spirv-builder/src/tests.rs @@ -0,0 +1,27 @@ +#[cfg(feature = "clap")] +mod clap { + pub use crate::*; + use clap::Parser; + + /// look at the output of this test to see what `--help` prints + #[test] + fn test_clap_help() { + match SpirvBuilder::try_parse_from(["", "--help"]) { + Ok(_) => panic!("help must fail to parse"), + Err(e) => { + let e = e.to_string(); + println!("{}", e); + assert!(e.contains("--target")); + } + }; + } + + #[test] + fn test_clap_target() { + let env = SpirvTargetEnv::OpenGL_4_2; + let builder = SpirvBuilder::try_parse_from(["", "--target", &env.target_triple()]) + .map_err(|e| e.to_string()) + .unwrap(); + assert_eq!(builder.target.unwrap().unwrap(), env); + } +} diff --git a/tests/compiletests/src/main.rs b/tests/compiletests/src/main.rs index 64ca1c7e70..0bf975412e 100644 --- a/tests/compiletests/src/main.rs +++ b/tests/compiletests/src/main.rs @@ -1,6 +1,6 @@ use clap::Parser; use itertools::Itertools as _; -use rustc_codegen_spirv_target_specs::TARGET_SPEC_DIR_PATH; +use rustc_codegen_spirv_target_specs::SpirvTargetEnv; use std::{ env, io, path::{Path, PathBuf}, @@ -28,12 +28,6 @@ impl Opt { } } -const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-"; - -fn target_spec_json(target: &str) -> String { - format!("{TARGET_SPEC_DIR_PATH}/{target}.json") -} - #[derive(Copy, Clone)] enum DepKind { SpirvLib, @@ -48,9 +42,9 @@ impl DepKind { } } - fn target_dir_suffix(self, target: &str) -> String { + fn target_dir_suffix(self, target: SpirvTargetEnv) -> String { match self { - Self::SpirvLib => format!("{target}/debug/deps"), + Self::SpirvLib => format!("{}/debug/deps", target.target_triple()), Self::ProcMacro => "debug/deps".into(), } } @@ -134,34 +128,40 @@ impl Runner { extra_flags: "", }]; - for (env, variation) in self + for (target, variation) in self .opt .environments() - .flat_map(|env| VARIATIONS.iter().map(move |variation| (env, variation))) + .flat_map(|target| VARIATIONS.iter().map(move |variation| (target, variation))) { + let target = if target.contains("-") { + SpirvTargetEnv::parse_triple(target) + } else { + SpirvTargetEnv::parse_triple(&format!("spirv-unknown-{target}")) + } + .unwrap(); + // HACK(eddyb) in order to allow *some* tests to have separate output // in different testing variations (i.e. experimental features), while // keeping *most* of the tests unchanged, we make use of "stage IDs", // which offer `// only-S` and `// ignore-S` for any stage ID `S`. let stage_id = if variation.name == "default" { // Use the environment name as the stage ID. - env.to_string() + target.to_string() } else { // Include the variation name in the stage ID. - format!("{}-{}", env, variation.name) + format!("{}-{}", target, variation.name) }; - println!("Testing env: {}\n", stage_id); + println!("Testing target: {}\n", target); - let target = format!("{SPIRV_TARGET_PREFIX}{env}"); - let libs = build_deps(&self.deps_target_dir, &self.codegen_backend_path, &target); + let libs = build_deps(&self.deps_target_dir, &self.codegen_backend_path, target); let mut flags = test_rustc_flags(&self.codegen_backend_path, &libs, &[ &self .deps_target_dir - .join(DepKind::SpirvLib.target_dir_suffix(&target)), + .join(DepKind::SpirvLib.target_dir_suffix(target)), &self .deps_target_dir - .join(DepKind::ProcMacro.target_dir_suffix(&target)), + .join(DepKind::ProcMacro.target_dir_suffix(target)), ]); flags += variation.extra_flags; @@ -169,7 +169,7 @@ impl Runner { stage_id, target_rustcflags: Some(flags), mode: mode.parse().expect("Invalid mode"), - target: target_spec_json(&target), + target: target.target_json_path(), src_base: self.tests_dir.join(mode), build_base: self.compiletest_build_dir.clone(), bless: self.opt.bless, @@ -185,7 +185,11 @@ impl Runner { } /// Runs the processes needed to build `spirv-std` & other deps. -fn build_deps(deps_target_dir: &Path, codegen_backend_path: &Path, target: &str) -> TestDeps { +fn build_deps( + deps_target_dir: &Path, + codegen_backend_path: &Path, + target: SpirvTargetEnv, +) -> TestDeps { // Build compiletests-deps-helper std::process::Command::new("cargo") .args([ @@ -194,7 +198,7 @@ fn build_deps(deps_target_dir: &Path, codegen_backend_path: &Path, target: &str) "compiletests-deps-helper", "-Zbuild-std=core", "-Zbuild-std-features=compiler-builtins-mem", - &*format!("--target={}", target_spec_json(target)), + &*format!("--target={}", target.target_json_path()), ]) .arg("--target-dir") .arg(deps_target_dir) @@ -279,7 +283,7 @@ fn find_lib( deps_target_dir: &Path, base: impl AsRef, dep_kind: DepKind, - target: &str, + target: SpirvTargetEnv, ) -> Result { let base = base.as_ref(); let (expected_prefix, expected_extension) = dep_kind.prefix_and_extension(); diff --git a/tests/difftests/tests/Cargo.lock b/tests/difftests/tests/Cargo.lock index 7ad114e41e..57c2ce5f85 100644 --- a/tests/difftests/tests/Cargo.lock +++ b/tests/difftests/tests/Cargo.lock @@ -847,6 +847,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_codegen_spirv-target-specs" +version = "0.9.0" +dependencies = [ + "serde", + "strum", + "thiserror 2.0.12", +] + [[package]] name = "rustc_codegen_spirv-types" version = "0.9.0" @@ -985,6 +994,7 @@ dependencies = [ "cargo_metadata", "memchr", "raw-string", + "rustc_codegen_spirv-target-specs", "rustc_codegen_spirv-types", "semver", "serde",