From 544ccf88daeede7614f8cbae7126496f04cbac68 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 31 May 2023 15:00:23 +0200 Subject: [PATCH] Handle case insensitive FXC HLSL keywords. (#2347) There are a few keywords like "pass" in HLSL that are actually case-insensitive for FXC. This can be disabled with strict mode, but even if you do that FXC will continue to give an error if you try to use them in identifiers (at all, with any casing). This changes the namer code to escape these keywords even if the casing is different. If you're wondering where I got the list from: I looked at the list of strings in D3DCompiler_47.dll. --- src/back/glsl/mod.rs | 8 ++++- src/back/hlsl/keywords.rs | 22 ++++++++---- src/back/hlsl/writer.rs | 9 +++-- src/back/msl/writer.rs | 2 +- src/back/wgsl/writer.rs | 1 + src/proc/namer.rs | 48 ++++++++++++++++++++++++- tests/in/hlsl-keyword.wgsl | 6 ++++ tests/out/hlsl/hlsl-keyword.hlsl | 9 +++++ tests/out/hlsl/hlsl-keyword.hlsl.config | 3 ++ tests/snapshots.rs | 1 + 10 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 tests/in/hlsl-keyword.wgsl create mode 100644 tests/out/hlsl/hlsl-keyword.hlsl create mode 100644 tests/out/hlsl/hlsl-keyword.hlsl.config diff --git a/src/back/glsl/mod.rs b/src/back/glsl/mod.rs index d6aff795c9..653826ae41 100644 --- a/src/back/glsl/mod.rs +++ b/src/back/glsl/mod.rs @@ -484,7 +484,13 @@ impl<'a, W: Write> Writer<'a, W> { // Generate a map with names required to write the module let mut names = crate::FastHashMap::default(); let mut namer = proc::Namer::default(); - namer.reset(module, keywords::RESERVED_KEYWORDS, &["gl_"], &mut names); + namer.reset( + module, + keywords::RESERVED_KEYWORDS, + &[], + &["gl_"], + &mut names, + ); // Build the instance let mut this = Self { diff --git a/src/back/hlsl/keywords.rs b/src/back/hlsl/keywords.rs index 7519b767a1..459db245ee 100644 --- a/src/back/hlsl/keywords.rs +++ b/src/back/hlsl/keywords.rs @@ -4,9 +4,23 @@ HLSL Reserved Words - */ +// When compiling with FXC without strict mode, these keywords are actually case insensitive. +// If you compile with strict mode and specify a different casing like "Pass" instead in an identifier, FXC will give this error: +// "error X3086: alternate cases for 'pass' are deprecated in strict mode" +// This behavior is not documented anywhere, but as far as I can tell this is the full list. +pub const RESERVED_CASE_INSENSITIVE: &[&str] = &[ + "asm", + "decl", + "pass", + "technique", + "Texture1D", + "Texture2D", + "Texture3D", + "TextureCube", +]; + pub const RESERVED: &[&str] = &[ "AppendStructuredBuffer", - "asm", "asm_fragment", "BlendState", "bool", @@ -68,7 +82,6 @@ pub const RESERVED: &[&str] = &[ "out", "OutputPatch", "packoffset", - "pass", "pixelfragment", "PixelShader", "point", @@ -101,18 +114,13 @@ pub const RESERVED: &[&str] = &[ "switch", "StructuredBuffer", "tbuffer", - "technique", "technique10", "technique11", "texture", - "Texture1D", "Texture1DArray", - "Texture2D", "Texture2DArray", "Texture2DMS", "Texture2DMSArray", - "Texture3D", - "TextureCube", "TextureCubeArray", "true", "typedef", diff --git a/src/back/hlsl/writer.rs b/src/back/hlsl/writer.rs index 0b08dbe0a1..940f0ae373 100644 --- a/src/back/hlsl/writer.rs +++ b/src/back/hlsl/writer.rs @@ -89,8 +89,13 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { fn reset(&mut self, module: &Module) { self.names.clear(); - self.namer - .reset(module, super::keywords::RESERVED, &[], &mut self.names); + self.namer.reset( + module, + super::keywords::RESERVED, + super::keywords::RESERVED_CASE_INSENSITIVE, + &[], + &mut self.names, + ); self.entry_point_io.clear(); self.named_expressions.clear(); self.wrapped.clear(); diff --git a/src/back/msl/writer.rs b/src/back/msl/writer.rs index 07a79f7497..cfef6645b6 100644 --- a/src/back/msl/writer.rs +++ b/src/back/msl/writer.rs @@ -3033,7 +3033,7 @@ impl Writer { ) -> Result { self.names.clear(); self.namer - .reset(module, super::keywords::RESERVED, &[], &mut self.names); + .reset(module, super::keywords::RESERVED, &[], &[], &mut self.names); self.struct_member_pads.clear(); writeln!( diff --git a/src/back/wgsl/writer.rs b/src/back/wgsl/writer.rs index da9f605fe3..d26ceda487 100644 --- a/src/back/wgsl/writer.rs +++ b/src/back/wgsl/writer.rs @@ -86,6 +86,7 @@ impl Writer { module, crate::keywords::wgsl::RESERVED, // an identifier must not start with two underscore + &[], &["__"], &mut self.names, ); diff --git a/src/proc/namer.rs b/src/proc/namer.rs index 053126b8ac..8227b50d89 100644 --- a/src/proc/namer.rs +++ b/src/proc/namer.rs @@ -1,5 +1,6 @@ use crate::{arena::Handle, FastHashMap, FastHashSet}; use std::borrow::Cow; +use std::hash::{Hash, Hasher}; pub type EntryPointIndex = u16; const SEPARATOR: char = '_'; @@ -25,6 +26,7 @@ pub struct Namer { /// The last numeric suffix used for each base name. Zero means "no suffix". unique: FastHashMap, keywords: FastHashSet, + keywords_case_insensitive: FastHashSet>, reserved_prefixes: Vec, } @@ -102,7 +104,12 @@ impl Namer { } None => { let mut suffixed = base.to_string(); - if base.ends_with(char::is_numeric) || self.keywords.contains(base.as_ref()) { + if base.ends_with(char::is_numeric) + || self.keywords.contains(base.as_ref()) + || self + .keywords_case_insensitive + .contains(&AsciiUniCase(base.as_ref())) + { suffixed.push(SEPARATOR); } debug_assert!(!self.keywords.contains(&suffixed)); @@ -137,6 +144,7 @@ impl Namer { &mut self, module: &crate::Module, reserved_keywords: &[&str], + reserved_keywords_case_insensitive: &[&'static str], reserved_prefixes: &[&str], output: &mut FastHashMap, ) { @@ -148,6 +156,17 @@ impl Namer { self.keywords.clear(); self.keywords .extend(reserved_keywords.iter().map(|string| (string.to_string()))); + + debug_assert!(reserved_keywords_case_insensitive + .iter() + .all(|s| s.is_ascii())); + self.keywords_case_insensitive.clear(); + self.keywords_case_insensitive.extend( + reserved_keywords_case_insensitive + .iter() + .map(|string| (AsciiUniCase(*string))), + ); + let mut temp = String::new(); for (ty_handle, ty) in module.types.iter() { @@ -252,6 +271,33 @@ impl Namer { } } +/// A string wrapper type with an ascii case insensitive Eq and Hash impl +struct AsciiUniCase + ?Sized>(S); + +impl> PartialEq for AsciiUniCase { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref()) + } +} + +impl> Eq for AsciiUniCase {} + +impl> Hash for AsciiUniCase { + #[inline] + fn hash(&self, hasher: &mut H) { + for byte in self + .0 + .as_ref() + .as_bytes() + .iter() + .map(|b| b.to_ascii_lowercase()) + { + hasher.write_u8(byte); + } + } +} + #[test] fn test() { let mut namer = Namer::default(); diff --git a/tests/in/hlsl-keyword.wgsl b/tests/in/hlsl-keyword.wgsl new file mode 100644 index 0000000000..e2e4e722d2 --- /dev/null +++ b/tests/in/hlsl-keyword.wgsl @@ -0,0 +1,6 @@ +@fragment +fn fs_main() -> @location(0) vec4f { + // Make sure case-insensitive keywords are escaped in HLSL. + var Pass = vec4(1.0,1.0,1.0,1.0); + return Pass; +} \ No newline at end of file diff --git a/tests/out/hlsl/hlsl-keyword.hlsl b/tests/out/hlsl/hlsl-keyword.hlsl new file mode 100644 index 0000000000..efdeb9960f --- /dev/null +++ b/tests/out/hlsl/hlsl-keyword.hlsl @@ -0,0 +1,9 @@ + +float4 fs_main() : SV_Target0 +{ + float4 Pass_ = (float4)0; + + Pass_ = float4(1.0, 1.0, 1.0, 1.0); + float4 _expr6 = Pass_; + return _expr6; +} diff --git a/tests/out/hlsl/hlsl-keyword.hlsl.config b/tests/out/hlsl/hlsl-keyword.hlsl.config new file mode 100644 index 0000000000..b9f047558a --- /dev/null +++ b/tests/out/hlsl/hlsl-keyword.hlsl.config @@ -0,0 +1,3 @@ +vertex=() +fragment=(fs_main:ps_5_1 ) +compute=() diff --git a/tests/snapshots.rs b/tests/snapshots.rs index 7a0d4333db..8f6fa3a8f7 100644 --- a/tests/snapshots.rs +++ b/tests/snapshots.rs @@ -584,6 +584,7 @@ fn convert_wgsl() { ("force_point_size_vertex_shader_webgl", Targets::GLSL), ("invariant", Targets::GLSL), ("ray-query", Targets::SPIRV | Targets::METAL), + ("hlsl-keyword", Targets::HLSL), ]; for &(name, targets) in inputs.iter() {