diff --git a/src/components/main/css/matching.rs b/src/components/main/css/matching.rs index b637bf9a5cb..b31826d45a4 100644 --- a/src/components/main/css/matching.rs +++ b/src/components/main/css/matching.rs @@ -6,13 +6,16 @@ use css::node_style::StyledNode; use layout::extra::LayoutAuxMethods; -use layout::incremental; use layout::util::LayoutDataAccess; use layout::wrapper::LayoutNode; use extra::arc::Arc; use script::layout_interface::LayoutChan; +use servo_util::cache::{Cache, LRUCache, SimpleHashCache}; +use servo_util::namespace::Null; use servo_util::smallvec::{SmallVec, SmallVec0, SmallVec16}; +use std::cast; +use std::to_bytes; use style::{After, Before, ComputedValues, PropertyDeclaration, Stylist, TNode, cascade}; pub struct ApplicableDeclarations { @@ -37,6 +40,108 @@ impl ApplicableDeclarations { } } +#[deriving(Clone)] +struct ApplicableDeclarationsCacheEntry { + declarations: SmallVec16>, +} + +impl ApplicableDeclarationsCacheEntry { + fn new(slice: &[Arc<~[PropertyDeclaration]>]) -> ApplicableDeclarationsCacheEntry { + let mut entry_declarations = SmallVec16::new(); + for declarations in slice.iter() { + entry_declarations.push(declarations.clone()); + } + ApplicableDeclarationsCacheEntry { + declarations: entry_declarations, + } + } +} + +impl Eq for ApplicableDeclarationsCacheEntry { + fn eq(&self, other: &ApplicableDeclarationsCacheEntry) -> bool { + let this_as_query = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice()); + this_as_query.equiv(other) + } +} + +impl IterBytes for ApplicableDeclarationsCacheEntry { + fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) -> bool { + ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice()).iter_bytes(lsb0, f) + } +} + +struct ApplicableDeclarationsCacheQuery<'a> { + declarations: &'a [Arc<~[PropertyDeclaration]>], +} + +impl<'a> ApplicableDeclarationsCacheQuery<'a> { + fn new(declarations: &'a [Arc<~[PropertyDeclaration]>]) + -> ApplicableDeclarationsCacheQuery<'a> { + ApplicableDeclarationsCacheQuery { + declarations: declarations, + } + } +} + +impl<'a> Equiv for ApplicableDeclarationsCacheQuery<'a> { + fn equiv(&self, other: &ApplicableDeclarationsCacheEntry) -> bool { + if self.declarations.len() != other.declarations.len() { + return false + } + for (this, other) in self.declarations.iter().zip(other.declarations.iter()) { + unsafe { + // Workaround for lack of `ptr_eq` on Arcs... + let this: uint = cast::transmute_copy(this); + let other: uint = cast::transmute_copy(other); + if this != other { + return false + } + } + } + return true + } +} + +impl<'a> IterBytes for ApplicableDeclarationsCacheQuery<'a> { + fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) -> bool { + let mut result = true; + for declaration in self.declarations.iter() { + let ptr: uint = unsafe { + cast::transmute_copy(declaration) + }; + result = ptr.iter_bytes(lsb0, |x| f(x)); + } + result + } +} + +static APPLICABLE_DECLARATIONS_CACHE_SIZE: uint = 32; + +pub struct ApplicableDeclarationsCache { + cache: SimpleHashCache>, +} + +impl ApplicableDeclarationsCache { + pub fn new() -> ApplicableDeclarationsCache { + ApplicableDeclarationsCache { + cache: SimpleHashCache::new(APPLICABLE_DECLARATIONS_CACHE_SIZE), + } + } + + fn find(&self, declarations: &[Arc<~[PropertyDeclaration]>]) -> Option> { + match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) { + None => None, + Some(ref values) => Some((*values).clone()), + } + } + + fn insert(&mut self, + declarations: &[Arc<~[PropertyDeclaration]>], + style: Arc) { + drop(self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style)) + } +} + pub trait MatchMethods { /// Performs aux initialization, selector matching, and cascading sequentially. fn match_and_cascade_subtree(&self, @@ -44,6 +149,7 @@ pub trait MatchMethods { layout_chan: &LayoutChan, applicable_declarations: &mut ApplicableDeclarations, initial_values: &ComputedValues, + applicable_declarations_cache: &mut ApplicableDeclarationsCache, parent: Option); fn match_node(&self, stylist: &Stylist, applicable_declarations: &mut ApplicableDeclarations); @@ -51,11 +157,68 @@ pub trait MatchMethods { unsafe fn cascade_node(&self, parent: Option, initial_values: &ComputedValues, - applicable_declarations: &ApplicableDeclarations); + applicable_declarations: &ApplicableDeclarations, + applicable_declarations_cache: &mut ApplicableDeclarationsCache); +} + +trait PrivateMatchMethods { + fn cascade_node_pseudo_element(&self, + parent_style: Option<&Arc>, + applicable_declarations: &[Arc<~[PropertyDeclaration]>], + style: &mut Option>, + initial_values: &ComputedValues, + applicable_declarations_cache: &mut + ApplicableDeclarationsCache); +} + +impl<'ln> PrivateMatchMethods for LayoutNode<'ln> { + fn cascade_node_pseudo_element(&self, + parent_style: Option<&Arc>, + applicable_declarations: &[Arc<~[PropertyDeclaration]>], + style: &mut Option>, + initial_values: &ComputedValues, + applicable_declarations_cache: &mut + ApplicableDeclarationsCache) { + let this_style; + let cacheable; + match parent_style { + Some(ref parent_style) => { + let cached_computed_values; + let cache_entry = applicable_declarations_cache.find(applicable_declarations); + match cache_entry { + None => cached_computed_values = None, + Some(ref style) => cached_computed_values = Some(style.get()), + } + let (the_style, is_cacheable) = cascade(applicable_declarations, + Some(parent_style.get()), + initial_values, + cached_computed_values); + cacheable = is_cacheable; + this_style = Arc::new(the_style); + } + None => { + let (the_style, is_cacheable) = cascade(applicable_declarations, + None, + initial_values, + None); + cacheable = is_cacheable; + this_style = Arc::new(the_style); + } + }; + + // Cache the resolved style if it was cacheable. + if cacheable { + applicable_declarations_cache.insert(applicable_declarations, this_style.clone()); + } + + *style = Some(this_style); + } } impl<'ln> MatchMethods for LayoutNode<'ln> { - fn match_node(&self, stylist: &Stylist, applicable_declarations: &mut ApplicableDeclarations) { + fn match_node(&self, + stylist: &Stylist, + applicable_declarations: &mut ApplicableDeclarations) { let style_attribute = self.with_element(|element| { match *element.style_attribute() { None => None, @@ -82,6 +245,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { layout_chan: &LayoutChan, applicable_declarations: &mut ApplicableDeclarations, initial_values: &ComputedValues, + applicable_declarations_cache: &mut ApplicableDeclarationsCache, parent: Option) { self.initialize_layout_data((*layout_chan).clone()); @@ -90,7 +254,10 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { } unsafe { - self.cascade_node(parent, initial_values, applicable_declarations) + self.cascade_node(parent, + initial_values, + applicable_declarations, + applicable_declarations_cache) } applicable_declarations.clear(); @@ -100,6 +267,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { layout_chan, applicable_declarations, initial_values, + applicable_declarations_cache, Some(*self)) } } @@ -107,65 +275,53 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { unsafe fn cascade_node(&self, parent: Option, initial_values: &ComputedValues, - applicable_declarations: &ApplicableDeclarations) { - macro_rules! cascade_node( - ($applicable_declarations: expr, $style: ident) => {{ - // Get our parent's style. This must be unsafe so that we don't touch the parent's - // borrow flags. - // - // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow - // enforced safe, race-free access to the parent style. - let parent_style = match parent { - None => None, - Some(parent_node) => { - let parent_layout_data = parent_node.borrow_layout_data_unchecked(); - match *parent_layout_data { - None => fail!("no parent data?!"), - Some(ref parent_layout_data) => { - match parent_layout_data.data.style { - None => fail!("parent hasn't been styled yet?!"), - Some(ref style) => Some(style), - } - } + applicable_declarations: &ApplicableDeclarations, + applicable_declarations_cache: &mut ApplicableDeclarationsCache) { + // Get our parent's style. This must be unsafe so that we don't touch the parent's + // borrow flags. + // + // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow + // enforced safe, race-free access to the parent style. + let parent_style = match parent { + None => None, + Some(parent_node) => { + let parent_layout_data = parent_node.borrow_layout_data_unchecked(); + match *parent_layout_data { + None => fail!("no parent data?!"), + Some(ref parent_layout_data) => { + match parent_layout_data.data.style { + None => fail!("parent hasn't been styled yet?!"), + Some(ref style) => Some(style), } } - }; - - let computed_values = match parent_style { - Some(ref style) => { - Arc::new(cascade($applicable_declarations.as_slice(), - Some(style.get()), - initial_values)) - } - None => Arc::new(cascade($applicable_declarations.as_slice(), - None, - initial_values)), - }; - - let mut layout_data_ref = self.mutate_layout_data(); - match *layout_data_ref.get() { - None => fail!("no layout data"), - Some(ref mut layout_data) => { - let style = &mut layout_data.data.$style; - match *style { - None => (), - Some(ref previous_style) => { - layout_data.data.restyle_damage = Some(incremental::compute_damage( - previous_style.get(), computed_values.get()).to_int()) - } - } - *style = Some(computed_values) - } } - }} - ); + } + }; - cascade_node!(applicable_declarations.normal, style); - if applicable_declarations.before.len() > 0 { - cascade_node!(applicable_declarations.before, before_style); - } - if applicable_declarations.after.len() > 0 { - cascade_node!(applicable_declarations.after, after_style); + let mut layout_data_ref = self.mutate_layout_data(); + match *layout_data_ref.get() { + None => fail!("no layout data"), + Some(ref mut layout_data) => { + self.cascade_node_pseudo_element(parent_style, + applicable_declarations.normal.as_slice(), + &mut layout_data.data.style, + initial_values, + applicable_declarations_cache); + if applicable_declarations.before.len() > 0 { + self.cascade_node_pseudo_element(parent_style, + applicable_declarations.before.as_slice(), + &mut layout_data.data.before_style, + initial_values, + applicable_declarations_cache); + } + if applicable_declarations.after.len() > 0 { + self.cascade_node_pseudo_element(parent_style, + applicable_declarations.after.as_slice(), + &mut layout_data.data.after_style, + initial_values, + applicable_declarations_cache); + } + } } } } diff --git a/src/components/main/layout/context.rs b/src/components/main/layout/context.rs index f3a1ac59bd9..432e0d8e541 100644 --- a/src/components/main/layout/context.rs +++ b/src/components/main/layout/context.rs @@ -4,28 +4,33 @@ //! Data needed by the layout task. -use extra::arc::{Arc, MutexArc}; -use green::task::GreenTask; +use css::matching::ApplicableDeclarationsCache; use layout::flow::FlowLeafSet; use layout::util::OpaqueNode; use layout::wrapper::DomLeafSet; + +use extra::arc::{Arc, MutexArc}; +use geom::size::Size2D; +use gfx::font_context::{FontContext, FontContextInfo}; +use green::task::GreenTask; +use script::layout_interface::LayoutChan; +use servo_msg::constellation_msg::ConstellationChan; +use servo_net::local_image_cache::LocalImageCache; +use servo_util::geometry::Au; use std::cast; use std::ptr; use std::rt::Runtime; use std::rt::local::Local; use std::rt::task::Task; - -use geom::size::Size2D; -use gfx::font_context::{FontContext, FontContextInfo}; -use script::layout_interface::LayoutChan; -use servo_msg::constellation_msg::ConstellationChan; -use servo_net::local_image_cache::LocalImageCache; -use servo_util::geometry::Au; use style::{ComputedValues, Stylist}; #[thread_local] static mut FONT_CONTEXT: *mut FontContext = 0 as *mut FontContext; +#[thread_local] +static mut APPLICABLE_DECLARATIONS_CACHE: *mut ApplicableDeclarationsCache = + 0 as *mut ApplicableDeclarationsCache; + /// Data shared by all layout workers. #[deriving(Clone)] pub struct LayoutContext { @@ -82,5 +87,27 @@ impl LayoutContext { cast::transmute(FONT_CONTEXT) } } + + pub fn applicable_declarations_cache<'a>(&'a self) -> &'a mut ApplicableDeclarationsCache { + // Sanity check. + { + let mut task = Local::borrow(None::); + match task.get().maybe_take_runtime::() { + Some(green) => { + task.get().put_runtime(green as ~Runtime); + fail!("can't call this on a green task!") + } + None => {} + } + } + + unsafe { + if APPLICABLE_DECLARATIONS_CACHE == ptr::mut_null() { + let cache = ~ApplicableDeclarationsCache::new(); + APPLICABLE_DECLARATIONS_CACHE = cast::transmute(cache) + } + cast::transmute(APPLICABLE_DECLARATIONS_CACHE) + } + } } diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index d85d0612716..0d9ba04b4f3 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -5,7 +5,7 @@ //! The layout task. Performs layout on the DOM, builds display lists and sends them to be /// rendered. -use css::matching::{ApplicableDeclarations, MatchMethods}; +use css::matching::{ApplicableDeclarations, ApplicableDeclarationsCache, MatchMethods}; use css::select::new_stylist; use css::node_style::StyledNode; use layout::construct::{FlowConstructionResult, FlowConstructor, NoConstructionResult}; @@ -568,10 +568,13 @@ impl LayoutTask { match self.parallel_traversal { None => { let mut applicable_declarations = ApplicableDeclarations::new(); + let mut applicable_declarations_cache = + ApplicableDeclarationsCache::new(); node.match_and_cascade_subtree(self.stylist, &layout_ctx.layout_chan, &mut applicable_declarations, layout_ctx.initial_css_values.get(), + &mut applicable_declarations_cache, None) } Some(ref mut traversal) => { diff --git a/src/components/main/layout/parallel.rs b/src/components/main/layout/parallel.rs index 8d6dd029413..17f9da7bcae 100644 --- a/src/components/main/layout/parallel.rs +++ b/src/components/main/layout/parallel.rs @@ -165,7 +165,8 @@ fn match_and_cascade_node(unsafe_layout_node: UnsafeLayoutNode, }; node.cascade_node(parent_opt, layout_context.initial_css_values.get(), - &applicable_declarations); + &applicable_declarations, + layout_context.applicable_declarations_cache()); // Enqueue kids. let mut child_count = 0; diff --git a/src/components/main/layout/util.rs b/src/components/main/layout/util.rs index 7c1b0ad567c..7a2eeb8f805 100644 --- a/src/components/main/layout/util.rs +++ b/src/components/main/layout/util.rs @@ -130,10 +130,12 @@ impl ElementMapping { /// Data that layout associates with a node. pub struct PrivateLayoutData { /// The results of CSS styling for this node. - before_style: Option>, - style: Option>, + /// The results of CSS styling for this node's `before` pseudo-element, if any. + before_style: Option>, + + /// The results of CSS styling for this node's `after` pseudo-element, if any. after_style: Option>, /// Description of how to account for recent style changes. diff --git a/src/components/style/properties.rs.mako b/src/components/style/properties.rs.mako index 10dc44ad2af..604d4572b07 100644 --- a/src/components/style/properties.rs.mako +++ b/src/components/style/properties.rs.mako @@ -1152,6 +1152,82 @@ pub fn initial_values() -> ComputedValues { } } +/// Fast path for the function below. Only computes new inherited styles. +#[allow(unused_mut)] +fn cascade_with_cached_declarations(applicable_declarations: &[Arc<~[PropertyDeclaration]>], + parent_style: &ComputedValues, + cached_style: &ComputedValues) + -> ComputedValues { + % for style_struct in STYLE_STRUCTS: + % if style_struct.inherited: + let mut style_${style_struct.name} = parent_style.${style_struct.name}.clone(); + % else: + let mut style_${style_struct.name} = cached_style.${style_struct.name}.clone(); + % endif + % endfor + + let mut context = computed::Context::new(&style_Color, + &style_Font, + &style_Box, + &style_Border, + false); + + <%def name="apply_cached(priority)"> + for sub_list in applicable_declarations.iter() { + for declaration in sub_list.get().iter() { + match declaration { + % for style_struct in STYLE_STRUCTS: + % if style_struct.inherited: + % for property in style_struct.longhands: + % if (property.needed_for_context and needed_for_context) or not \ + needed_for_context: + &${property.ident}_declaration(SpecifiedValue(ref value)) => { + % if property.needed_for_context and needed_for_context: + context.set_${property.ident}(computed_value) + % elif not needed_for_context: + // Overwrite earlier declarations. + let computed_value = + longhands::${property.ident}::to_computed_value( + (*value).clone(), + &context); + style_${style_struct.name}.get_mut() + .${property.ident} = + computed_value + % endif + } + &${property.ident}_declaration(CSSWideKeyword(Initial)) => { + let computed_value = + longhands::${property.ident}::get_initial_value(); + % if property.needed_for_context and needed_for_context: + context.set_${property.ident}(computed_value) + % elif not needed_for_context: + // Overwrite earlier declarations. + style_${style_struct.name}.get_mut() + .${property.ident} = + computed_value + % endif + } + % endif + % endfor + % endif + % endfor + _ => {} + } + } + } + + + ${apply_cached(True)} + context.use_parent_font_size = false; + ${apply_cached(False)} + + ComputedValues { + % for style_struct in STYLE_STRUCTS: + ${style_struct.name}: style_${style_struct.name}, + % endfor + } +} + /// Performs the CSS cascade, computing new styles for an element from its parent style and /// optionally a cached related style. The arguments are: /// @@ -1161,11 +1237,26 @@ pub fn initial_values() -> ComputedValues { /// /// * `initial_values`: The initial set of CSS values as defined by the specification. /// -/// Returns the computed values. +/// * `cached_style`: If present, cascading is short-circuited for everything but inherited +/// values and these values are used instead. Obviously, you must be careful when supplying +/// this that it is safe to only provide inherited declarations. If `parent_style` is `None`, +/// this is ignored. +/// +/// Returns the computed values and a boolean indicating whether the result is cacheable. pub fn cascade(applicable_declarations: &[Arc<~[PropertyDeclaration]>], - parent_style: Option< &ComputedValues>, - initial_values: &ComputedValues) - -> ComputedValues { + parent_style: Option< &ComputedValues >, + initial_values: &ComputedValues, + cached_style: Option< &ComputedValues >) + -> (ComputedValues, bool) { + match (cached_style, parent_style) { + (Some(cached_style), Some(parent_style)) => { + return (cascade_with_cached_declarations(applicable_declarations, + parent_style, + cached_style), false) + } + (_, _) => {} + } + let is_root_element; % for style_struct in STYLE_STRUCTS: let mut style_${style_struct.name}; @@ -1195,6 +1286,7 @@ pub fn cascade(applicable_declarations: &[Arc<~[PropertyDeclaration]>], &style_Border, is_root_element); + let mut cacheable = true; <%def name="apply(needed_for_context)"> for sub_list in applicable_declarations.iter() { for declaration in sub_list.get().iter() { @@ -1231,6 +1323,7 @@ pub fn cascade(applicable_declarations: &[Arc<~[PropertyDeclaration]>], % if not needed_for_context: &${property.ident}_declaration(CSSWideKeyword(Inherit)) => { // This is a bit slow, but this is rare so it shouldn't matter. + cacheable = false; match parent_style { None => { style_${style_struct.name}.get_mut() @@ -1262,11 +1355,11 @@ pub fn cascade(applicable_declarations: &[Arc<~[PropertyDeclaration]>], context.use_parent_font_size = false; ${apply(False)} - ComputedValues { + (ComputedValues { % for style_struct in STYLE_STRUCTS: ${style_struct.name}: style_${style_struct.name}, % endfor - } + }, cacheable) } diff --git a/src/components/util/cache.rs b/src/components/util/cache.rs index b8c89a3ac90..3a3ee473e33 100644 --- a/src/components/util/cache.rs +++ b/src/components/util/cache.rs @@ -3,6 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::hashmap::HashMap; +use std::rand::Rng; +use std::rand; +use std::vec::VecIterator; +use std::vec; pub trait Cache { fn insert(&mut self, key: K, value: V); @@ -165,6 +169,73 @@ impl Cache for LRUCache { } } +pub struct SimpleHashCache { + entries: ~[Option<(K,V)>], + k0: u64, + k1: u64, +} + +impl SimpleHashCache { + pub fn new(cache_size: uint) -> SimpleHashCache { + let mut r = rand::task_rng(); + SimpleHashCache { + entries: vec::from_elem(cache_size, None), + k0: r.gen(), + k1: r.gen(), + } + } + + #[inline] + fn to_bucket(&self, h: uint) -> uint { + h % self.entries.len() + } + + #[inline] + fn bucket_for_key(&self, key: &Q) -> uint { + self.to_bucket(key.hash_keyed(self.k0, self.k1) as uint) + } + + #[inline] + pub fn find_equiv<'a,Q:Hash+Equiv>(&'a self, key: &Q) -> Option<&'a V> { + let bucket_index = self.bucket_for_key(key); + match self.entries[bucket_index] { + Some((ref existing_key, ref value)) if key.equiv(existing_key) => Some(value), + _ => None, + } + } +} + +impl Cache for SimpleHashCache { + fn insert(&mut self, key: K, value: V) { + let bucket_index = self.bucket_for_key(&key); + self.entries[bucket_index] = Some((key, value)) + } + + fn find(&mut self, key: &K) -> Option { + let bucket_index = self.bucket_for_key(key); + match self.entries[bucket_index] { + Some((ref existing_key, ref value)) if existing_key == key => Some((*value).clone()), + _ => None, + } + } + + fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V { + match self.find(key) { + Some(value) => return value, + None => {} + } + let value = blk(key); + self.insert((*key).clone(), value.clone()); + value + } + + fn evict_all(&mut self) { + for slot in self.entries.mut_iter() { + *slot = None + } + } +} + #[test] fn test_lru_cache() { let one = @"one"; diff --git a/src/components/util/smallvec.rs b/src/components/util/smallvec.rs index 67b796d673e..cd96ce9b573 100644 --- a/src/components/util/smallvec.rs +++ b/src/components/util/smallvec.rs @@ -324,6 +324,20 @@ macro_rules! def_small_vector_drop_impl( ) ) +macro_rules! def_small_vector_clone_impl( + ($name:ident) => ( + impl Clone for $name { + fn clone(&self) -> $name { + let mut new_vector = $name::new(); + for element in self.iter() { + new_vector.push((*element).clone()) + } + new_vector + } + } + ) +) + macro_rules! def_small_vector_impl( ($name:ident, $size:expr) => ( impl $name { @@ -396,47 +410,55 @@ impl SmallVec0 { } def_small_vector_drop_impl!(SmallVec0, 0) +def_small_vector_clone_impl!(SmallVec0) def_small_vector!(SmallVec1, 1) def_small_vector_private_trait_impl!(SmallVec1, 1) def_small_vector_trait_impl!(SmallVec1, 1) def_small_vector_drop_impl!(SmallVec1, 1) +def_small_vector_clone_impl!(SmallVec1) def_small_vector_impl!(SmallVec1, 1) def_small_vector!(SmallVec2, 2) def_small_vector_private_trait_impl!(SmallVec2, 2) def_small_vector_trait_impl!(SmallVec2, 2) def_small_vector_drop_impl!(SmallVec2, 2) +def_small_vector_clone_impl!(SmallVec2) def_small_vector_impl!(SmallVec2, 2) def_small_vector!(SmallVec4, 4) def_small_vector_private_trait_impl!(SmallVec4, 4) def_small_vector_trait_impl!(SmallVec4, 4) def_small_vector_drop_impl!(SmallVec4, 4) +def_small_vector_clone_impl!(SmallVec4) def_small_vector_impl!(SmallVec4, 4) def_small_vector!(SmallVec8, 8) def_small_vector_private_trait_impl!(SmallVec8, 8) def_small_vector_trait_impl!(SmallVec8, 8) def_small_vector_drop_impl!(SmallVec8, 8) +def_small_vector_clone_impl!(SmallVec8) def_small_vector_impl!(SmallVec8, 8) def_small_vector!(SmallVec16, 16) def_small_vector_private_trait_impl!(SmallVec16, 16) def_small_vector_trait_impl!(SmallVec16, 16) def_small_vector_drop_impl!(SmallVec16, 16) +def_small_vector_clone_impl!(SmallVec16) def_small_vector_impl!(SmallVec16, 16) def_small_vector!(SmallVec24, 24) def_small_vector_private_trait_impl!(SmallVec24, 24) def_small_vector_trait_impl!(SmallVec24, 24) def_small_vector_drop_impl!(SmallVec24, 24) +def_small_vector_clone_impl!(SmallVec24) def_small_vector_impl!(SmallVec24, 24) def_small_vector!(SmallVec32, 32) def_small_vector_private_trait_impl!(SmallVec32, 32) def_small_vector_trait_impl!(SmallVec32, 32) def_small_vector_drop_impl!(SmallVec32, 32) +def_small_vector_clone_impl!(SmallVec32) def_small_vector_impl!(SmallVec32, 32) #[cfg(test)]