From a9c36bd7336d564bfa985be9149d36814ac19251 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 17 Dec 2025 14:32:13 +0100 Subject: [PATCH 1/2] target declaration refactor --- .../rustc_codegen_spirv-types/src/target.rs | 2 + .../rustc_codegen_spirv/src/builder_spirv.rs | 4 +- .../rustc_codegen_spirv/src/codegen_cx/mod.rs | 4 +- crates/rustc_codegen_spirv/src/lib.rs | 2 +- crates/rustc_codegen_spirv/src/linker/test.rs | 6 +- crates/rustc_codegen_spirv/src/target.rs | 689 +++++++++++++++--- 6 files changed, 607 insertions(+), 100 deletions(-) diff --git a/crates/rustc_codegen_spirv-types/src/target.rs b/crates/rustc_codegen_spirv-types/src/target.rs index 86c0bb1a0f..2f42e74817 100644 --- a/crates/rustc_codegen_spirv-types/src/target.rs +++ b/crates/rustc_codegen_spirv-types/src/target.rs @@ -1,6 +1,8 @@ use std::fmt::{Debug, Formatter}; use thiserror::Error; +pub const SPIRV_VENDOR: &str = "unknown"; +pub const SPIRV_ARCH: &str = "spirv"; pub const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-"; /// A well-formed rust-gpu target. diff --git a/crates/rustc_codegen_spirv/src/builder_spirv.rs b/crates/rustc_codegen_spirv/src/builder_spirv.rs index e05b433057..be6b247d87 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::{SpirvTarget, SpirvTargetVariant, SpirvVersion}; use crate::target_feature::TargetFeature; use rspirv::dr::{Builder, Instruction, Module, Operand}; use rspirv::spirv::{ @@ -492,7 +492,7 @@ impl<'tcx> BuilderSpirv<'tcx> { add_cap(&mut builder, &mut enabled_capabilities, Capability::Shader); if memory_model == MemoryModel::Vulkan { - if version < (1, 5) { + if version < SpirvVersion::V1_5 { add_ext(&mut builder, sym.spv_khr_vulkan_memory_model); } add_cap( diff --git a/crates/rustc_codegen_spirv/src/codegen_cx/mod.rs b/crates/rustc_codegen_spirv/src/codegen_cx/mod.rs index d36e789b1c..daefbd8cd8 100644 --- a/crates/rustc_codegen_spirv/src/codegen_cx/mod.rs +++ b/crates/rustc_codegen_spirv/src/codegen_cx/mod.rs @@ -10,11 +10,11 @@ use crate::builder_spirv::{ 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::SpirvTarget; use itertools::Itertools as _; use rspirv::dr::{Module, Operand}; use rspirv::spirv::{Decoration, LinkageType, Word}; @@ -99,7 +99,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 target = SpirvTarget::parse(target_tuple).unwrap_or_else(|_| { let qualifier = if !target_tuple.starts_with("spirv-") { "non-SPIR-V " } else { diff --git a/crates/rustc_codegen_spirv/src/lib.rs b/crates/rustc_codegen_spirv/src/lib.rs index 3e57d8648d..be6b291e25 100644 --- a/crates/rustc_codegen_spirv/src/lib.rs +++ b/crates/rustc_codegen_spirv/src/lib.rs @@ -136,7 +136,7 @@ mod linker; mod spirv_type; mod spirv_type_constraints; mod symbols; -mod target; +pub mod target; mod target_feature; use builder::Builder; diff --git a/crates/rustc_codegen_spirv/src/linker/test.rs b/crates/rustc_codegen_spirv/src/linker/test.rs index 3fdd7185af..a160fd9380 100644 --- a/crates/rustc_codegen_spirv/src/linker/test.rs +++ b/crates/rustc_codegen_spirv/src/linker/test.rs @@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex}; // We need to construct an emitter as yet another workaround, // see https://github.com/rust-lang/rust/pull/102992. extern crate termcolor; +use crate::target::SpirvTarget; use termcolor::{ColorSpec, WriteColor}; // https://github.com/colin-kiegel/rust-pretty-assertions/issues/24 @@ -130,10 +131,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 = SpirvTarget::UNIVERSAL_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 71390f9b8d..cd93695332 100644 --- a/crates/rustc_codegen_spirv/src/target.rs +++ b/crates/rustc_codegen_spirv/src/target.rs @@ -1,55 +1,565 @@ use rspirv::spirv::MemoryModel; +use rustc_codegen_spirv_types::{SPIRV_ARCH, SPIRV_TARGET_PREFIX, SPIRV_VENDOR}; use rustc_target::spec::{Cc, LinkerFlavor, PanicStrategy, Target, TargetOptions}; -use spirv_tools::TargetEnv; +use std::cmp::Ordering; +use std::fmt::{Debug, Display, Formatter}; +use std::ops::{Deref, DerefMut}; +use std::str::FromStr; -const ARCH: &str = "spirv"; +#[derive(Clone, Eq, PartialEq)] +pub enum TargetError { + UnknownTarget(String), + InvalidTargetVersion(SpirvTarget), +} + +impl Display for TargetError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TargetError::UnknownTarget(target) => { + write!(f, "Unknown target `{target}`") + } + TargetError::InvalidTargetVersion(target) => { + write!(f, "Invalid version in target `{}`", target.env()) + } + } + } +} + +impl Debug for TargetError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +/// A version with a major and minor component +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Version(pub u8, pub u8); + +impl Version { + pub const fn new(major: u8, minor: u8) -> Self { + Self(major, minor) + } + + pub const fn from_tuple(tuple: (u8, u8)) -> Self { + Self(tuple.0, tuple.1) + } + + pub const fn to_tuple(self) -> (u8, u8) { + (self.0, self.1) + } + + fn parse_unbounded(s: &str) -> Option<(Self, &str)> { + fn parse_num(s: &str) -> Option<(&str, u8)> { + let mut value = 0; + let mut len = 0; + for digit in s.as_bytes().iter().copied() { + if !digit.is_ascii_digit() { + break; + } + if value == 0 && len > 0 { + return None; + } + value = value * 10 + (digit - b'0') as u64; + len += 1; + } + (len > 0).then_some((&s[len..], u8::try_from(value).ok()?)) + } + let (s, major) = parse_num(s)?; + if !matches!(s.chars().next(), Some('.')) { + return None; + } + let s = &s[1..]; + let (s, minor) = parse_num(s)?; + Some((Self(major, minor), s)) + } +} + +impl FromStr for Version { + type Err = (); + + fn from_str(s: &str) -> Result { + let (out, s) = Self::parse_unbounded(s).ok_or(())?; + s.is_empty().then_some(out).ok_or(()) + } +} + +#[inline] +fn from_str_to_parse_unbounded( + s: &str, + parse_unbounded: impl FnOnce(&str) -> Option<(T, &str)>, +) -> Result { + let unknown = || TargetError::UnknownTarget(s.to_owned()); + let (out, s) = parse_unbounded(s).ok_or_else(unknown)?; + if !s.is_empty() { + return Err(unknown()); + } + out.validate()?; + Ok(out) +} + +impl Display for Version { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.0, self.1) + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} -pub struct SpirvTarget { - env: TargetEnv, - vendor: String, +impl Ord for Version { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0).then(self.1.cmp(&other.1)) + } +} + +/// A SPIR-V version +/// +/// For the SPIR-V universal target, see [`UniversalTarget`] +#[repr(transparent)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct SpirvVersion { + pub version: Version, +} + +impl SpirvVersion { + pub const V1_0: Self = Self::new(1, 0); + pub const V1_1: Self = Self::new(1, 1); + pub const V1_2: Self = Self::new(1, 2); + pub const V1_3: Self = Self::new(1, 3); + pub const V1_4: Self = Self::new(1, 4); + pub const V1_5: Self = Self::new(1, 5); + pub const V1_6: Self = Self::new(1, 6); + + #[inline] + pub const fn new(major: u8, minor: u8) -> Self { + Self { + version: Version::new(major, minor), + } + } +} + +impl From for SpirvVersion { + fn from(version: Version) -> Self { + Self { version } + } +} + +impl Deref for SpirvVersion { + type Target = Version; + + fn deref(&self) -> &Self::Target { + &self.version + } +} + +impl DerefMut for SpirvVersion { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.version + } +} + +/// A trait to describe common properties between different target variants +pub trait SpirvTargetVariant { + /// Validate the target version + fn validate(&self) -> Result<(), TargetError>; + /// Get the [`spirv_tools::TargetEnv`] to use for `spirv-val` and `spirv-opt`. May panic if version is invalid. + fn to_spirv_tools(&self) -> spirv_tools::TargetEnv; + /// Get the [`SpirvVersion`] of this target. May panic if version is invalid. + fn spirv_version(&self) -> SpirvVersion; +} + +/// A SPIR-V universal target +/// +/// This is different from [`SpirvVersion`] to prevent misuse! [`Self::spirv_version`] doesn't return a +/// [`UniversalTarget`] but a [`SpirvVersion`], which can't accidentally be passed onwards as a target. So you can't +/// accidentally turn a [`VulkanTarget`] target into a [`UniversalTarget`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct UniversalTarget { + pub version: Version, +} + +impl UniversalTarget { + pub const UNIVERSAL_1_0: Self = Self::new(Version(1, 0)); + pub const UNIVERSAL_1_1: Self = Self::new(Version(1, 1)); + pub const UNIVERSAL_1_2: Self = Self::new(Version(1, 2)); + pub const UNIVERSAL_1_3: Self = Self::new(Version(1, 3)); + pub const UNIVERSAL_1_4: Self = Self::new(Version(1, 4)); + pub const UNIVERSAL_1_5: Self = Self::new(Version(1, 5)); + pub const UNIVERSAL_1_6: Self = Self::new(Version(1, 6)); + pub const ALL_UNIVERSAL_TARGETS: &'static [Self] = &[ + Self::UNIVERSAL_1_0, + Self::UNIVERSAL_1_1, + Self::UNIVERSAL_1_2, + Self::UNIVERSAL_1_3, + Self::UNIVERSAL_1_4, + Self::UNIVERSAL_1_5, + Self::UNIVERSAL_1_6, + ]; + + pub const fn new(version: Version) -> Self { + Self { version } + } + + pub const fn properties(self) -> Result { + Ok(match self.version { + Version(1, 0) => spirv_tools::TargetEnv::Universal_1_0, + Version(1, 1) => spirv_tools::TargetEnv::Universal_1_1, + Version(1, 2) => spirv_tools::TargetEnv::Universal_1_2, + Version(1, 3) => spirv_tools::TargetEnv::Universal_1_3, + Version(1, 4) => spirv_tools::TargetEnv::Universal_1_4, + Version(1, 5) => spirv_tools::TargetEnv::Universal_1_5, + Version(1, 6) => spirv_tools::TargetEnv::Universal_1_6, + _ => { + return Err(TargetError::InvalidTargetVersion(SpirvTarget::Universal( + self, + ))); + } + }) + } +} + +impl SpirvTargetVariant for UniversalTarget { + fn validate(&self) -> Result<(), TargetError> { + self.properties()?; + Ok(()) + } + + fn to_spirv_tools(&self) -> spirv_tools::TargetEnv { + self.properties().unwrap() + } + + fn spirv_version(&self) -> SpirvVersion { + SpirvVersion::from(self.version) + } +} + +impl UniversalTarget { + fn parse_unbounded(s: &str) -> Option<(Self, &str)> { + let s = s.strip_prefix("spv")?; + let (version, s) = Version::parse_unbounded(s)?; + Some((Self::new(version), s)) + } +} + +impl FromStr for UniversalTarget { + type Err = TargetError; + + fn from_str(s: &str) -> Result { + from_str_to_parse_unbounded(s, Self::parse_unbounded) + } +} + +impl Display for UniversalTarget { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "spv{}", self.version) + } +} + +/// A Vulkan target +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct VulkanTarget { + pub version: Version, + /// optional, may specify a spv version + pub spv_version: Option, +} + +impl VulkanTarget { + pub const VULKAN_1_0: Self = Self::new(Version(1, 0)); + pub const VULKAN_1_1: Self = Self::new(Version(1, 1)); + pub const VULKAN_1_1_SPV_1_4: Self = Self { + version: Version(1, 1), + spv_version: Some(Version(1, 4)), + }; + pub const VULKAN_1_2: Self = Self::new(Version(1, 2)); + pub const VULKAN_1_3: Self = Self::new(Version(1, 3)); + pub const VULKAN_1_4: Self = Self::new(Version(1, 4)); + pub const ALL_VULKAN_TARGETS: &'static [Self] = &[ + Self::VULKAN_1_0, + Self::VULKAN_1_1, + Self::VULKAN_1_1_SPV_1_4, + Self::VULKAN_1_2, + Self::VULKAN_1_3, + Self::VULKAN_1_4, + ]; + + pub const fn new(version: Version) -> Self { + Self { + version, + spv_version: None, + } + } + + pub const fn properties(self) -> Result<(SpirvVersion, spirv_tools::TargetEnv), TargetError> { + let err = Err(TargetError::InvalidTargetVersion(SpirvTarget::Vulkan(self))); + Ok(match (self.version, self.spv_version) { + (Version(1, 0), None) => (SpirvVersion::new(1, 0), spirv_tools::TargetEnv::Vulkan_1_0), + (Version(1, 1), None) => (SpirvVersion::new(1, 3), spirv_tools::TargetEnv::Vulkan_1_1), + (Version(1, 1), Some(Version(1, 4))) => ( + SpirvVersion::new(1, 4), + spirv_tools::TargetEnv::Vulkan_1_1_Spirv_1_4, + ), + (Version(1, 2), None) => (SpirvVersion::new(1, 5), spirv_tools::TargetEnv::Vulkan_1_2), + (Version(1, 3), None) => (SpirvVersion::new(1, 6), spirv_tools::TargetEnv::Vulkan_1_3), + (Version(1, 4), None) => (SpirvVersion::new(1, 6), spirv_tools::TargetEnv::Vulkan_1_4), + _ => return err, + }) + } +} + +impl SpirvTargetVariant for VulkanTarget { + fn validate(&self) -> Result<(), TargetError> { + self.properties()?; + Ok(()) + } + + fn to_spirv_tools(&self) -> spirv_tools::TargetEnv { + self.properties().unwrap().1 + } + + fn spirv_version(&self) -> SpirvVersion { + self.properties().unwrap().0 + } +} + +impl VulkanTarget { + fn parse_unbounded(s: &str) -> Option<(Self, &str)> { + let s = s.strip_prefix("vulkan")?; + let (version, s) = Version::parse_unbounded(s)?; + let (spv_version, s) = if !s.is_empty() { + let s = s.strip_prefix("-spv")?; + let (spv_version, s) = Version::parse_unbounded(s)?; + (Some(spv_version), s) + } else { + (None, s) + }; + Some(( + Self { + version, + spv_version, + }, + s, + )) + } +} + +impl FromStr for VulkanTarget { + type Err = TargetError; + + fn from_str(s: &str) -> Result { + from_str_to_parse_unbounded(s, Self::parse_unbounded) + } +} + +impl Display for VulkanTarget { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "vulkan{}", self.version)?; + if let Some(spv_version) = self.spv_version { + write!(f, "-spv{spv_version}")?; + } + Ok(()) + } +} + +/// An OpenGL target +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct OpenGLTarget { + pub version: Version, +} + +impl OpenGLTarget { + pub const OPENGL_4_0: Self = Self::new(Version(4, 0)); + pub const OPENGL_4_1: Self = Self::new(Version(4, 1)); + pub const OPENGL_4_2: Self = Self::new(Version(4, 2)); + pub const OPENGL_4_3: Self = Self::new(Version(4, 3)); + pub const OPENGL_4_5: Self = Self::new(Version(4, 5)); + pub const ALL_OPENGL_TARGETS: &'static [Self] = &[ + Self::OPENGL_4_0, + Self::OPENGL_4_1, + Self::OPENGL_4_2, + Self::OPENGL_4_3, + Self::OPENGL_4_5, + ]; + + pub const fn new(version: Version) -> Self { + Self { version } + } + + pub const fn properties(self) -> Result { + Ok(match self.version { + Version(4, 0) => spirv_tools::TargetEnv::OpenGL_4_0, + Version(4, 1) => spirv_tools::TargetEnv::OpenGL_4_1, + Version(4, 2) => spirv_tools::TargetEnv::OpenGL_4_2, + Version(4, 3) => spirv_tools::TargetEnv::OpenGL_4_3, + Version(4, 5) => spirv_tools::TargetEnv::OpenGL_4_5, + _ => { + return Err(TargetError::InvalidTargetVersion(SpirvTarget::OpenGL(self))); + } + }) + } +} + +impl SpirvTargetVariant for OpenGLTarget { + fn validate(&self) -> Result<(), TargetError> { + self.properties()?; + Ok(()) + } + + fn to_spirv_tools(&self) -> spirv_tools::TargetEnv { + self.properties().unwrap() + } + + /// always 1.0 + fn spirv_version(&self) -> SpirvVersion { + SpirvVersion::new(1, 0) + } +} + +impl OpenGLTarget { + fn parse_unbounded(s: &str) -> Option<(Self, &str)> { + let s = s.strip_prefix("opengl")?; + let (version, s) = Version::parse_unbounded(s)?; + Some((Self { version }, s)) + } +} + +impl FromStr for OpenGLTarget { + type Err = TargetError; + + fn from_str(s: &str) -> Result { + from_str_to_parse_unbounded(s, Self::parse_unbounded) + } +} + +impl Display for OpenGLTarget { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "opengl{}", self.version) + } +} + +/// A rust-gpu target +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum SpirvTarget { + Universal(UniversalTarget), + Vulkan(VulkanTarget), + OpenGL(OpenGLTarget), +} + +impl SpirvTarget { + pub const UNIVERSAL_1_0: Self = Self::Universal(UniversalTarget::UNIVERSAL_1_0); + pub const UNIVERSAL_1_1: Self = Self::Universal(UniversalTarget::UNIVERSAL_1_1); + pub const UNIVERSAL_1_2: Self = Self::Universal(UniversalTarget::UNIVERSAL_1_2); + pub const UNIVERSAL_1_3: Self = Self::Universal(UniversalTarget::UNIVERSAL_1_3); + pub const UNIVERSAL_1_4: Self = Self::Universal(UniversalTarget::UNIVERSAL_1_4); + pub const UNIVERSAL_1_5: Self = Self::Universal(UniversalTarget::UNIVERSAL_1_5); + pub const UNIVERSAL_1_6: Self = Self::Universal(UniversalTarget::UNIVERSAL_1_6); + pub const VULKAN_1_0: Self = Self::Vulkan(VulkanTarget::VULKAN_1_0); + pub const VULKAN_1_1: Self = Self::Vulkan(VulkanTarget::VULKAN_1_1); + pub const VULKAN_1_1_SPV_1_4: Self = Self::Vulkan(VulkanTarget::VULKAN_1_1_SPV_1_4); + pub const VULKAN_1_2: Self = Self::Vulkan(VulkanTarget::VULKAN_1_2); + pub const VULKAN_1_3: Self = Self::Vulkan(VulkanTarget::VULKAN_1_3); + pub const VULKAN_1_4: Self = Self::Vulkan(VulkanTarget::VULKAN_1_4); + pub const OPENGL_4_0: Self = Self::OpenGL(OpenGLTarget::OPENGL_4_0); + pub const OPENGL_4_1: Self = Self::OpenGL(OpenGLTarget::OPENGL_4_1); + pub const OPENGL_4_2: Self = Self::OpenGL(OpenGLTarget::OPENGL_4_2); + pub const OPENGL_4_3: Self = Self::OpenGL(OpenGLTarget::OPENGL_4_3); + pub const OPENGL_4_5: Self = Self::OpenGL(OpenGLTarget::OPENGL_4_5); + + pub const fn memory_model(&self) -> MemoryModel { + match self { + SpirvTarget::Universal(_) => MemoryModel::Simple, + SpirvTarget::Vulkan(_) => MemoryModel::Vulkan, + SpirvTarget::OpenGL(_) => MemoryModel::GLSL450, + } + } +} + +impl SpirvTargetVariant for SpirvTarget { + fn validate(&self) -> Result<(), TargetError> { + match self { + SpirvTarget::Universal(t) => t.validate(), + SpirvTarget::Vulkan(t) => t.validate(), + SpirvTarget::OpenGL(t) => t.validate(), + } + } + + fn to_spirv_tools(&self) -> spirv_tools::TargetEnv { + match self { + SpirvTarget::Universal(t) => t.to_spirv_tools(), + SpirvTarget::Vulkan(t) => t.to_spirv_tools(), + SpirvTarget::OpenGL(t) => t.to_spirv_tools(), + } + } + + fn spirv_version(&self) -> SpirvVersion { + match self { + SpirvTarget::Universal(t) => t.spirv_version(), + SpirvTarget::Vulkan(t) => t.spirv_version(), + SpirvTarget::OpenGL(t) => t.spirv_version(), + } + } } 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, - - TargetEnv::Vulkan_1_0 - | TargetEnv::Vulkan_1_1 - | TargetEnv::WebGPU_0_DEPRECATED - | TargetEnv::Vulkan_1_1_Spirv_1_4 - | TargetEnv::Vulkan_1_2 - | TargetEnv::Vulkan_1_3 - | TargetEnv::Vulkan_1_4 => MemoryModel::Vulkan, - } - } - - pub fn spirv_version(&self) -> (u8, u8) { - self.env.spirv_version() - } - - fn init_target_opts(&self) -> TargetOptions { + pub fn parse_env(s: &str) -> Result { + let mut result; + result = UniversalTarget::from_str(s).map(Self::Universal); + if matches!(result, Err(TargetError::UnknownTarget(..))) { + result = VulkanTarget::from_str(s).map(Self::Vulkan); + } + if matches!(result, Err(TargetError::UnknownTarget(..))) { + result = OpenGLTarget::from_str(s).map(Self::OpenGL); + } + result + } + + pub fn parse_target(s: &str) -> Result { + let s = s + .strip_prefix(SPIRV_TARGET_PREFIX) + .ok_or_else(|| TargetError::UnknownTarget(s.to_owned()))?; + Self::parse_env(s) + } + + pub fn parse(s: &str) -> Result { + Self::parse_env(s.strip_prefix(SPIRV_TARGET_PREFIX).unwrap_or(s)) + } + + /// returns the target env, e.g. `vulkan1.3` + pub fn env(&self) -> String { + match self { + SpirvTarget::Universal(t) => t.to_string(), + SpirvTarget::Vulkan(t) => t.to_string(), + SpirvTarget::OpenGL(t) => t.to_string(), + } + } + + /// returns the full target, e.g. `spirv-unknown-vulkan1.3` + pub fn target(&self) -> String { + format!("{}{}", SPIRV_TARGET_PREFIX, self.env()) + } + + pub fn all_targets() -> impl Iterator { + UniversalTarget::ALL_UNIVERSAL_TARGETS + .iter() + .map(|t| Self::Universal(*t)) + .chain( + VulkanTarget::ALL_VULKAN_TARGETS + .iter() + .map(|t| Self::Vulkan(*t)), + ) + .chain( + OpenGLTarget::ALL_OPENGL_TARGETS + .iter() + .map(|t| Self::OpenGL(*t)), + ) + } +} + +impl SpirvTarget { + pub fn rustc_target(&self) -> Target { let mut o = TargetOptions::default(); o.simd_types_indirect = false; o.allows_weak_linkage = false; @@ -61,69 +571,66 @@ impl SpirvTarget { o.emit_debug_gdb_scripts = false; o.linker_flavor = LinkerFlavor::Unix(Cc::No); o.panic_strategy = PanicStrategy::Abort; - o.env = self.env.to_string().into(); - o.vendor = self.vendor.clone().into(); + o.env = self.env().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().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(), + arch: SPIRV_ARCH.into(), + options: o, } } } -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()); - } - - 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)?; +#[cfg(test)] +mod tests { + use super::*; - if iter.next().is_some() { - return Err(error()); - } - - let result = Self { env, vendor }; + #[test] + fn test_prefix_matches_arch_vendor() { + assert_eq!(SPIRV_TARGET_PREFIX, format!("{SPIRV_ARCH}-{SPIRV_VENDOR}-")); + } - if result.memory_model() == MemoryModel::OpenCL { - return Err(error()); + #[test] + fn test_str_roundtrip_target() { + for target in SpirvTarget::all_targets() { + assert_eq!(SpirvTarget::parse_target(&target.target()), Ok(target)); } - - 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) + #[test] + fn test_str_roundtrip_env() { + for target in SpirvTarget::all_targets() { + assert_eq!(SpirvTarget::parse_env(&target.env()), Ok(target)); + } } -} - -#[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) + #[test] + fn test_unknown_target() { + let result = SpirvTarget::parse_env("unknown"); + assert!( + matches!(result, Err(TargetError::UnknownTarget(..))), + "{result:?}", + ); + let result = SpirvTarget::parse_env("vulkan6.8"); + assert!( + matches!(result, Err(TargetError::InvalidTargetVersion(..))), + "{result:?}", + ); + let result = SpirvTarget::parse_env("vulkan1.4-spv1.0"); + assert!( + matches!(result, Err(TargetError::InvalidTargetVersion(..))), + "{result:?}", + ); + let result = SpirvTarget::parse_env("spv1.4-spv1.0"); + assert!( + matches!(result, Err(TargetError::UnknownTarget(..))), + "{result:?}", + ); } } From d637f7c0b718dd92357fe7849ed71f1660ed344b Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 17 Dec 2025 17:32:42 +0100 Subject: [PATCH 2/2] target declaration refactor: make `spirv-val` and `spirv-opt` use new target declaration --- crates/rustc_codegen_spirv/src/link.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/rustc_codegen_spirv/src/link.rs b/crates/rustc_codegen_spirv/src/link.rs index 038d307930..6f80acaf5f 100644 --- a/crates/rustc_codegen_spirv/src/link.rs +++ b/crates/rustc_codegen_spirv/src/link.rs @@ -2,6 +2,7 @@ use crate::maybe_pqp_cg_ssa as rustc_codegen_ssa; use crate::codegen_cx::{CodegenArgs, SpirvMetadata}; +use crate::target::{SpirvTarget, SpirvTargetVariant}; use crate::{SpirvCodegenBackend, SpirvModuleBuffer, linker}; use ar::{Archive, GnuBuilder, Header}; use rspirv::binary::Assemble; @@ -336,7 +337,8 @@ fn do_spirv_opt( opt::{self, Optimizer}, }; - let mut optimizer = opt::create(sess.target.options.env.parse().ok()); + let target = SpirvTarget::parse_env(&sess.target.options.env).unwrap(); + let mut optimizer = opt::create(Some(target.to_spirv_tools())); match sess.opts.optimize { OptLevel::No => {} @@ -398,7 +400,8 @@ fn do_spirv_val( ) { use spirv_tools::val::{self, Validator}; - let validator = val::create(sess.target.options.env.parse().ok()); + let target = SpirvTarget::parse_env(&sess.target.options.env).unwrap(); + let validator = val::create(Some(target.to_spirv_tools())); if let Err(e) = validator.validate(spv_binary, Some(options)) { let mut err = sess.dcx().struct_err(e.to_string());