layout: Cache applicable-declarations-to-computed-values mappings.

If the cache is hit, then we can only compute inherited properties. This
is a WebKit optimization.
This commit is contained in:
Patrick Walton 2014-02-07 18:10:17 -08:00
parent 1678cc9a88
commit 0fa0940ce9
8 changed files with 453 additions and 78 deletions

View file

@ -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<Arc<~[PropertyDeclaration]>>,
}
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<ApplicableDeclarationsCacheEntry> 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<ApplicableDeclarationsCacheEntry,Arc<ComputedValues>>,
}
impl ApplicableDeclarationsCache {
pub fn new() -> ApplicableDeclarationsCache {
ApplicableDeclarationsCache {
cache: SimpleHashCache::new(APPLICABLE_DECLARATIONS_CACHE_SIZE),
}
}
fn find(&self, declarations: &[Arc<~[PropertyDeclaration]>]) -> Option<Arc<ComputedValues>> {
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<ComputedValues>) {
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<LayoutNode>);
fn match_node(&self, stylist: &Stylist, applicable_declarations: &mut ApplicableDeclarations);
@ -51,11 +157,68 @@ pub trait MatchMethods {
unsafe fn cascade_node(&self,
parent: Option<LayoutNode>,
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<ComputedValues>>,
applicable_declarations: &[Arc<~[PropertyDeclaration]>],
style: &mut Option<Arc<ComputedValues>>,
initial_values: &ComputedValues,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache);
}
impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
fn cascade_node_pseudo_element(&self,
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[Arc<~[PropertyDeclaration]>],
style: &mut Option<Arc<ComputedValues>>,
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<LayoutNode>) {
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<LayoutNode>,
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);
}
}
}
}
}

View file

@ -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::<Task>);
match task.get().maybe_take_runtime::<GreenTask>() {
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)
}
}
}

View file

@ -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) => {

View file

@ -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;

View file

@ -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<Arc<ComputedValues>>,
style: Option<Arc<ComputedValues>>,
/// The results of CSS styling for this node's `before` pseudo-element, if any.
before_style: Option<Arc<ComputedValues>>,
/// The results of CSS styling for this node's `after` pseudo-element, if any.
after_style: Option<Arc<ComputedValues>>,
/// Description of how to account for recent style changes.

View file

@ -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
_ => {}
}
}
}
</%def>
${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)
}

View file

@ -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<K: Eq, V: Clone> {
fn insert(&mut self, key: K, value: V);
@ -165,6 +169,73 @@ impl<K: Clone + Eq, V: Clone> Cache<K,V> for LRUCache<K,V> {
}
}
pub struct SimpleHashCache<K,V> {
entries: ~[Option<(K,V)>],
k0: u64,
k1: u64,
}
impl<K:Clone+Eq+Hash,V:Clone> SimpleHashCache<K,V> {
pub fn new(cache_size: uint) -> SimpleHashCache<K,V> {
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<Q:Hash>(&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<K>>(&'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<K:Clone+Eq+Hash,V:Clone> Cache<K,V> for SimpleHashCache<K,V> {
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<V> {
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";

View file

@ -324,6 +324,20 @@ macro_rules! def_small_vector_drop_impl(
)
)
macro_rules! def_small_vector_clone_impl(
($name:ident) => (
impl<T:Clone> Clone for $name<T> {
fn clone(&self) -> $name<T> {
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<T> $name<T> {
@ -396,47 +410,55 @@ impl<T> SmallVec0<T> {
}
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)]