Skip to content
This repository was archived by the owner on Jan 29, 2025. It is now read-only.

Commit 8b26721

Browse files
jimblandyteoxoy
authored andcommitted
Implement module compaction.
Add a new Naga feature, `"compact"`, which adds a new function `naga::compact::compact`, which removes unused expressions, types, and constants from a `Module`.
1 parent 0b7afb3 commit 8b26721

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+10076
-6182
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ wgsl-out = []
3838
hlsl-out = []
3939
span = ["codespan-reporting", "termcolor"]
4040
validate = []
41+
compact = []
4142

4243
[[bench]]
4344
name = "criterion"

cli/Cargo.toml

+20-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,28 @@ keywords = ["shader", "SPIR-V", "GLSL", "MSL"]
1010
license = "MIT OR Apache-2.0"
1111

1212
[dependencies]
13-
naga = { version = "0.13", path = "../", features = ["validate", "span", "wgsl-in", "wgsl-out", "glsl-in", "glsl-out", "spv-in", "spv-out", "msl-out", "hlsl-out", "dot-out", "serialize", "deserialize"] }
1413
bincode = "1"
1514
log = "0.4"
1615
codespan-reporting = "0.11"
1716
env_logger = "0.10"
1817
argh = "0.1.5"
18+
19+
[dependencies.naga]
20+
version = "0.13"
21+
path = "../"
22+
features = [
23+
"validate",
24+
"compact",
25+
"span",
26+
"wgsl-in",
27+
"wgsl-out",
28+
"glsl-in",
29+
"glsl-out",
30+
"spv-in",
31+
"spv-out",
32+
"msl-out",
33+
"hlsl-out",
34+
"dot-out",
35+
"serialize",
36+
"deserialize"
37+
]

cli/src/bin/naga.rs

+56-2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ struct Args {
7979
#[argh(switch, short = 'g')]
8080
generate_debug_symbols: bool,
8181

82+
/// compact the module's IR and revalidate.
83+
///
84+
/// Output files will reflect the compacted IR. If you want to see the IR as
85+
/// it was before compaction, use the `--before-compaction` option.
86+
#[argh(switch)]
87+
compact: bool,
88+
89+
/// write the module's IR before compaction to the given file.
90+
///
91+
/// This implies `--compact`. Like any other output file, the filename
92+
/// extension determines the form in which the module is written.
93+
#[argh(option)]
94+
before_compaction: Option<String>,
95+
8296
/// show version
8397
#[argh(switch)]
8498
version: bool,
@@ -288,7 +302,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
288302
!params.keep_coordinate_space,
289303
);
290304

291-
let (module, input_text) = match Path::new(&input_path)
305+
let (mut module, input_text) = match Path::new(&input_path)
292306
.extension()
293307
.ok_or(CliError("Input filename has no extension"))?
294308
.to_str()
@@ -391,12 +405,13 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
391405
caps & !missing
392406
});
393407

394-
// validate the IR
408+
// Validate the IR before compaction.
395409
let info = match naga::valid::Validator::new(params.validation_flags, validation_caps)
396410
.validate(&module)
397411
{
398412
Ok(info) => Some(info),
399413
Err(error) => {
414+
// Validation failure is not fatal. Just report the error.
400415
if let Some(input) = &input_text {
401416
let filename = input_path.file_name().and_then(std::ffi::OsStr::to_str);
402417
emit_annotated_error(&error, filename.unwrap_or("input"), input);
@@ -406,6 +421,45 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
406421
}
407422
};
408423

424+
// Compact the module, if requested.
425+
let info = if args.compact || args.before_compaction.is_some() {
426+
// Compact only if validation succeeded. Otherwise, compaction may panic.
427+
if info.is_some() {
428+
// Write out the module state before compaction, if requested.
429+
if let Some(ref before_compaction) = args.before_compaction {
430+
write_output(&module, &info, &params, before_compaction)?;
431+
}
432+
433+
naga::compact::compact(&mut module);
434+
435+
// Re-validate the IR after compaction.
436+
match naga::valid::Validator::new(params.validation_flags, validation_caps)
437+
.validate(&module)
438+
{
439+
Ok(info) => Some(info),
440+
Err(error) => {
441+
// Validation failure is not fatal. Just report the error.
442+
eprintln!("Error validating compacted module:");
443+
if let Some(input) = &input_text {
444+
let filename = input_path.file_name().and_then(std::ffi::OsStr::to_str);
445+
emit_annotated_error(&error, filename.unwrap_or("input"), input);
446+
}
447+
print_err(&error);
448+
None
449+
}
450+
}
451+
} else {
452+
eprintln!("Skipping compaction due to validation failure.");
453+
None
454+
}
455+
} else {
456+
info
457+
};
458+
459+
// If no output was requested, then report validation results and stop here.
460+
//
461+
// If the user asked for output, don't stop: some output formats (".txt",
462+
// ".dot", ".bin") can be generated even without a `ModuleInfo`.
409463
if output_paths.is_empty() {
410464
if info.is_some() {
411465
println!("Validation successful");

src/arena.rs

+70
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,31 @@ impl<T> Iterator for Range<T> {
192192
}
193193

194194
impl<T> Range<T> {
195+
/// Return a range enclosing handles `first` through `last`, inclusive.
195196
pub fn new_from_bounds(first: Handle<T>, last: Handle<T>) -> Self {
196197
Self {
197198
inner: (first.index() as u32)..(last.index() as u32 + 1),
198199
marker: Default::default(),
199200
}
200201
}
202+
203+
/// Return the first and last handles included in `self`.
204+
///
205+
/// If `self` is an empty range, there are no handles included, so
206+
/// return `None`.
207+
pub fn first_and_last(&self) -> Option<(Handle<T>, Handle<T>)> {
208+
if self.inner.start < self.inner.end {
209+
Some((
210+
// `Range::new_from_bounds` expects a 1-based, start- and
211+
// end-inclusive range, but `self.inner` is a zero-based,
212+
// end-exclusive range.
213+
Handle::new(Index::new(self.inner.start + 1).unwrap()),
214+
Handle::new(Index::new(self.inner.end).unwrap()),
215+
))
216+
} else {
217+
None
218+
}
219+
}
201220
}
202221

203222
/// An arena holding some kind of component (e.g., type, constant,
@@ -382,6 +401,19 @@ impl<T> Arena<T> {
382401
Ok(())
383402
}
384403
}
404+
405+
#[cfg(feature = "compact")]
406+
pub(crate) fn retain_mut<P>(&mut self, mut predicate: P)
407+
where
408+
P: FnMut(Handle<T>, &mut T) -> bool,
409+
{
410+
let mut index = 0;
411+
self.data.retain_mut(|elt| {
412+
index += 1;
413+
let handle = Handle::new(Index::new(index).unwrap());
414+
predicate(handle, elt)
415+
})
416+
}
385417
}
386418

387419
#[cfg(feature = "deserialize")]
@@ -544,6 +576,44 @@ impl<T> UniqueArena<T> {
544576
Span::default()
545577
}
546578
}
579+
580+
#[cfg(feature = "compact")]
581+
pub(crate) fn drain_all(&mut self) -> UniqueArenaDrain<T> {
582+
UniqueArenaDrain {
583+
inner_elts: self.set.drain(..),
584+
#[cfg(feature = "span")]
585+
inner_spans: self.span_info.drain(..),
586+
index: Index::new(1).unwrap(),
587+
}
588+
}
589+
}
590+
591+
#[cfg(feature = "compact")]
592+
pub(crate) struct UniqueArenaDrain<'a, T> {
593+
inner_elts: indexmap::set::Drain<'a, T>,
594+
#[cfg(feature = "span")]
595+
inner_spans: std::vec::Drain<'a, Span>,
596+
index: Index,
597+
}
598+
599+
#[cfg(feature = "compact")]
600+
impl<'a, T> Iterator for UniqueArenaDrain<'a, T> {
601+
type Item = (Handle<T>, T, Span);
602+
603+
fn next(&mut self) -> Option<Self::Item> {
604+
match self.inner_elts.next() {
605+
Some(elt) => {
606+
let handle = Handle::new(self.index);
607+
self.index = self.index.checked_add(1).unwrap();
608+
#[cfg(feature = "span")]
609+
let span = self.inner_spans.next().unwrap();
610+
#[cfg(not(feature = "span"))]
611+
let span = Span::default();
612+
Some((handle, elt, span))
613+
}
614+
None => None,
615+
}
616+
}
547617
}
548618

549619
impl<T: Eq + hash::Hash> UniqueArena<T> {

0 commit comments

Comments
 (0)