summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/named.rs234
1 files changed, 181 insertions, 53 deletions
diff --git a/src/ir/named.rs b/src/ir/named.rs
index e434a58d..3c676662 100644
--- a/src/ir/named.rs
+++ b/src/ir/named.rs
@@ -76,11 +76,50 @@
//! fixed-point.
//!
//! We use the "monotone framework" for this fix-point analysis where our
-//! lattice is the powerset of the template parameters that appear in the input
-//! C++ header, our join function is set union, and we use the
-//! `ir::traversal::Trace` trait to implement the work-list optimization so we
-//! don't have to revisit every node in the graph when for every iteration
-//! towards the fix-point.
+//! lattice is the mapping from each IR item to the powerset of the template
+//! parameters that appear in the input C++ header, our join function is set
+//! union, and we use the `ir::traversal::Trace` trait to implement the
+//! work-list optimization so we don't have to revisit every node in the graph
+//! when for every iteration towards the fix-point.
+//!
+//! A lattice is a set with a partial ordering between elements, where there is
+//! a single least upper bound and a single greatest least bound for every
+//! subset. We are dealing with finite lattices, which means that it has a
+//! finite number of elements, and it follows that there exists a single top and
+//! a single bottom member of the lattice. For example, the power set of a
+//! finite set forms a finite lattice where partial ordering is defined by set
+//! inclusion, that is `a <= b` if `a` is a subset of `b`. Here is the finite
+//! lattice constructed from the set {0,1,2}:
+//!
+//! ```text
+//! .----- Top = {0,1,2} -----.
+//! / | \
+//! / | \
+//! / | \
+//! {0,1} -------. {0,2} .--------- {1,2}
+//! | \ / \ / |
+//! | / \ |
+//! | / \ / \ |
+//! {0} --------' {1} `---------- {2}
+//! \ | /
+//! \ | /
+//! \ | /
+//! `------ Bottom = {} ------'
+//! ```
+//!
+//! A monotone function `f` is a function where if `x <= y`, then it holds that
+//! `f(x) <= f(y)`. It should be clear that running a monotone function to a
+//! fix-point on a finite lattice will always terminate: `f` can only "move"
+//! along the lattice in a single direction, and therefore can only either find
+//! a fix-point in the middle of the lattice or continue to the top or bottom
+//! depending if it is ascending or descending the lattice respectively.
+//!
+//! For our analysis, we are collecting the set of template parameters used by
+//! any given IR node. The set of template parameters appearing in the program
+//! is finite. Our lattice is their powerset. We start at the bottom element,
+//! the empty set. Our analysis only adds members to the set of used template
+//! parameters, never removes them, so it is monotone, and therefore iteration
+//! to a fix-point will terminate.
//!
//! For a deeper introduction to the general form of this kind of analysis, see
//! [Static Program Analysis by Anders Møller and Michael I. Schwartzbach][spa].
@@ -173,38 +212,119 @@ pub fn analyze<Analysis>(extra: Analysis::Extra) -> Analysis::Output
analysis.into()
}
-/// An analysis that finds the set of template parameters that actually end up
-/// used in our generated bindings.
+/// An analysis that finds for each IR item its set of template parameters that
+/// it uses.
+///
+/// We use the following monotone constraint function:
+///
+/// ```ignore
+/// template_param_usage(v) =
+/// self_template_param_usage(v) union
+/// template_param_usage(w_0) union
+/// template_param_usage(w_1) union
+/// ...
+/// template_param_usage(w_n)
+/// ```
+///
+/// Where `v` has direct edges in the IR graph to each of `w_0`, `w_1`,
+/// ..., `w_n` (for example, if `v` were a struct type and `x` and `y`
+/// were the types of two of `v`'s fields). We ignore certain edges, such
+/// as edges from a template declaration to its template parameters'
+/// definitions for this analysis. If we didn't, then we would mistakenly
+/// determine that ever template parameter is always used.
+///
+/// Finally, `self_template_param_usage` is defined with the following cases:
+///
+/// * If `T` is a template parameter:
+///
+/// ```ignore
+/// self_template_param_usage(T) = { T }
+/// ```
+///
+/// * If `inst` is a template instantiation, `inst.args` are the template
+/// instantiation's template arguments, and `inst.decl` is the template
+/// declaration being instantiated:
+///
+/// ```ignore
+/// self_template_param_usage(inst) =
+/// { T: for T in inst.args, if T in template_param_usage(inst.decl) }
+/// ```
+///
+/// * And for all other IR items, the result is the empty set:
+///
+/// ```ignore
+/// self_template_param_usage(_) = { }
+/// ```
#[derive(Debug, Clone)]
pub struct UsedTemplateParameters<'a> {
ctx: &'a BindgenContext<'a>,
- used: ItemSet,
+ used: HashMap<ItemId, ItemSet>,
dependencies: HashMap<ItemId, Vec<ItemId>>,
}
+impl<'a> UsedTemplateParameters<'a> {
+ fn consider_edge(kind: EdgeKind) -> bool {
+ match kind {
+ // For each of these kinds of edges, if the referent uses a template
+ // parameter, then it should be considered that the origin of the
+ // edge also uses the template parameter.
+ EdgeKind::TemplateArgument |
+ EdgeKind::BaseMember |
+ EdgeKind::Field |
+ EdgeKind::InnerType |
+ EdgeKind::InnerVar |
+ EdgeKind::Constructor |
+ EdgeKind::VarType |
+ EdgeKind::TypeReference => true,
+
+ // We can't emit machine code for new instantiations of function
+ // templates and class templates' methods (and don't detect explicit
+ // instantiations) so we must ignore template parameters that are
+ // only used by functions.
+ EdgeKind::Method |
+ EdgeKind::FunctionReturn |
+ EdgeKind::FunctionParameter => false,
+
+ // If we considered these edges, we would end up mistakenly claiming
+ // that every template parameter always used.
+ EdgeKind::TemplateDeclaration |
+ EdgeKind::TemplateParameterDefinition => false,
+
+ // Since we have to be careful about which edges we consider for
+ // this analysis to be correct, we ignore generic edges. We also
+ // avoid a `_` wild card to force authors of new edge kinds to
+ // determine whether they need to be considered by this analysis.
+ EdgeKind::Generic => false,
+ }
+ }
+}
+
impl<'a> MonotoneFramework for UsedTemplateParameters<'a> {
type Node = ItemId;
type Extra = &'a BindgenContext<'a>;
- type Output = ItemSet;
+ type Output = HashMap<ItemId, ItemSet>;
fn new(ctx: &'a BindgenContext<'a>) -> UsedTemplateParameters<'a> {
+ let mut used = HashMap::new();
let mut dependencies = HashMap::new();
for item in ctx.whitelisted_items() {
+ dependencies.entry(item).or_insert(vec![]);
+ used.insert(item, ItemSet::new());
+
{
// We reverse our natural IR graph edges to find dependencies
// between nodes.
- let mut add_reverse_edge = |sub_item, _| {
+ item.trace(ctx, &mut |sub_item, _| {
dependencies.entry(sub_item).or_insert(vec![]).push(item);
- };
- item.trace(ctx, &mut add_reverse_edge, &());
+ }, &());
}
// Additionally, whether a template instantiation's template
// arguments are used depends on whether the template declaration's
// generic template parameters are used.
- ctx.resolve_item_fallible(item)
- .and_then(|item| item.as_type())
+ ctx.resolve_item(item)
+ .as_type()
.map(|ty| match ty.kind() {
&TypeKind::TemplateInstantiation(decl, ref args) => {
let decl = ctx.resolve_type(decl);
@@ -222,57 +342,65 @@ impl<'a> MonotoneFramework for UsedTemplateParameters<'a> {
UsedTemplateParameters {
ctx: ctx,
- used: ItemSet::new(),
+ used: used,
dependencies: dependencies,
}
}
- fn initial_worklist(&self) -> Vec<Self::Node> {
+ fn initial_worklist(&self) -> Vec<ItemId> {
self.ctx.whitelisted_items().collect()
}
- fn constrain(&mut self, item: ItemId) -> bool {
- let original_size = self.used.len();
+ fn constrain(&mut self, id: ItemId) -> bool {
+ let original_len = self.used[&id].len();
- item.trace(self.ctx, &mut |item, edge_kind| {
- if edge_kind == EdgeKind::TemplateParameterDefinition {
- // The definition of a template parameter is not considered a
- // use of said template parameter. Ignore this edge.
- return;
+ // First, add this item's self template parameter usage.
+ let item = self.ctx.resolve_item(id);
+ let ty_kind = item.as_type().map(|ty| ty.kind());
+ match ty_kind {
+ Some(&TypeKind::Named) => {
+ // This is a trivial use of the template type parameter.
+ self.used.get_mut(&id).unwrap().insert(id);
}
-
- let ty_kind = self.ctx.resolve_item(item)
- .as_type()
- .map(|ty| ty.kind());
-
- match ty_kind {
- Some(&TypeKind::Named) => {
- // This is a "trivial" use of the template type parameter.
- self.used.insert(item);
- },
- Some(&TypeKind::TemplateInstantiation(decl, ref args)) => {
- // A template instantiation's concrete template argument is
- // only used if the template declaration uses the
- // corresponding template parameter.
- let decl = self.ctx.resolve_type(decl);
- let params = decl.self_template_params(self.ctx)
- .expect("a template instantiation's referenced \
- template declaration should have template \
- parameters");
- for (arg, param) in args.iter().zip(params.iter()) {
- if self.used.contains(param) {
- if self.ctx.resolve_item(*arg).is_named() {
- self.used.insert(*arg);
- }
+ Some(&TypeKind::TemplateInstantiation(decl, ref args)) => {
+ // A template instantiation's concrete template argument is
+ // only used if the template declaration uses the
+ // corresponding template parameter.
+ let params = decl.self_template_params(self.ctx)
+ .expect("a template instantiation's referenced \
+ template declaration should have template \
+ parameters");
+ for (arg, param) in args.iter().zip(params.iter()) {
+ if self.used[&decl].contains(param) {
+ if self.ctx.resolve_item(*arg).is_named() {
+ self.used.get_mut(&id).unwrap().insert(*arg);
}
}
- },
- _ => return,
+ }
}
+ _ => {}
+ }
+
+ // Second, add the union of each of its referent item's template
+ // parameter usage.
+ item.trace(self.ctx, &mut |sub_id, edge_kind| {
+ if sub_id == id || !Self::consider_edge(edge_kind) {
+ return;
+ }
+
+ // This clone is unfortunate because we are potentially thrashing
+ // malloc. We could investigate replacing the ItemSet values with
+ // Rc<RefCell<ItemSet>> to make the borrow checker happy, but it
+ // isn't clear that the added indirection wouldn't outweigh the cost
+ // of malloc'ing a new ItemSet here. Ideally, `HashMap` would have a
+ // `split_entries` method analogous to `slice::split_at_mut`...
+ let to_add = self.used[&sub_id].clone();
+ self.used.get_mut(&id).unwrap().extend(to_add);
}, &());
- let new_size = self.used.len();
- new_size != original_size
+ let new_len = self.used[&id].len();
+ assert!(new_len >= original_len);
+ new_len != original_len
}
fn each_depending_on<F>(&self, item: ItemId, mut f: F)
@@ -286,8 +414,8 @@ impl<'a> MonotoneFramework for UsedTemplateParameters<'a> {
}
}
-impl<'a> From<UsedTemplateParameters<'a>> for ItemSet {
- fn from(used_templ_params: UsedTemplateParameters) -> ItemSet {
+impl<'a> From<UsedTemplateParameters<'a>> for HashMap<ItemId, ItemSet> {
+ fn from(used_templ_params: UsedTemplateParameters) -> Self {
used_templ_params.used
}
}