Skip to content

has vtable analysis #850

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 1 commit into from
Jul 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 5 additions & 5 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1473,7 +1473,7 @@ impl CodeGenerator for CompInfo {
// the parent too.
let mut fields = vec![];
let mut struct_layout = StructLayoutTracker::new(ctx, self, &canonical_name);
if self.needs_explicit_vtable(ctx) {
if self.needs_explicit_vtable(ctx, item) {
let vtable =
Vtable::new(item.id(), self.methods(), self.base_members());
vtable.codegen(ctx, result, item);
Expand Down Expand Up @@ -1504,7 +1504,7 @@ impl CodeGenerator for CompInfo {
// NB: We won't include unsized types in our base chain because they
// would contribute to our size given the dummy field we insert for
// unsized types.
if base_ty.is_unsized(ctx) {
if base_ty.is_unsized(ctx, &base.ty) {
continue;
}

Expand Down Expand Up @@ -1583,7 +1583,7 @@ impl CodeGenerator for CompInfo {
warn!("Opaque type without layout! Expect dragons!");
}
}
} else if !is_union && !self.is_unsized(ctx) {
} else if !is_union && !self.is_unsized(ctx, &item.id()) {
if let Some(padding_field) =
layout.and_then(|layout| {
struct_layout.pad_struct(layout)
Expand All @@ -1607,7 +1607,7 @@ impl CodeGenerator for CompInfo {
//
// NOTE: This check is conveniently here to avoid the dummy fields we
// may add for unused template parameters.
if self.is_unsized(ctx) {
if self.is_unsized(ctx, &item.id()) {
let has_address = if is_opaque {
// Generate the address field if it's an opaque type and
// couldn't determine the layout of the blob.
Expand Down Expand Up @@ -1707,7 +1707,7 @@ impl CodeGenerator for CompInfo {
let too_many_base_vtables = self.base_members()
.iter()
.filter(|base| {
ctx.resolve_type(base.ty).has_vtable(ctx)
ctx.lookup_item_id_has_vtable(&base.ty)
})
.count() > 1;

Expand Down
186 changes: 186 additions & 0 deletions src/ir/analysis/has_vtable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//! Determining which types has vtable
use super::{ConstrainResult, MonotoneFramework};
use std::collections::HashSet;
use std::collections::HashMap;
use ir::context::{BindgenContext, ItemId};
use ir::traversal::EdgeKind;
use ir::ty::TypeKind;
use ir::traversal::Trace;

/// An analysis that finds for each IR item whether it has vtable or not
///
/// We use the monotone function `has vtable`, defined as follows:
///
/// * If T is a type alias, a templated alias, an indirection to another type,
/// or a reference of a type, T has vtable if the type T refers to has vtable.
/// * If T is a compound type, T has vtable if we saw a virtual function when
/// parsing it or any of its base member has vtable.
/// * If T is an instantiation of an abstract template definition, T has
/// vtable if template definition has vtable
#[derive(Debug, Clone)]
pub struct HasVtableAnalysis<'ctx, 'gen>
where 'gen: 'ctx
{
ctx: &'ctx BindgenContext<'gen>,

// The incremental result of this analysis's computation. Everything in this
// set definitely has a vtable.
have_vtable: HashSet<ItemId>,

// Dependencies saying that if a key ItemId has been inserted into the
// `have_vtable` set, then each of the ids in Vec<ItemId> need to be
// considered again.
//
// This is a subset of the natural IR graph with reversed edges, where we
// only include the edges from the IR graph that can affect whether a type
// has a vtable or not.
dependencies: HashMap<ItemId, Vec<ItemId>>,
}

impl<'ctx, 'gen> HasVtableAnalysis<'ctx, 'gen> {
fn consider_edge(kind: EdgeKind) -> bool {
match kind {
// These are the only edges that can affect whether a type has a
// vtable or not.
EdgeKind::TypeReference |
EdgeKind::BaseMember |
EdgeKind::TemplateDeclaration => true,
_ => false,
}
}

fn insert(&mut self, id: ItemId) -> ConstrainResult {
let was_not_already_in_set = self.have_vtable.insert(id);
assert!(
was_not_already_in_set,
"We shouldn't try and insert {:?} twice because if it was \
already in the set, `constrain` should have exited early.",
id
);
ConstrainResult::Changed
}
}

impl<'ctx, 'gen> MonotoneFramework for HasVtableAnalysis<'ctx, 'gen> {
type Node = ItemId;
type Extra = &'ctx BindgenContext<'gen>;
type Output = HashSet<ItemId>;

fn new(ctx: &'ctx BindgenContext<'gen>) -> HasVtableAnalysis<'ctx, 'gen> {
let have_vtable = HashSet::new();
let mut dependencies = HashMap::new();

for &item in ctx.whitelisted_items() {
dependencies.entry(item).or_insert(vec![]);

{
// We reverse our natural IR graph edges to find dependencies
// between nodes.
item.trace(ctx, &mut |sub_item: ItemId, edge_kind| {
if ctx.whitelisted_items().contains(&sub_item) &&
Self::consider_edge(edge_kind) {
dependencies.entry(sub_item)
.or_insert(vec![])
.push(item);
Copy link
Member

Choose a reason for hiding this comment

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

Want to do a follow up PR to pull the creation of the dependencies graph out and share its definition between all of our analyses? This is getting copied with slight changes a little too much now, and its clear we need a helper function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

}
}, &());
}
}

HasVtableAnalysis {
ctx,
have_vtable,
dependencies,
}
}

fn initial_worklist(&self) -> Vec<ItemId> {
self.ctx.whitelisted_items().iter().cloned().collect()
}

fn constrain(&mut self, id: ItemId) -> ConstrainResult {
if self.have_vtable.contains(&id) {
// We've already computed that this type has a vtable and that can't
// change.
return ConstrainResult::Same;
}

let item = self.ctx.resolve_item(id);
let ty = match item.as_type() {
None => return ConstrainResult::Same,
Some(ty) => ty
};

// TODO #851: figure out a way to handle deriving from template type parameters.
match *ty.kind() {
TypeKind::TemplateAlias(t, _) |
TypeKind::Alias(t) |
TypeKind::ResolvedTypeRef(t) |
TypeKind::Reference(t) => {
if self.have_vtable.contains(&t) {
self.insert(id)
} else {
ConstrainResult::Same
}
},

TypeKind::Comp(ref info) => {
if info.has_own_virtual_method() {
return self.insert(id);
}
let bases_has_vtable = info.base_members().iter().any(|base| {
self.have_vtable.contains(&base.ty)
});
if bases_has_vtable {
self.insert(id)
} else {
ConstrainResult::Same
}
},

TypeKind::TemplateInstantiation(ref inst) => {
if self.have_vtable.contains(&inst.template_definition()) {
self.insert(id)
} else {
ConstrainResult::Same
}
},

_ => ConstrainResult::Same,
}
}

fn each_depending_on<F>(&self, id: ItemId, mut f: F)
where F: FnMut(ItemId),
{
if let Some(edges) = self.dependencies.get(&id) {
for item in edges {
trace!("enqueue {:?} into worklist", item);
f(*item);
}
}
}
}

impl<'ctx, 'gen> From<HasVtableAnalysis<'ctx, 'gen>> for HashSet<ItemId> {
fn from(analysis: HasVtableAnalysis<'ctx, 'gen>) -> Self {
analysis.have_vtable
}
}

/// A convenience trait for the things for which we might wonder if they have a
/// vtable during codegen.
///
/// This is not for _computing_ whether the thing has a vtable, it is for
/// looking up the results of the HasVtableAnalysis's computations for a
/// specific thing.
pub trait HasVtable {

/// Implementations can define this type to get access to any extra
/// information required to determine whether they have vtable. If
/// extra information is unneeded, then this should simply be the unit type.
type Extra;

/// Return `true` if this thing has vtable, `false` otherwise.
fn has_vtable(&self, ctx: &BindgenContext, extra: &Self::Extra) -> bool;
}
3 changes: 3 additions & 0 deletions src/ir/analysis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ mod template_params;
pub use self::template_params::UsedTemplateParameters;
mod derive_debug;
pub use self::derive_debug::CannotDeriveDebug;
mod has_vtable;
pub use self::has_vtable::HasVtableAnalysis;
pub use self::has_vtable::HasVtable;

use std::fmt;

Expand Down
45 changes: 21 additions & 24 deletions src/ir/comp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ impl FieldMethods for FieldData {
}
}

impl CanDeriveDefault for Field {
impl<'a> CanDeriveDefault<'a> for Field {
type Extra = ();

fn can_derive_default(&self, ctx: &BindgenContext, _: ()) -> bool {
Expand Down Expand Up @@ -819,7 +819,7 @@ pub struct CompInfo {

/// Whether this type should generate an vtable (TODO: Should be able to
/// look at the virtual methods and ditch this field).
has_vtable: bool,
has_own_virtual_method: bool,

/// Whether this type has destructor.
has_destructor: bool,
Expand Down Expand Up @@ -870,7 +870,7 @@ impl CompInfo {
base_members: vec![],
inner_types: vec![],
inner_vars: vec![],
has_vtable: false,
has_own_virtual_method: false,
has_destructor: false,
has_nonempty_base: false,
has_non_type_template_params: false,
Expand All @@ -883,10 +883,10 @@ impl CompInfo {
}

/// Is this compound type unsized?
pub fn is_unsized(&self, ctx: &BindgenContext) -> bool {
!self.has_vtable(ctx) && self.fields().is_empty() &&
pub fn is_unsized(&self, ctx: &BindgenContext, itemid: &ItemId) -> bool {
!ctx.lookup_item_id_has_vtable(itemid) && self.fields().is_empty() &&
self.base_members.iter().all(|base| {
ctx.resolve_type(base.ty).canonical_type(ctx).is_unsized(ctx)
ctx.resolve_type(base.ty).canonical_type(ctx).is_unsized(ctx, &base.ty)
})
}

Expand Down Expand Up @@ -964,13 +964,10 @@ impl CompInfo {
self.has_non_type_template_params
}

/// Does this type have a virtual table?
pub fn has_vtable(&self, ctx: &BindgenContext) -> bool {
self.has_vtable ||
self.base_members().iter().any(|base| {
ctx.resolve_type(base.ty)
.has_vtable(ctx)
})
/// Do we see a virtual function during parsing?
/// Get the has_own_virtual_method boolean.
pub fn has_own_virtual_method(&self) -> bool {
return self.has_own_virtual_method;
}

/// Get this type's set of methods.
Expand Down Expand Up @@ -1169,7 +1166,7 @@ impl CompInfo {
}
CXCursor_CXXBaseSpecifier => {
let is_virtual_base = cur.is_virtual_base();
ci.has_vtable |= is_virtual_base;
ci.has_own_virtual_method |= is_virtual_base;

let kind = if is_virtual_base {
BaseKind::Virtual
Expand All @@ -1192,7 +1189,7 @@ impl CompInfo {
debug_assert!(!(is_static && is_virtual), "How?");

ci.has_destructor |= cur.kind() == CXCursor_Destructor;
ci.has_vtable |= is_virtual;
ci.has_own_virtual_method |= is_virtual;

// This used to not be here, but then I tried generating
// stylo bindings with this (without path filters), and
Expand Down Expand Up @@ -1335,8 +1332,8 @@ impl CompInfo {

/// Returns whether this type needs an explicit vtable because it has
/// virtual methods and none of its base classes has already a vtable.
pub fn needs_explicit_vtable(&self, ctx: &BindgenContext) -> bool {
self.has_vtable(ctx) &&
pub fn needs_explicit_vtable(&self, ctx: &BindgenContext, item: &Item) -> bool {
ctx.lookup_item_id_has_vtable(&item.id()) &&
!self.base_members.iter().any(|base| {
// NB: Ideally, we could rely in all these types being `comp`, and
// life would be beautiful.
Expand All @@ -1347,7 +1344,7 @@ impl CompInfo {
ctx.resolve_type(base.ty)
.canonical_type(ctx)
.as_comp()
.map_or(false, |ci| ci.has_vtable(ctx))
.map_or(false, |_| ctx.lookup_item_id_has_vtable(&base.ty))
})
}

Expand All @@ -1368,7 +1365,7 @@ impl DotAttributes for CompInfo {
{
writeln!(out, "<tr><td>CompKind</td><td>{:?}</td></tr>", self.kind)?;

if self.has_vtable {
if self.has_own_virtual_method {
writeln!(out, "<tr><td>has_vtable</td><td>true</td></tr>")?;
}

Expand Down Expand Up @@ -1424,12 +1421,12 @@ impl TemplateParameters for CompInfo {
}
}

impl CanDeriveDefault for CompInfo {
type Extra = Option<Layout>;
impl<'a> CanDeriveDefault<'a> for CompInfo {
type Extra = (&'a Item, Option<Layout>);

fn can_derive_default(&self,
ctx: &BindgenContext,
layout: Option<Layout>)
(item, layout): (&Item, Option<Layout>))
-> bool {
// We can reach here recursively via template parameters of a member,
// for example.
Expand All @@ -1452,8 +1449,8 @@ impl CanDeriveDefault for CompInfo {

self.detect_derive_default_cycle.set(true);

let can_derive_default = !self.has_vtable(ctx) &&
!self.needs_explicit_vtable(ctx) &&
let can_derive_default = !ctx.lookup_item_id_has_vtable(&item.id()) &&
!self.needs_explicit_vtable(ctx, item) &&
self.base_members
.iter()
.all(|base| base.ty.can_derive_default(ctx, ())) &&
Expand Down
Loading