Skip to content

Gotta land some of this stuff... #539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 23, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 181 additions & 53 deletions src/ir/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1000 for the comment/code ratio, this is awesome :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

//! 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].
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe you should be able to use the self.used[&id].insert(*arg) syntax here too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was originally like that, but for some reason type inference was inferring an immutable indexing operation, rather than a mutable one.

/me shrugs

}
}
},
_ => 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`...
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fixing this unnecessary malloc thrashing in a follow up, before we turn this analysis on by default.

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I'd be really scared if this didn't hold, but better safe than sorry.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, just a sanity check, more for future modifications than for the code as it is now.

new_len != original_len
}

fn each_depending_on<F>(&self, item: ItemId, mut f: F)
Expand All @@ -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
}
}
Expand Down