Skip to content

Commit 1c8d769

Browse files
authored
[naga] Introduce KeywordSet and CaseInsensitiveKeywordSet. (#8136)
1 parent ca3f7f8 commit 1c8d769

File tree

13 files changed

+213
-125
lines changed

13 files changed

+213
-125
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ This allows using precompiled shaders without manually checking which backend's
106106
- Naga now requires that no type be larger than 1 GB. This limit may be lowered in the future; feedback on an appropriate value for the limit is welcome. By @andyleiserson in [#7950](https://github.com/gfx-rs/wgpu/pull/7950).
107107
- If the shader source contains control characters, Naga now replaces them with U+FFFD ("replacement character") in diagnostic output. By @andyleiserson in [#8049](https://github.com/gfx-rs/wgpu/pull/8049).
108108
- Add f16 IO polyfill on Vulkan backend to enable SHADER_F16 use without requiring `storageInputOutput16`. By @cryvosh in [#7884](https://github.com/gfx-rs/wgpu/pull/7884).
109+
- For custom Naga backend authors: `naga::proc::Namer` now accepts reserved keywords using two new dedicated types, `proc::{KeywordSet, CaseInsensitiveKeywordSet}`. By @kpreid in [#8136](https://github.com/gfx-rs/wgpu/pull/8136).
109110

110111
#### DX12
111112

naga/src/back/glsl/keywords.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
use crate::proc::KeywordSet;
12
use crate::racy_lock::RacyLock;
23

3-
use hashbrown::HashSet;
4-
54
pub const RESERVED_KEYWORDS: &[&str] = &[
65
//
76
// GLSL 4.6 keywords, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L2004-L2322
@@ -499,11 +498,5 @@ pub const RESERVED_KEYWORDS: &[&str] = &[
499498
/// significant time during [`Namer::reset`](crate::proc::Namer::reset).
500499
///
501500
/// See <https://github.com/gfx-rs/wgpu/pull/7338> for benchmarks.
502-
pub static RESERVED_KEYWORD_SET: RacyLock<HashSet<&'static str>> = RacyLock::new(|| {
503-
let mut set = HashSet::default();
504-
set.reserve(RESERVED_KEYWORDS.len());
505-
for &word in RESERVED_KEYWORDS {
506-
set.insert(word);
507-
}
508-
set
509-
});
501+
pub static RESERVED_KEYWORD_SET: RacyLock<KeywordSet> =
502+
RacyLock::new(|| KeywordSet::from_iter(RESERVED_KEYWORDS));

naga/src/back/glsl/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ impl<'a, W: Write> Writer<'a, W> {
663663
namer.reset(
664664
module,
665665
&keywords::RESERVED_KEYWORD_SET,
666-
&[],
666+
proc::CaseInsensitiveKeywordSet::empty(),
667667
&[
668668
"gl_", // all GL built-in variables
669669
"_group", // all normal bindings

naga/src/back/hlsl/keywords.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
use crate::proc::{CaseInsensitiveKeywordSet, KeywordSet};
12
use crate::racy_lock::RacyLock;
23

3-
use hashbrown::HashSet;
4-
54
// When compiling with FXC without strict mode, these keywords are actually case insensitive.
65
// If you compile with strict mode and specify a different casing like "Pass" instead in an identifier, FXC will give this error:
76
// "error X3086: alternate cases for 'pass' are deprecated in strict mode"
@@ -927,17 +926,11 @@ pub const TYPES: &[&str] = &{
927926
/// significant time during [`Namer::reset`](crate::proc::Namer::reset).
928927
///
929928
/// See <https://github.com/gfx-rs/wgpu/pull/7338> for benchmarks.
930-
pub static RESERVED_SET: RacyLock<HashSet<&'static str>> = RacyLock::new(|| {
931-
let mut set = HashSet::default();
932-
set.reserve(RESERVED.len() + TYPES.len());
933-
for &word in RESERVED {
934-
set.insert(word);
935-
}
936-
for &word in TYPES {
937-
set.insert(word);
938-
}
939-
set
940-
});
929+
pub static RESERVED_SET: RacyLock<KeywordSet> =
930+
RacyLock::new(|| KeywordSet::from_iter(RESERVED.iter().chain(TYPES)));
931+
932+
pub static RESERVED_CASE_INSENSITIVE_SET: RacyLock<CaseInsensitiveKeywordSet> =
933+
RacyLock::new(|| CaseInsensitiveKeywordSet::from_iter(RESERVED_CASE_INSENSITIVE));
941934

942935
pub const RESERVED_PREFIXES: &[&str] = &[
943936
"__dynamic_buffer_offsets",

naga/src/back/hlsl/writer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
155155
self.namer.reset(
156156
module,
157157
&super::keywords::RESERVED_SET,
158-
super::keywords::RESERVED_CASE_INSENSITIVE,
158+
&super::keywords::RESERVED_CASE_INSENSITIVE_SET,
159159
super::keywords::RESERVED_PREFIXES,
160160
&mut self.names,
161161
);

naga/src/back/msl/keywords.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
use crate::proc::KeywordSet;
12
use crate::racy_lock::RacyLock;
23

3-
use hashbrown::HashSet;
4-
54
// MSLS - Metal Shading Language Specification:
65
// https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
76
//
@@ -364,11 +363,4 @@ pub const RESERVED: &[&str] = &[
364363
/// significant time during [`Namer::reset`](crate::proc::Namer::reset).
365364
///
366365
/// See <https://github.com/gfx-rs/wgpu/pull/7338> for benchmarks.
367-
pub static RESERVED_SET: RacyLock<HashSet<&'static str>> = RacyLock::new(|| {
368-
let mut set = HashSet::default();
369-
set.reserve(RESERVED.len());
370-
for &word in RESERVED {
371-
set.insert(word);
372-
}
373-
set
374-
});
366+
pub static RESERVED_SET: RacyLock<KeywordSet> = RacyLock::new(|| KeywordSet::from_iter(RESERVED));

naga/src/back/msl/writer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4279,7 +4279,7 @@ impl<W: Write> Writer<W> {
42794279
self.namer.reset(
42804280
module,
42814281
&super::keywords::RESERVED_SET,
4282-
&[],
4282+
proc::CaseInsensitiveKeywordSet::empty(),
42834283
&[CLAMPED_LOD_LOAD_PREFIX],
42844284
&mut self.names,
42854285
);

naga/src/back/wgsl/writer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl<W: Write> Writer<W> {
101101
module,
102102
&crate::keywords::wgsl::RESERVED_SET,
103103
// an identifier must not start with two underscore
104-
&[],
104+
proc::CaseInsensitiveKeywordSet::empty(),
105105
&["__", "_naga"],
106106
&mut self.names,
107107
);

naga/src/keywords/wgsl.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ Keywords for [WGSL][wgsl] (WebGPU Shading Language).
44
[wgsl]: https://gpuweb.github.io/gpuweb/wgsl.html
55
*/
66

7+
use crate::proc::KeywordSet;
78
use crate::racy_lock::RacyLock;
89

9-
use hashbrown::HashSet;
10-
1110
// https://gpuweb.github.io/gpuweb/wgsl/#keyword-summary
1211
// last sync: https://github.com/gpuweb/gpuweb/blob/39f2321f547c8f0b7f473cf1d47fba30b1691303/wgsl/index.bs
1312
pub const RESERVED: &[&str] = &[
@@ -238,11 +237,4 @@ pub const RESERVED: &[&str] = &[
238237
/// significant time during [`Namer::reset`](crate::proc::Namer::reset).
239238
///
240239
/// See <https://github.com/gfx-rs/wgpu/pull/7338> for benchmarks.
241-
pub static RESERVED_SET: RacyLock<HashSet<&'static str>> = RacyLock::new(|| {
242-
let mut set = HashSet::default();
243-
set.reserve(RESERVED.len());
244-
for &word in RESERVED {
245-
set.insert(word);
246-
}
247-
set
248-
});
240+
pub static RESERVED_SET: RacyLock<KeywordSet> = RacyLock::new(|| KeywordSet::from_iter(RESERVED));

naga/src/proc/keyword_set.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use core::{fmt, hash};
2+
3+
use crate::racy_lock::RacyLock;
4+
use crate::FastHashSet;
5+
6+
/// A case-sensitive set of strings,
7+
/// for use with [`Namer`][crate::proc::Namer] to avoid collisions with keywords and other reserved
8+
/// identifiers.
9+
///
10+
/// This is currently implemented as a hash table.
11+
/// Future versions of Naga may change the implementation based on speed and code size
12+
/// considerations.
13+
#[derive(Clone, Debug, Default, Eq, PartialEq)]
14+
pub struct KeywordSet(FastHashSet<&'static str>);
15+
16+
impl KeywordSet {
17+
/// Returns a new mutable empty set.
18+
pub fn new() -> Self {
19+
Self::default()
20+
}
21+
22+
/// Returns a reference to the empty set.
23+
pub fn empty() -> &'static Self {
24+
static EMPTY: RacyLock<KeywordSet> = RacyLock::new(Default::default);
25+
&EMPTY
26+
}
27+
28+
/// Returns whether the set contains the given string.
29+
#[inline]
30+
pub fn contains(&self, identifier: &str) -> bool {
31+
self.0.contains(identifier)
32+
}
33+
}
34+
35+
impl Default for &'static KeywordSet {
36+
fn default() -> Self {
37+
KeywordSet::empty()
38+
}
39+
}
40+
41+
impl FromIterator<&'static str> for KeywordSet {
42+
fn from_iter<T: IntoIterator<Item = &'static str>>(iter: T) -> Self {
43+
Self(iter.into_iter().collect())
44+
}
45+
}
46+
47+
/// Accepts double references so that `KeywordSet::from_iter(&["foo"])` works.
48+
impl<'a> FromIterator<&'a &'static str> for KeywordSet {
49+
fn from_iter<T: IntoIterator<Item = &'a &'static str>>(iter: T) -> Self {
50+
Self::from_iter(iter.into_iter().copied())
51+
}
52+
}
53+
54+
impl Extend<&'static str> for KeywordSet {
55+
#[expect(
56+
clippy::useless_conversion,
57+
reason = "doing .into_iter() sooner reduces distinct monomorphizations"
58+
)]
59+
fn extend<T: IntoIterator<Item = &'static str>>(&mut self, iter: T) {
60+
self.0.extend(iter.into_iter())
61+
}
62+
}
63+
64+
/// Accepts double references so that `.extend(&["foo"])` works.
65+
impl<'a> Extend<&'a &'static str> for KeywordSet {
66+
fn extend<T: IntoIterator<Item = &'a &'static str>>(&mut self, iter: T) {
67+
self.extend(iter.into_iter().copied())
68+
}
69+
}
70+
71+
/// A case-insensitive, ASCII-only set of strings,
72+
/// for use with [`Namer`][crate::proc::Namer] to avoid collisions with keywords and other reserved
73+
/// identifiers.
74+
///
75+
/// This is currently implemented as a hash table.
76+
/// Future versions of Naga may change the implementation based on speed and code size
77+
/// considerations.
78+
#[derive(Clone, Debug, Default, Eq, PartialEq)]
79+
pub struct CaseInsensitiveKeywordSet(FastHashSet<AsciiUniCase<&'static str>>);
80+
81+
impl CaseInsensitiveKeywordSet {
82+
/// Returns a new mutable empty set.
83+
pub fn new() -> Self {
84+
Self::default()
85+
}
86+
87+
/// Returns a reference to the empty set.
88+
pub fn empty() -> &'static Self {
89+
static EMPTY: RacyLock<CaseInsensitiveKeywordSet> = RacyLock::new(Default::default);
90+
&EMPTY
91+
}
92+
93+
/// Returns whether the set contains the given string, with comparison
94+
/// by [`str::eq_ignore_ascii_case()`].
95+
#[inline]
96+
pub fn contains(&self, identifier: &str) -> bool {
97+
self.0.contains(&AsciiUniCase(identifier))
98+
}
99+
}
100+
101+
impl Default for &'static CaseInsensitiveKeywordSet {
102+
fn default() -> Self {
103+
CaseInsensitiveKeywordSet::empty()
104+
}
105+
}
106+
107+
impl FromIterator<&'static str> for CaseInsensitiveKeywordSet {
108+
fn from_iter<T: IntoIterator<Item = &'static str>>(iter: T) -> Self {
109+
Self(
110+
iter.into_iter()
111+
.inspect(debug_assert_ascii)
112+
.map(AsciiUniCase)
113+
.collect(),
114+
)
115+
}
116+
}
117+
118+
/// Accepts double references so that `CaseInsensitiveKeywordSet::from_iter(&["foo"])` works.
119+
impl<'a> FromIterator<&'a &'static str> for CaseInsensitiveKeywordSet {
120+
fn from_iter<T: IntoIterator<Item = &'a &'static str>>(iter: T) -> Self {
121+
Self::from_iter(iter.into_iter().copied())
122+
}
123+
}
124+
125+
impl Extend<&'static str> for CaseInsensitiveKeywordSet {
126+
fn extend<T: IntoIterator<Item = &'static str>>(&mut self, iter: T) {
127+
self.0.extend(
128+
iter.into_iter()
129+
.inspect(debug_assert_ascii)
130+
.map(AsciiUniCase),
131+
)
132+
}
133+
}
134+
135+
/// Accepts double references so that `.extend(&["foo"])` works.
136+
impl<'a> Extend<&'a &'static str> for CaseInsensitiveKeywordSet {
137+
fn extend<T: IntoIterator<Item = &'a &'static str>>(&mut self, iter: T) {
138+
self.extend(iter.into_iter().copied())
139+
}
140+
}
141+
142+
/// A string wrapper type with an ascii case insensitive Eq and Hash impl
143+
#[derive(Clone, Copy)]
144+
struct AsciiUniCase<S: AsRef<str> + ?Sized>(S);
145+
146+
impl<S: ?Sized + AsRef<str>> fmt::Debug for AsciiUniCase<S> {
147+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148+
self.0.as_ref().fmt(f)
149+
}
150+
}
151+
152+
impl<S: AsRef<str>> PartialEq<Self> for AsciiUniCase<S> {
153+
#[inline]
154+
fn eq(&self, other: &Self) -> bool {
155+
self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
156+
}
157+
}
158+
159+
impl<S: AsRef<str>> Eq for AsciiUniCase<S> {}
160+
161+
impl<S: AsRef<str>> hash::Hash for AsciiUniCase<S> {
162+
#[inline]
163+
fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
164+
for byte in self
165+
.0
166+
.as_ref()
167+
.as_bytes()
168+
.iter()
169+
.map(|b| b.to_ascii_lowercase())
170+
{
171+
hasher.write_u8(byte);
172+
}
173+
}
174+
}
175+
176+
fn debug_assert_ascii(s: &&'static str) {
177+
debug_assert!(s.is_ascii(), "{s:?} not ASCII")
178+
}

0 commit comments

Comments
 (0)