/// Remove most unused objects from `module`, which must be valid. /// /// Always removes the following unused objects: /// - anonymous types, overrides, and constants /// - abstract-typed constants /// - expressions /// /// If `keep_unused` is `Yes`, the following are never considered unused, /// otherwise, they will also be removed if unused: /// - functions /// - global variables /// - named types and overrides /// /// The following are never removed: /// - named constants with a concrete type /// - special types /// - entry points /// - within an entry point or a used function: /// - arguments /// - local variables /// - named expressions /// /// After removing items according to the rules above, all handles in the /// remaining objects are adjusted as necessary. When `KeepUnused` is `Yes`, the /// resulting module should have all the named objects (except abstract-typed /// constants) present in the original, and those objects should be functionally /// identical. When `KeepUnused` is `No`, the resulting module should have the /// entry points present in the original, and those entry points should be /// functionally identical. /// /// # Panics /// /// If `module` would not pass validation, this may panic. pubfn compact(module: &mutcrate::Module, keep_unused: KeepUnused) { // The trickiest part of compaction is determining what is used and what is // not. Once we have computed that correctly, it's easy enough to call // `retain_mut` on each arena, drop unused elements, and fix up the handles // in what's left. // // For every compactable arena in a `Module`, whether global to the `Module` // or local to a function or entry point, the `ModuleTracer` type holds a // bitmap indicating which elements of that arena are used. Our task is to // populate those bitmaps correctly. // // First, we mark everything that is considered used by definition, as // described in this function's documentation. // // Since functions and entry points are considered used by definition, we // traverse their statement trees, and mark the referents of all handles // appearing in those statements as used. // // Once we've marked which elements of an arena are referred to directly by // handles elsewhere (for example, which of a function's expressions are // referred to by handles in its body statements), we can mark all the other // arena elements that are used indirectly in a single pass, traversing the // arena from back to front. Since Naga allows arena elements to refer only // to prior elements, we know that by the time we reach an element, all // other elements that could possibly refer to it have already been visited. // Thus, if the present element has not been marked as used, then it is // definitely unused, and compaction can remove it. Otherwise, the element // is used and must be retained, so we must mark everything it refers to. // // The final step is to mark the global expressions and types, which must be // traversed simultaneously; see `ModuleTracer::type_expression_tandem`'s // documentation for details. // // # A definition and a rule of thumb // // In this module, to "trace" something is to mark everything else it refers // to as used, on the assumption that the thing itself is used. For example, // to trace an `Expression` is to mark its subexpressions as used, as well // as any types, constants, overrides, etc. that it refers to. This is what // `ExpressionTracer::trace_expression` does. // // Given that we we want to visit each thing only once (to keep compaction // linear in the size of the module), this definition of "trace" implies // that things that are not "used by definition" must be marked as used // *before* we trace them. // // Thus, whenever you are marking something as used, it's a good idea to ask // yourself how you know that thing will be traced in the future. If you're // not sure, then you could be marking it too late to be noticed. The thing // itself will be retained by compaction, but since it will not be traced, // anything it refers to could be compacted away. letmut module_tracer = ModuleTracer::new(module);
// Observe what each entry point actually uses.
log::trace!("tracing entry points"); let entry_point_maps = module
.entry_points
.iter()
.map(|e| {
log::trace!("tracing entry point {:?}", e.function.name);
iflet Some(sizes) = e.workgroup_size_overrides { for size in sizes.iter().filter_map(|x| *x) {
module_tracer.global_expressions_used.insert(size);
}
}
iflet Some(task_payload) = e.task_payload {
module_tracer.global_variables_used.insert(task_payload);
} iflet Some(ref mesh_info) = e.mesh_info {
module_tracer
.global_variables_used
.insert(mesh_info.output_variable);
module_tracer
.types_used
.insert(mesh_info.vertex_output_type);
module_tracer
.types_used
.insert(mesh_info.primitive_output_type); iflet Some(max_vertices_override) = mesh_info.max_vertices_override {
module_tracer
.global_expressions_used
.insert(max_vertices_override);
} iflet Some(max_primitives_override) = mesh_info.max_primitives_override {
module_tracer
.global_expressions_used
.insert(max_primitives_override);
}
} if e.stage == crate::ShaderStage::Task || e.stage == crate::ShaderStage::Mesh { // Mesh shaders always need a u32 type, as it is e.g. the type of some // expressions. We tolerate its absence here because compaction is // infallible, but the module will fail validation. iflet Some(u32_type) = module.types.iter().find_map(|tuple| {
(tuple.1.inner == crate::TypeInner::Scalar(crate::Scalar::U32))
.then_some(tuple.0)
}) {
module_tracer.types_used.insert(u32_type);
}
}
letmut used = module_tracer.as_function(&e.function);
used.trace();
FunctionMap::from(used)
})
.collect::<Vec<_>>();
// Observe which types, constant expressions, constants, and expressions // each function uses, and produce maps for each function from // pre-compaction to post-compaction expression handles. // // The function tracing logic here works in conjunction with // `FunctionTracer::trace_call`, which, when tracing a `Statement::Call` // to a function not already identified as used, adds the called function // to both `functions_used` and `functions_pending`. // // Called functions are required to appear before their callers in the // functions arena (recursion is disallowed). We have already traced the // entry point(s) and added any functions called directly by the entry // point(s) to `functions_pending`. We proceed by repeatedly tracing the // last function in `functions_pending`. By an inductive argument, any // functions after the last function in `functions_pending` must be unused. // // When `KeepUnused` is active, we simply mark all functions as pending, // and then trace all of them.
log::trace!("tracing functions"); letmut function_maps = HandleMap::with_capacity(module.functions.len()); if keep_unused.into() {
module_tracer.functions_used.add_all();
module_tracer.functions_pending.add_all();
} whilelet Some(handle) = module_tracer.functions_pending.pop() { let function = &module.functions[handle];
log::trace!("tracing function {function:?}"); letmut function_tracer = module_tracer.as_function(function);
function_tracer.trace();
function_maps.insert(handle, FunctionMap::from(function_tracer));
}
// We treat all special types as used by definition.
log::trace!("tracing special types");
module_tracer.trace_special_types(&module.special_types);
log::trace!("tracing global variables"); if keep_unused.into() {
module_tracer.global_variables_used.add_all();
} for global in module_tracer.global_variables_used.iter() {
log::trace!("tracing global {:?}", module.global_variables[global].name);
module_tracer
.types_used
.insert(module.global_variables[global].ty); iflet Some(init) = module.global_variables[global].init {
module_tracer.global_expressions_used.insert(init);
}
}
// We treat all named constants as used by definition, unless they have an // abstract type as we do not want those reaching the validator.
log::trace!("tracing named constants"); for (handle, constant) in module.constants.iter() { if constant.name.is_none() || module.types[constant.ty].inner.is_abstract(&module.types) { continue;
}
if keep_unused.into() { // Treat all named overrides as used. for (handle, r#override) in module.overrides.iter() { if r#override.name.is_some() && module_tracer.overrides_used.insert(handle) {
module_tracer.types_used.insert(r#override.ty); iflet Some(init) = r#override.init {
module_tracer.global_expressions_used.insert(init);
}
}
}
// Treat all named types as used. for (handle, ty) in module.types.iter() { if ty.name.is_some() {
module_tracer.types_used.insert(handle);
}
}
}
module_tracer.type_expression_tandem();
// Now that we know what is used and what is never touched, // produce maps from the `Handle`s that appear in `module` now to // the corresponding `Handle`s that will refer to the same items // in the compacted module. let module_map = ModuleMap::from(module_tracer);
// Drop unused types from the type arena. // // `FastIndexSet`s don't have an underlying Vec<T> that we can // steal, compact in place, and then rebuild the `FastIndexSet` // from. So we have to rebuild the type arena from scratch.
log::trace!("compacting types"); letmut new_types = arena::UniqueArena::new(); for (old_handle, mut ty, span) in module.types.drain_all() { iflet Some(expected_new_handle) = module_map.types.try_adjust(old_handle) {
module_map.adjust_type(&mut ty); let actual_new_handle = new_types.insert(ty, span);
assert_eq!(actual_new_handle, expected_new_handle);
}
}
module.types = new_types;
log::trace!("adjusting special types");
module_map.adjust_special_types(&mut module.special_types);
// Temporary storage to help us reuse allocations of existing // named expression tables. letmut reused_named_expressions = crate::NamedExpressions::default();
// Drop unused functions. Compact and adjust used functions.
module.functions.retain_mut(|handle, function| { iflet Some(map) = function_maps.get(handle) {
log::trace!("retaining and compacting function {:?}", function.name);
map.compact(function, &module_map, &mut reused_named_expressions); true
} else {
log::trace!("dropping function {:?}", function.name); false
}
});
iflet Some(ray_desc) = *ray_desc { self.types_used.insert(ray_desc);
} iflet Some(ray_intersection) = *ray_intersection { self.types_used.insert(ray_intersection);
} iflet Some(ray_vertex_return) = *ray_vertex_return { self.types_used.insert(ray_vertex_return);
} // The `external_texture_params` type is generated purely as a // convenience to the backends. While it will never actually be used in // the IR, it must be marked as used so that it survives compaction. iflet Some(external_texture_params) = *external_texture_params { self.types_used.insert(external_texture_params);
} iflet Some(external_texture_transfer_function) = *external_texture_transfer_function { self.types_used.insert(external_texture_transfer_function);
} for (_, &handle) in predeclared_types { self.types_used.insert(handle);
}
}
/// Traverse types and global expressions in tandem to determine which are used. /// /// Assuming that all types and global expressions used by other parts of /// the module have been added to [`types_used`] and /// [`global_expressions_used`], expand those sets to include all types and /// global expressions reachable from those. /// /// [`types_used`]: ModuleTracer::types_used /// [`global_expressions_used`]: ModuleTracer::global_expressions_used fn type_expression_tandem(&mutself) { // For each type T, compute the latest global expression E that T and // its predecessors refer to. Given the ordering rules on types and // global expressions in valid modules, we can do this with a single // forward scan of the type arena. The rules further imply that T can // only be referred to by expressions after E. letmut max_dep = Vec::with_capacity(self.module.types.len()); letmut previous = None; for (_handle, ty) inself.module.types.iter() {
previous = core::cmp::max(
previous, match ty.inner { crate::TypeInner::Array { size, .. }
| crate::TypeInner::BindingArray { size, .. } => match size { crate::ArraySize::Constant(_) | crate::ArraySize::Dynamic => None, crate::ArraySize::Pending(handle) => self.module.overrides[handle].init,
},
_ => None,
},
);
max_dep.push(previous);
}
// Visit types and global expressions from youngest to oldest. // // The outer loop visits types. Before visiting each type, the inner // loop ensures that all global expressions that could possibly refer to // it have been visited. And since the inner loop stop at the latest // expression that the type could possibly refer to, we know that we // have previously visited any types that might refer to each expression // we visit. // // This lets us assume that any type or expression that is *not* marked // as used by the time we visit it is genuinely unused, and can be // ignored. letmut exprs = self.module.global_expressions.iter().rev().peekable();
/// Test mutual references between types and expressions via override /// lengths. #[test] fn array_length_override_mutual() { usecrate::Expression as Ex; usecrate::Scalar as Sc; usecrate::TypeInner as Ti;
// This type is only referred to by the override's init // expression, so if we visit that too early, this type will be // removed incorrectly. let ty_i32 = module.types.insert( crate::Type {
name: None,
inner: Ti::Scalar(Sc::I32),
},
nowhere,
);
// An override that the other override's init can refer to. let first_override = module.overrides.append( crate::Override {
name: None, // so it is not considered used by definition
id: Some(41),
ty: ty_i32,
init: None,
},
nowhere,
);
// Initializer expression for the override: // // (first_override + 0) as u32 // // The `first_override` makes it an override expression; the `0` // gets a use of `ty_i32` in there; and the `as` makes it match // the type of `second_override` without actually making // `second_override` point at `ty_i32` directly. let first_override_expr = module
.global_expressions
.append(Ex::Override(first_override), nowhere); let zero = module
.global_expressions
.append(Ex::ZeroValue(ty_i32), nowhere); let sum = module.global_expressions.append(
Ex::Binary {
op: crate::BinaryOperator::Add,
left: first_override_expr,
right: zero,
},
nowhere,
); let init = module.global_expressions.append(
Ex::As {
expr: sum,
kind: crate::ScalarKind::Uint,
convert: None,
},
nowhere,
);
// Override that serves as the array's length. let second_override = module.overrides.append( crate::Override {
name: None, // so it is not considered used by definition
id: Some(42),
ty: ty_u32,
init: Some(init),
},
nowhere,
);
// Array type that uses the overload as its length. // Since this is named, it is considered used by definition. let _ty_array = module.types.insert( crate::Type {
name: Some("delicious_array".to_string()),
inner: Ti::Array {
base: ty_u32,
size: crate::ArraySize::Pending(second_override),
stride: 4,
},
},
nowhere,
);
// This will only be retained if we trace the initializers // of overrides referred to by `Expression::Override` // in global expressions. let expr1 = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(1)), crate::Span::default(),
);
// This will only be traced via a global `Expression::Override`. let o = module.overrides.append( crate::Override {
name: None,
id: Some(42),
ty: ty_u32,
init: Some(expr1),
}, crate::Span::default(),
);
// This is retained by _p. let expr2 = module
.global_expressions
.append(crate::Expression::Override(o), crate::Span::default());
// Since this is named, it will be retained. let _p = module.overrides.append( crate::Override {
name: Some("p".to_string()),
id: None,
ty: ty_u32,
init: Some(expr2),
}, crate::Span::default(),
);
// This will only be retained if we trace the initializers // of overrides referred to by `Expression::Override` in a function. let expr1 = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(1)), crate::Span::default(),
);
// This will be removed by compaction. let _unused_override = module.overrides.append( crate::Override {
name: None,
id: Some(41),
ty: ty_u32,
init: None,
}, crate::Span::default(),
);
// This will only be traced via an `Expression::Override` in a function. let o = module.overrides.append( crate::Override {
name: None,
id: Some(42),
ty: ty_u32,
init: Some(expr1),
}, crate::Span::default(),
);
// This is used by the `Return` statement. let o_expr = fun
.expressions
.append(crate::Expression::Override(o), crate::Span::default());
fun.body.push( crate::Statement::Return {
value: Some(o_expr),
}, crate::Span::default(),
);
// This type is used only by the unnamed constant. let ty_u32 = module.types.insert( crate::Type {
name: None,
inner: crate::TypeInner::Scalar(crate::Scalar::U32),
},
nowhere,
);
// This type is used by the named constant. let ty_vec_u32 = module.types.insert( crate::Type {
name: None,
inner: crate::TypeInner::Vector {
size: crate::VectorSize::Bi,
scalar: crate::Scalar::U32,
},
},
nowhere,
);
let unnamed_init = module
.global_expressions
.append(crate::Expression::Literal(crate::Literal::U32(0)), nowhere);
// The named constant is initialized using a Splat expression, to // give the named constant a type distinct from the unnamed // constant's. let unnamed_constant_expr = module
.global_expressions
.append(crate::Expression::Constant(unnamed_constant), nowhere); let named_init = module.global_expressions.append( crate::Expression::Splat {
size: crate::VectorSize::Bi,
value: unnamed_constant_expr,
},
nowhere,
);
// This type is used only by the unnamed override. let ty_u32 = module.types.insert( crate::Type {
name: None,
inner: crate::TypeInner::Scalar(crate::Scalar::U32),
},
nowhere,
);
// This type is used by the named override. let ty_i32 = module.types.insert( crate::Type {
name: None,
inner: crate::TypeInner::Scalar(crate::Scalar::I32),
},
nowhere,
);
let unnamed_init = module
.global_expressions
.append(crate::Expression::Literal(crate::Literal::U32(0)), nowhere);
// The named override is initialized using a Splat expression, to // give the named override a type distinct from the unnamed // override's. let unnamed_override_expr = module
.global_expressions
.append(crate::Expression::Override(unnamed_override), nowhere); let named_init = module.global_expressions.append( crate::Expression::As {
expr: unnamed_override_expr,
kind: crate::ScalarKind::Sint,
convert: None,
},
nowhere,
);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.