2013-06-16 10:50:16 -05:00
|
|
|
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
|
2012-12-03 18:48:01 -06:00
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2012-03-20 21:06:04 -05:00
|
|
|
// Dynamic arenas.
|
|
|
|
|
2012-08-21 17:32:30 -05:00
|
|
|
// Arenas are used to quickly allocate objects that share a
|
|
|
|
// lifetime. The arena uses ~[u8] vectors as a backing store to
|
|
|
|
// allocate objects from. For each allocated object, the arena stores
|
|
|
|
// a pointer to the type descriptor followed by the
|
|
|
|
// object. (Potentially with alignment padding after each of them.)
|
|
|
|
// When the arena is destroyed, it iterates through all of its chunks,
|
|
|
|
// and uses the tydesc information to trace through the objects,
|
|
|
|
// calling the destructors on them.
|
|
|
|
// One subtle point that needs to be addressed is how to handle
|
|
|
|
// failures while running the user provided initializer function. It
|
2013-05-08 12:34:47 -05:00
|
|
|
// is important to not run the destructor on uninitialized objects, but
|
2012-08-21 17:32:30 -05:00
|
|
|
// how to detect them is somewhat subtle. Since alloc() can be invoked
|
|
|
|
// recursively, it is not sufficient to simply exclude the most recent
|
|
|
|
// object. To solve this without requiring extra space, we use the low
|
|
|
|
// order bit of the tydesc pointer to encode whether the object it
|
|
|
|
// describes has been fully initialized.
|
|
|
|
|
2012-08-21 18:26:19 -05:00
|
|
|
// As an optimization, objects with destructors are stored in
|
|
|
|
// different chunks than objects without destructors. This reduces
|
|
|
|
// overhead when initializing plain-old-data and means we don't need
|
|
|
|
// to waste time running the destructors of POD.
|
2012-08-21 17:32:30 -05:00
|
|
|
|
2013-05-28 22:11:41 -05:00
|
|
|
#[allow(missing_doc)];
|
|
|
|
|
2013-05-17 17:28:44 -05:00
|
|
|
|
2013-04-29 17:23:04 -05:00
|
|
|
use list::{MutList, MutCons, MutNil};
|
2012-12-23 16:41:37 -06:00
|
|
|
|
2013-06-28 17:32:26 -05:00
|
|
|
use std::at_vec;
|
|
|
|
use std::cast::{transmute, transmute_mut, transmute_mut_region};
|
|
|
|
use std::cast;
|
Replaces the free-standing functions in f32, &c.
The free-standing functions in f32, f64, i8, i16, i32, i64, u8, u16,
u32, u64, float, int, and uint are replaced with generic functions in
num instead.
If you were previously using any of those functions, just replace them
with the corresponding function with the same name in num.
Note: If you were using a function that corresponds to an operator, use
the operator instead.
2013-07-08 11:05:17 -05:00
|
|
|
use std::num;
|
2013-06-28 17:32:26 -05:00
|
|
|
use std::ptr;
|
|
|
|
use std::sys;
|
|
|
|
use std::uint;
|
|
|
|
use std::vec;
|
|
|
|
use std::unstable::intrinsics;
|
|
|
|
use std::unstable::intrinsics::{TyDesc};
|
2012-10-05 16:58:42 -05:00
|
|
|
|
2013-06-20 04:39:49 -05:00
|
|
|
#[cfg(not(stage0))]
|
2013-06-28 17:32:26 -05:00
|
|
|
use std::unstable::intrinsics::{get_tydesc};
|
2013-03-05 14:21:02 -06:00
|
|
|
|
2013-06-21 03:00:49 -05:00
|
|
|
#[cfg(stage0)]
|
|
|
|
unsafe fn get_tydesc<T>() -> *TyDesc {
|
|
|
|
intrinsics::get_tydesc::<T>() as *TyDesc
|
2012-08-21 17:32:30 -05:00
|
|
|
}
|
2013-03-05 14:21:02 -06:00
|
|
|
|
2012-08-21 17:32:30 -05:00
|
|
|
// The way arena uses arrays is really deeply awful. The arrays are
|
|
|
|
// allocated, and have capacities reserved, but the fill for the array
|
|
|
|
// will always stay at 0.
|
2013-01-22 10:44:24 -06:00
|
|
|
struct Chunk {
|
|
|
|
data: @[u8],
|
2013-04-29 17:23:04 -05:00
|
|
|
fill: uint,
|
2013-01-22 10:44:24 -06:00
|
|
|
is_pod: bool,
|
|
|
|
}
|
2012-07-11 17:00:40 -05:00
|
|
|
|
2013-06-28 16:36:33 -05:00
|
|
|
#[mutable] // XXX remove after snap
|
|
|
|
#[no_freeze]
|
2012-09-28 18:24:57 -05:00
|
|
|
pub struct Arena {
|
2013-05-03 17:57:18 -05:00
|
|
|
// The head is separated out from the list as a unbenchmarked
|
2012-08-21 17:32:30 -05:00
|
|
|
// microoptimization, to avoid needing to case on the list to
|
|
|
|
// access the head.
|
2013-04-29 17:23:04 -05:00
|
|
|
priv head: Chunk,
|
|
|
|
priv pod_head: Chunk,
|
|
|
|
priv chunks: @mut MutList<Chunk>,
|
2012-11-13 20:38:18 -06:00
|
|
|
}
|
|
|
|
|
2013-03-20 20:18:57 -05:00
|
|
|
#[unsafe_destructor]
|
2013-02-14 13:47:00 -06:00
|
|
|
impl Drop for Arena {
|
2013-06-20 20:06:13 -05:00
|
|
|
fn drop(&self) {
|
2012-08-21 17:32:30 -05:00
|
|
|
unsafe {
|
2012-09-19 20:14:30 -05:00
|
|
|
destroy_chunk(&self.head);
|
2013-04-29 17:23:04 -05:00
|
|
|
for self.chunks.each |chunk| {
|
|
|
|
if !chunk.is_pod {
|
|
|
|
destroy_chunk(chunk);
|
|
|
|
}
|
2012-08-21 18:26:19 -05:00
|
|
|
}
|
2012-08-21 17:32:30 -05:00
|
|
|
}
|
|
|
|
}
|
2012-07-11 17:00:40 -05:00
|
|
|
}
|
2012-03-20 21:06:04 -05:00
|
|
|
|
2012-08-11 09:08:42 -05:00
|
|
|
fn chunk(size: uint, is_pod: bool) -> Chunk {
|
2013-03-22 19:58:50 -05:00
|
|
|
let mut v: @[u8] = @[];
|
2012-09-26 14:28:30 -05:00
|
|
|
unsafe { at_vec::raw::reserve(&mut v, size); }
|
2013-01-22 10:44:24 -06:00
|
|
|
Chunk {
|
|
|
|
data: unsafe { cast::transmute(v) },
|
|
|
|
fill: 0u,
|
|
|
|
is_pod: is_pod,
|
|
|
|
}
|
2012-03-20 21:06:04 -05:00
|
|
|
}
|
|
|
|
|
2012-09-28 18:24:57 -05:00
|
|
|
pub fn arena_with_size(initial_size: uint) -> Arena {
|
2013-01-22 10:44:24 -06:00
|
|
|
Arena {
|
|
|
|
head: chunk(initial_size, false),
|
|
|
|
pod_head: chunk(initial_size, true),
|
2013-04-29 17:23:04 -05:00
|
|
|
chunks: @mut MutNil,
|
2013-01-22 10:44:24 -06:00
|
|
|
}
|
2012-03-20 21:06:04 -05:00
|
|
|
}
|
|
|
|
|
2012-09-28 18:24:57 -05:00
|
|
|
pub fn Arena() -> Arena {
|
2012-03-20 21:06:04 -05:00
|
|
|
arena_with_size(32u)
|
|
|
|
}
|
|
|
|
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-03-21 23:34:30 -05:00
|
|
|
fn round_up_to(base: uint, align: uint) -> uint {
|
2012-08-21 17:32:30 -05:00
|
|
|
(base + (align - 1)) & !(align - 1)
|
|
|
|
}
|
|
|
|
|
2013-06-22 14:36:00 -05:00
|
|
|
#[inline]
|
|
|
|
#[cfg(not(stage0))]
|
|
|
|
unsafe fn call_drop_glue(tydesc: *TyDesc, data: *i8) {
|
|
|
|
// This function should be inlined when stage0 is gone
|
|
|
|
((*tydesc).drop_glue)(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
#[cfg(stage0)]
|
|
|
|
unsafe fn call_drop_glue(tydesc: *TyDesc, data: *i8) {
|
|
|
|
((*tydesc).drop_glue)(0 as **TyDesc, data);
|
|
|
|
}
|
|
|
|
|
2012-08-21 17:32:30 -05:00
|
|
|
// Walk down a chunk, running the destructors for any objects stored
|
|
|
|
// in it.
|
2012-09-19 20:14:30 -05:00
|
|
|
unsafe fn destroy_chunk(chunk: &Chunk) {
|
2012-08-21 17:32:30 -05:00
|
|
|
let mut idx = 0;
|
2012-09-12 19:45:23 -05:00
|
|
|
let buf = vec::raw::to_ptr(chunk.data);
|
2012-08-21 17:32:30 -05:00
|
|
|
let fill = chunk.fill;
|
|
|
|
|
|
|
|
while idx < fill {
|
2013-04-20 09:27:16 -05:00
|
|
|
let tydesc_data: *uint = transmute(ptr::offset(buf, idx));
|
2012-08-21 17:32:30 -05:00
|
|
|
let (tydesc, is_done) = un_bitpack_tydesc_ptr(*tydesc_data);
|
2013-06-04 23:43:41 -05:00
|
|
|
let (size, align) = ((*tydesc).size, (*tydesc).align);
|
2012-08-21 17:32:30 -05:00
|
|
|
|
2013-06-20 04:39:49 -05:00
|
|
|
let after_tydesc = idx + sys::size_of::<*TyDesc>();
|
2012-08-21 17:32:30 -05:00
|
|
|
|
|
|
|
let start = round_up_to(after_tydesc, align);
|
|
|
|
|
|
|
|
//debug!("freeing object: idx = %u, size = %u, align = %u, done = %b",
|
|
|
|
// start, size, align, is_done);
|
|
|
|
if is_done {
|
2013-06-22 14:36:00 -05:00
|
|
|
call_drop_glue(tydesc, ptr::offset(buf, start) as *i8);
|
2012-08-21 17:32:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find where the next tydesc lives
|
2013-06-20 04:39:49 -05:00
|
|
|
idx = round_up_to(start + size, sys::pref_align_of::<*TyDesc>());
|
2012-08-21 17:32:30 -05:00
|
|
|
}
|
2012-08-02 18:00:45 -05:00
|
|
|
}
|
|
|
|
|
2012-08-21 17:32:30 -05:00
|
|
|
// We encode whether the object a tydesc describes has been
|
|
|
|
// initialized in the arena in the low bit of the tydesc pointer. This
|
|
|
|
// is necessary in order to properly do cleanup if a failure occurs
|
|
|
|
// during an initializer.
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-06-20 04:39:49 -05:00
|
|
|
unsafe fn bitpack_tydesc_ptr(p: *TyDesc, is_done: bool) -> uint {
|
2013-04-20 09:27:16 -05:00
|
|
|
let p_bits: uint = transmute(p);
|
2012-08-21 17:32:30 -05:00
|
|
|
p_bits | (is_done as uint)
|
|
|
|
}
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-06-20 04:39:49 -05:00
|
|
|
unsafe fn un_bitpack_tydesc_ptr(p: uint) -> (*TyDesc, bool) {
|
2013-04-20 09:27:16 -05:00
|
|
|
(transmute(p & !1), p & 1 == 1)
|
2012-08-21 17:32:30 -05:00
|
|
|
}
|
|
|
|
|
2013-05-31 17:17:22 -05:00
|
|
|
impl Arena {
|
2012-10-05 16:58:42 -05:00
|
|
|
// Functions for the POD part of the arena
|
2013-05-31 17:17:22 -05:00
|
|
|
fn alloc_pod_grow(&mut self, n_bytes: uint, align: uint) -> *u8 {
|
2012-10-05 16:58:42 -05:00
|
|
|
// Allocate a new chunk.
|
|
|
|
let chunk_size = at_vec::capacity(self.pod_head.data);
|
Replaces the free-standing functions in f32, &c.
The free-standing functions in f32, f64, i8, i16, i32, i64, u8, u16,
u32, u64, float, int, and uint are replaced with generic functions in
num instead.
If you were previously using any of those functions, just replace them
with the corresponding function with the same name in num.
Note: If you were using a function that corresponds to an operator, use
the operator instead.
2013-07-08 11:05:17 -05:00
|
|
|
let new_min_chunk_size = num::max(n_bytes, chunk_size);
|
2013-06-27 19:41:35 -05:00
|
|
|
self.chunks = @mut MutCons(self.pod_head, self.chunks);
|
2012-10-05 16:58:42 -05:00
|
|
|
self.pod_head =
|
|
|
|
chunk(uint::next_power_of_two(new_min_chunk_size + 1u), true);
|
|
|
|
|
|
|
|
return self.alloc_pod_inner(n_bytes, align);
|
|
|
|
}
|
|
|
|
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-05-31 17:17:22 -05:00
|
|
|
fn alloc_pod_inner(&mut self, n_bytes: uint, align: uint) -> *u8 {
|
2013-04-29 17:23:04 -05:00
|
|
|
unsafe {
|
2013-06-11 21:13:42 -05:00
|
|
|
let this = transmute_mut_region(self);
|
|
|
|
let start = round_up_to(this.pod_head.fill, align);
|
2013-04-29 17:23:04 -05:00
|
|
|
let end = start + n_bytes;
|
2013-06-11 21:13:42 -05:00
|
|
|
if end > at_vec::capacity(this.pod_head.data) {
|
|
|
|
return this.alloc_pod_grow(n_bytes, align);
|
2013-04-29 17:23:04 -05:00
|
|
|
}
|
2013-06-11 21:13:42 -05:00
|
|
|
this.pod_head.fill = end;
|
2012-10-05 16:58:42 -05:00
|
|
|
|
2013-04-29 17:23:04 -05:00
|
|
|
//debug!("idx = %u, size = %u, align = %u, fill = %u",
|
|
|
|
// start, n_bytes, align, head.fill);
|
2012-10-05 16:58:42 -05:00
|
|
|
|
2013-06-11 21:13:42 -05:00
|
|
|
ptr::offset(vec::raw::to_ptr(this.pod_head.data), start)
|
2012-10-05 16:58:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-05-31 17:17:22 -05:00
|
|
|
fn alloc_pod<'a, T>(&'a mut self, op: &fn() -> T) -> &'a T {
|
2012-10-05 16:58:42 -05:00
|
|
|
unsafe {
|
2013-06-20 04:39:49 -05:00
|
|
|
let tydesc = get_tydesc::<T>();
|
2012-10-05 16:58:42 -05:00
|
|
|
let ptr = self.alloc_pod_inner((*tydesc).size, (*tydesc).align);
|
2013-04-20 09:27:16 -05:00
|
|
|
let ptr: *mut T = transmute(ptr);
|
2013-05-09 14:49:14 -05:00
|
|
|
intrinsics::move_val_init(&mut (*ptr), op());
|
2013-04-20 09:27:16 -05:00
|
|
|
return transmute(ptr);
|
2012-10-05 16:58:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Functions for the non-POD part of the arena
|
2013-05-31 17:17:22 -05:00
|
|
|
fn alloc_nonpod_grow(&mut self, n_bytes: uint, align: uint)
|
|
|
|
-> (*u8, *u8) {
|
2012-10-05 16:58:42 -05:00
|
|
|
// Allocate a new chunk.
|
|
|
|
let chunk_size = at_vec::capacity(self.head.data);
|
Replaces the free-standing functions in f32, &c.
The free-standing functions in f32, f64, i8, i16, i32, i64, u8, u16,
u32, u64, float, int, and uint are replaced with generic functions in
num instead.
If you were previously using any of those functions, just replace them
with the corresponding function with the same name in num.
Note: If you were using a function that corresponds to an operator, use
the operator instead.
2013-07-08 11:05:17 -05:00
|
|
|
let new_min_chunk_size = num::max(n_bytes, chunk_size);
|
2013-06-27 19:41:35 -05:00
|
|
|
self.chunks = @mut MutCons(self.head, self.chunks);
|
2012-10-05 16:58:42 -05:00
|
|
|
self.head =
|
|
|
|
chunk(uint::next_power_of_two(new_min_chunk_size + 1u), false);
|
|
|
|
|
|
|
|
return self.alloc_nonpod_inner(n_bytes, align);
|
|
|
|
}
|
|
|
|
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-05-31 17:17:22 -05:00
|
|
|
fn alloc_nonpod_inner(&mut self, n_bytes: uint, align: uint)
|
|
|
|
-> (*u8, *u8) {
|
2013-04-29 17:23:04 -05:00
|
|
|
unsafe {
|
2013-06-25 21:19:38 -05:00
|
|
|
let start;
|
|
|
|
let end;
|
|
|
|
let tydesc_start;
|
|
|
|
let after_tydesc;
|
|
|
|
|
|
|
|
{
|
|
|
|
let head = transmute_mut_region(&mut self.head);
|
|
|
|
|
|
|
|
tydesc_start = head.fill;
|
|
|
|
after_tydesc = head.fill + sys::size_of::<*TyDesc>();
|
|
|
|
start = round_up_to(after_tydesc, align);
|
|
|
|
end = start + n_bytes;
|
|
|
|
}
|
2013-04-29 17:23:04 -05:00
|
|
|
|
2013-06-11 21:13:42 -05:00
|
|
|
if end > at_vec::capacity(self.head.data) {
|
2013-04-29 17:23:04 -05:00
|
|
|
return self.alloc_nonpod_grow(n_bytes, align);
|
|
|
|
}
|
2013-06-25 21:19:38 -05:00
|
|
|
|
|
|
|
let head = transmute_mut_region(&mut self.head);
|
2013-06-20 04:39:49 -05:00
|
|
|
head.fill = round_up_to(end, sys::pref_align_of::<*TyDesc>());
|
2012-10-05 16:58:42 -05:00
|
|
|
|
2013-04-29 17:23:04 -05:00
|
|
|
//debug!("idx = %u, size = %u, align = %u, fill = %u",
|
|
|
|
// start, n_bytes, align, head.fill);
|
2012-10-05 16:58:42 -05:00
|
|
|
|
2013-06-11 21:13:42 -05:00
|
|
|
let buf = vec::raw::to_ptr(self.head.data);
|
2012-10-05 16:58:42 -05:00
|
|
|
return (ptr::offset(buf, tydesc_start), ptr::offset(buf, start));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-05-31 17:17:22 -05:00
|
|
|
fn alloc_nonpod<'a, T>(&'a mut self, op: &fn() -> T) -> &'a T {
|
2012-10-05 16:58:42 -05:00
|
|
|
unsafe {
|
2013-06-20 04:39:49 -05:00
|
|
|
let tydesc = get_tydesc::<T>();
|
2012-10-05 16:58:42 -05:00
|
|
|
let (ty_ptr, ptr) =
|
|
|
|
self.alloc_nonpod_inner((*tydesc).size, (*tydesc).align);
|
2013-04-20 09:27:16 -05:00
|
|
|
let ty_ptr: *mut uint = transmute(ty_ptr);
|
|
|
|
let ptr: *mut T = transmute(ptr);
|
2012-10-05 16:58:42 -05:00
|
|
|
// Write in our tydesc along with a bit indicating that it
|
|
|
|
// has *not* been initialized yet.
|
2013-04-20 09:27:16 -05:00
|
|
|
*ty_ptr = transmute(tydesc);
|
2012-10-05 16:58:42 -05:00
|
|
|
// Actually initialize it
|
2013-05-09 14:49:14 -05:00
|
|
|
intrinsics::move_val_init(&mut(*ptr), op());
|
2012-10-05 16:58:42 -05:00
|
|
|
// Now that we are done, update the tydesc to indicate that
|
|
|
|
// the object is there.
|
|
|
|
*ty_ptr = bitpack_tydesc_ptr(tydesc, true);
|
|
|
|
|
2013-04-20 09:27:16 -05:00
|
|
|
return transmute(ptr);
|
2012-10-05 16:58:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The external interface
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-06-16 10:50:16 -05:00
|
|
|
pub fn alloc<'a, T>(&'a self, op: &fn() -> T) -> &'a T {
|
2013-04-10 15:14:06 -05:00
|
|
|
unsafe {
|
2013-04-29 17:23:04 -05:00
|
|
|
// XXX: Borrow check
|
2013-06-16 10:50:16 -05:00
|
|
|
let this = transmute_mut(self);
|
|
|
|
if intrinsics::needs_drop::<T>() {
|
|
|
|
this.alloc_nonpod(op)
|
|
|
|
} else {
|
|
|
|
this.alloc_pod(op)
|
2013-04-10 15:14:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-10-05 16:58:42 -05:00
|
|
|
}
|
2012-03-20 21:06:04 -05:00
|
|
|
|
2012-08-21 17:32:30 -05:00
|
|
|
#[test]
|
|
|
|
fn test_arena_destructors() {
|
2013-06-16 10:50:16 -05:00
|
|
|
let arena = Arena();
|
2012-08-21 17:32:30 -05:00
|
|
|
for uint::range(0, 10) |i| {
|
|
|
|
// Arena allocate something with drop glue to make sure it
|
|
|
|
// doesn't leak.
|
|
|
|
do arena.alloc { @i };
|
|
|
|
// Allocate something with funny size and alignment, to keep
|
|
|
|
// things interesting.
|
2012-10-09 23:28:04 -05:00
|
|
|
do arena.alloc { [0u8, 1u8, 2u8] };
|
2012-08-21 17:32:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-29 17:23:04 -05:00
|
|
|
#[test]
|
|
|
|
#[should_fail]
|
|
|
|
#[ignore(cfg(windows))]
|
2012-08-21 17:32:30 -05:00
|
|
|
fn test_arena_destructors_fail() {
|
2013-06-16 10:50:16 -05:00
|
|
|
let arena = Arena();
|
2012-08-21 17:32:30 -05:00
|
|
|
// Put some stuff in the arena.
|
|
|
|
for uint::range(0, 10) |i| {
|
|
|
|
// Arena allocate something with drop glue to make sure it
|
|
|
|
// doesn't leak.
|
|
|
|
do arena.alloc { @i };
|
|
|
|
// Allocate something with funny size and alignment, to keep
|
|
|
|
// things interesting.
|
2012-10-09 23:28:04 -05:00
|
|
|
do arena.alloc { [0u8, 1u8, 2u8] };
|
2012-08-21 17:32:30 -05:00
|
|
|
}
|
|
|
|
// Now, fail while allocating
|
|
|
|
do arena.alloc::<@int> {
|
|
|
|
// Now fail.
|
2013-02-11 21:26:38 -06:00
|
|
|
fail!();
|
2012-08-21 17:32:30 -05:00
|
|
|
};
|
|
|
|
}
|