naga/compact/
mod.rs

1mod expressions;
2mod functions;
3mod handle_set_map;
4mod statements;
5mod types;
6
7use crate::arena::HandleSet;
8use crate::{arena, compact::functions::FunctionTracer};
9use handle_set_map::HandleMap;
10
11/// Remove unused types, expressions, and constants from `module`.
12///
13/// Assuming that all globals, named constants, special types,
14/// functions and entry points in `module` are used, determine which
15/// types, constants, and expressions (both function-local and global
16/// constant expressions) are actually used, and remove the rest,
17/// adjusting all handles as necessary. The result should be a module
18/// functionally identical to the original.
19///
20/// This may be useful to apply to modules generated in the snapshot
21/// tests. Our backends often generate temporary names based on handle
22/// indices, which means that adding or removing unused arena entries
23/// can affect the output even though they have no semantic effect.
24/// Such meaningless changes add noise to snapshot diffs, making
25/// accurate patch review difficult. Compacting the modules before
26/// generating snapshots makes the output independent of unused arena
27/// entries.
28///
29/// # Panics
30///
31/// If `module` has not passed validation, this may panic.
32pub fn compact(module: &mut crate::Module) {
33    let mut module_tracer = ModuleTracer::new(module);
34
35    // We treat all globals as used by definition.
36    log::trace!("tracing global variables");
37    {
38        for (_, global) in module.global_variables.iter() {
39            log::trace!("tracing global {:?}", global.name);
40            module_tracer.types_used.insert(global.ty);
41            if let Some(init) = global.init {
42                module_tracer.global_expressions_used.insert(init);
43            }
44        }
45    }
46
47    // We treat all special types as used by definition.
48    module_tracer.trace_special_types(&module.special_types);
49
50    // We treat all named constants as used by definition.
51    for (handle, constant) in module.constants.iter() {
52        if constant.name.is_some() {
53            module_tracer.constants_used.insert(handle);
54            module_tracer.global_expressions_used.insert(constant.init);
55        }
56    }
57
58    // We treat all overrides as used by definition.
59    for (_, override_) in module.overrides.iter() {
60        module_tracer.types_used.insert(override_.ty);
61        if let Some(init) = override_.init {
62            module_tracer.global_expressions_used.insert(init);
63        }
64    }
65
66    // We assume that all functions are used.
67    //
68    // Observe which types, constant expressions, constants, and
69    // expressions each function uses, and produce maps for each
70    // function from pre-compaction to post-compaction expression
71    // handles.
72    log::trace!("tracing functions");
73    let function_maps: Vec<FunctionMap> = module
74        .functions
75        .iter()
76        .map(|(_, f)| {
77            log::trace!("tracing function {:?}", f.name);
78            let mut function_tracer = module_tracer.as_function(f);
79            function_tracer.trace();
80            FunctionMap::from(function_tracer)
81        })
82        .collect();
83
84    // Similarly, observe what each entry point actually uses.
85    log::trace!("tracing entry points");
86    let entry_point_maps: Vec<FunctionMap> = module
87        .entry_points
88        .iter()
89        .map(|e| {
90            log::trace!("tracing entry point {:?}", e.function.name);
91            let mut used = module_tracer.as_function(&e.function);
92            used.trace();
93            FunctionMap::from(used)
94        })
95        .collect();
96
97    // Given that the above steps have marked all the constant
98    // expressions used directly by globals, constants, functions, and
99    // entry points, walk the constant expression arena to find all
100    // constant expressions used, directly or indirectly.
101    module_tracer.as_const_expression().trace_expressions();
102
103    // Constants' initializers are taken care of already, because
104    // expression tracing sees through constants. But we still need to
105    // note type usage.
106    for (handle, constant) in module.constants.iter() {
107        if module_tracer.constants_used.contains(handle) {
108            module_tracer.types_used.insert(constant.ty);
109        }
110    }
111
112    // Treat all named types as used.
113    for (handle, ty) in module.types.iter() {
114        log::trace!("tracing type {:?}, name {:?}", handle, ty.name);
115        if ty.name.is_some() {
116            module_tracer.types_used.insert(handle);
117        }
118    }
119
120    // Propagate usage through types.
121    module_tracer.as_type().trace_types();
122
123    // Now that we know what is used and what is never touched,
124    // produce maps from the `Handle`s that appear in `module` now to
125    // the corresponding `Handle`s that will refer to the same items
126    // in the compacted module.
127    let module_map = ModuleMap::from(module_tracer);
128
129    // Drop unused types from the type arena.
130    //
131    // `FastIndexSet`s don't have an underlying Vec<T> that we can
132    // steal, compact in place, and then rebuild the `FastIndexSet`
133    // from. So we have to rebuild the type arena from scratch.
134    log::trace!("compacting types");
135    let mut new_types = arena::UniqueArena::new();
136    for (old_handle, mut ty, span) in module.types.drain_all() {
137        if let Some(expected_new_handle) = module_map.types.try_adjust(old_handle) {
138            module_map.adjust_type(&mut ty);
139            let actual_new_handle = new_types.insert(ty, span);
140            assert_eq!(actual_new_handle, expected_new_handle);
141        }
142    }
143    module.types = new_types;
144    log::trace!("adjusting special types");
145    module_map.adjust_special_types(&mut module.special_types);
146
147    // Drop unused constant expressions, reusing existing storage.
148    log::trace!("adjusting constant expressions");
149    module.global_expressions.retain_mut(|handle, expr| {
150        if module_map.global_expressions.used(handle) {
151            module_map.adjust_expression(expr, &module_map.global_expressions);
152            true
153        } else {
154            false
155        }
156    });
157
158    // Drop unused constants in place, reusing existing storage.
159    log::trace!("adjusting constants");
160    module.constants.retain_mut(|handle, constant| {
161        if module_map.constants.used(handle) {
162            module_map.types.adjust(&mut constant.ty);
163            module_map.global_expressions.adjust(&mut constant.init);
164            true
165        } else {
166            false
167        }
168    });
169
170    // Adjust override types and initializers.
171    log::trace!("adjusting overrides");
172    for (_, override_) in module.overrides.iter_mut() {
173        module_map.types.adjust(&mut override_.ty);
174        if let Some(init) = override_.init.as_mut() {
175            module_map.global_expressions.adjust(init);
176        }
177    }
178
179    // Adjust global variables' types and initializers.
180    log::trace!("adjusting global variables");
181    for (_, global) in module.global_variables.iter_mut() {
182        log::trace!("adjusting global {:?}", global.name);
183        module_map.types.adjust(&mut global.ty);
184        if let Some(ref mut init) = global.init {
185            module_map.global_expressions.adjust(init);
186        }
187    }
188
189    // Temporary storage to help us reuse allocations of existing
190    // named expression tables.
191    let mut reused_named_expressions = crate::NamedExpressions::default();
192
193    // Compact each function.
194    for ((_, function), map) in module.functions.iter_mut().zip(function_maps.iter()) {
195        log::trace!("compacting function {:?}", function.name);
196        map.compact(function, &module_map, &mut reused_named_expressions);
197    }
198
199    // Compact each entry point.
200    for (entry, map) in module.entry_points.iter_mut().zip(entry_point_maps.iter()) {
201        log::trace!("compacting entry point {:?}", entry.function.name);
202        map.compact(
203            &mut entry.function,
204            &module_map,
205            &mut reused_named_expressions,
206        );
207    }
208}
209
210struct ModuleTracer<'module> {
211    module: &'module crate::Module,
212    types_used: HandleSet<crate::Type>,
213    constants_used: HandleSet<crate::Constant>,
214    global_expressions_used: HandleSet<crate::Expression>,
215}
216
217impl<'module> ModuleTracer<'module> {
218    fn new(module: &'module crate::Module) -> Self {
219        Self {
220            module,
221            types_used: HandleSet::for_arena(&module.types),
222            constants_used: HandleSet::for_arena(&module.constants),
223            global_expressions_used: HandleSet::for_arena(&module.global_expressions),
224        }
225    }
226
227    fn trace_special_types(&mut self, special_types: &crate::SpecialTypes) {
228        let crate::SpecialTypes {
229            ref ray_desc,
230            ref ray_intersection,
231            ref predeclared_types,
232        } = *special_types;
233
234        if let Some(ray_desc) = *ray_desc {
235            self.types_used.insert(ray_desc);
236        }
237        if let Some(ray_intersection) = *ray_intersection {
238            self.types_used.insert(ray_intersection);
239        }
240        for (_, &handle) in predeclared_types {
241            self.types_used.insert(handle);
242        }
243    }
244
245    fn as_type(&mut self) -> types::TypeTracer {
246        types::TypeTracer {
247            types: &self.module.types,
248            types_used: &mut self.types_used,
249        }
250    }
251
252    fn as_const_expression(&mut self) -> expressions::ExpressionTracer {
253        expressions::ExpressionTracer {
254            expressions: &self.module.global_expressions,
255            constants: &self.module.constants,
256            types_used: &mut self.types_used,
257            constants_used: &mut self.constants_used,
258            expressions_used: &mut self.global_expressions_used,
259            global_expressions_used: None,
260        }
261    }
262
263    pub fn as_function<'tracer>(
264        &'tracer mut self,
265        function: &'tracer crate::Function,
266    ) -> FunctionTracer<'tracer> {
267        FunctionTracer {
268            function,
269            constants: &self.module.constants,
270            types_used: &mut self.types_used,
271            constants_used: &mut self.constants_used,
272            global_expressions_used: &mut self.global_expressions_used,
273            expressions_used: HandleSet::for_arena(&function.expressions),
274        }
275    }
276}
277
278struct ModuleMap {
279    types: HandleMap<crate::Type>,
280    constants: HandleMap<crate::Constant>,
281    global_expressions: HandleMap<crate::Expression>,
282}
283
284impl From<ModuleTracer<'_>> for ModuleMap {
285    fn from(used: ModuleTracer) -> Self {
286        ModuleMap {
287            types: HandleMap::from_set(used.types_used),
288            constants: HandleMap::from_set(used.constants_used),
289            global_expressions: HandleMap::from_set(used.global_expressions_used),
290        }
291    }
292}
293
294impl ModuleMap {
295    fn adjust_special_types(&self, special: &mut crate::SpecialTypes) {
296        let crate::SpecialTypes {
297            ref mut ray_desc,
298            ref mut ray_intersection,
299            ref mut predeclared_types,
300        } = *special;
301
302        if let Some(ref mut ray_desc) = *ray_desc {
303            self.types.adjust(ray_desc);
304        }
305        if let Some(ref mut ray_intersection) = *ray_intersection {
306            self.types.adjust(ray_intersection);
307        }
308
309        for handle in predeclared_types.values_mut() {
310            self.types.adjust(handle);
311        }
312    }
313}
314
315struct FunctionMap {
316    expressions: HandleMap<crate::Expression>,
317}
318
319impl From<FunctionTracer<'_>> for FunctionMap {
320    fn from(used: FunctionTracer) -> Self {
321        FunctionMap {
322            expressions: HandleMap::from_set(used.expressions_used),
323        }
324    }
325}