]> git.lizzy.rs Git - rust.git/blob - src/libarena/lib.rs
dbebb8fb2bc680edd1ec61b4ac64a7b69a8967c0
[rust.git] / src / libarena / lib.rs
1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10 //
11 //! The arena, a fast but limited type of allocator.
12 //!
13 //! Arenas are a type of allocator that destroy the objects within, all at
14 //! once, once the arena itself is destroyed. They do not support deallocation
15 //! of individual objects while the arena itself is still alive. The benefit
16 //! of an arena is very fast allocation; just a pointer bump.
17
18 #[crate_id = "arena#0.10-pre"];
19 #[crate_type = "rlib"];
20 #[crate_type = "dylib"];
21 #[license = "MIT/ASL2"];
22 #[doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
23       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
24       html_root_url = "http://static.rust-lang.org/doc/master")];
25 #[allow(missing_doc)];
26 #[feature(managed_boxes)];
27 #[allow(deprecated_owned_vector)]; // NOTE: remove after stage0
28
29 extern crate collections;
30
31 use collections::list::{List, Cons, Nil};
32
33 use std::cast::{transmute, transmute_mut, transmute_mut_region};
34 use std::cast;
35 use std::cell::{Cell, RefCell};
36 use std::mem;
37 use std::ptr::read;
38 use std::cmp;
39 use std::num;
40 use std::kinds::marker;
41 use std::rc::Rc;
42 use std::rt::global_heap;
43 use std::intrinsics::{TyDesc, get_tydesc};
44 use std::intrinsics;
45 use std::slice;
46
47 // The way arena uses arrays is really deeply awful. The arrays are
48 // allocated, and have capacities reserved, but the fill for the array
49 // will always stay at 0.
50 #[deriving(Clone, Eq)]
51 struct Chunk {
52     data: Rc<RefCell<~[u8]>>,
53     fill: Cell<uint>,
54     is_pod: Cell<bool>,
55 }
56 impl Chunk {
57     fn capacity(&self) -> uint {
58         self.data.deref().borrow().get().capacity()
59     }
60
61     unsafe fn as_ptr(&self) -> *u8 {
62         self.data.deref().borrow().get().as_ptr()
63     }
64 }
65
66 // Arenas are used to quickly allocate objects that share a
67 // lifetime. The arena uses ~[u8] vectors as a backing store to
68 // allocate objects from. For each allocated object, the arena stores
69 // a pointer to the type descriptor followed by the
70 // object. (Potentially with alignment padding after each of them.)
71 // When the arena is destroyed, it iterates through all of its chunks,
72 // and uses the tydesc information to trace through the objects,
73 // calling the destructors on them.
74 // One subtle point that needs to be addressed is how to handle
75 // failures while running the user provided initializer function. It
76 // is important to not run the destructor on uninitialized objects, but
77 // how to detect them is somewhat subtle. Since alloc() can be invoked
78 // recursively, it is not sufficient to simply exclude the most recent
79 // object. To solve this without requiring extra space, we use the low
80 // order bit of the tydesc pointer to encode whether the object it
81 // describes has been fully initialized.
82
83 // As an optimization, objects with destructors are stored in
84 // different chunks than objects without destructors. This reduces
85 // overhead when initializing plain-old-data and means we don't need
86 // to waste time running the destructors of POD.
87 pub struct Arena {
88     // The head is separated out from the list as a unbenchmarked
89     // microoptimization, to avoid needing to case on the list to
90     // access the head.
91     priv head: Chunk,
92     priv pod_head: Chunk,
93     priv chunks: RefCell<@List<Chunk>>,
94     priv no_freeze: marker::NoFreeze,
95 }
96
97 impl Arena {
98     pub fn new() -> Arena {
99         Arena::new_with_size(32u)
100     }
101
102     pub fn new_with_size(initial_size: uint) -> Arena {
103         Arena {
104             head: chunk(initial_size, false),
105             pod_head: chunk(initial_size, true),
106             chunks: RefCell::new(@Nil),
107             no_freeze: marker::NoFreeze,
108         }
109     }
110 }
111
112 fn chunk(size: uint, is_pod: bool) -> Chunk {
113     Chunk {
114         data: Rc::new(RefCell::new(slice::with_capacity(size))),
115         fill: Cell::new(0u),
116         is_pod: Cell::new(is_pod),
117     }
118 }
119
120 #[unsafe_destructor]
121 impl Drop for Arena {
122     fn drop(&mut self) {
123         unsafe {
124             destroy_chunk(&self.head);
125             for chunk in self.chunks.get().iter() {
126                 if !chunk.is_pod.get() {
127                     destroy_chunk(chunk);
128                 }
129             }
130         }
131     }
132 }
133
134 #[inline]
135 fn round_up(base: uint, align: uint) -> uint {
136     (base.checked_add(&(align - 1))).unwrap() & !(&(align - 1))
137 }
138
139 // Walk down a chunk, running the destructors for any objects stored
140 // in it.
141 unsafe fn destroy_chunk(chunk: &Chunk) {
142     let mut idx = 0;
143     let buf = chunk.as_ptr();
144     let fill = chunk.fill.get();
145
146     while idx < fill {
147         let tydesc_data: *uint = transmute(buf.offset(idx as int));
148         let (tydesc, is_done) = un_bitpack_tydesc_ptr(*tydesc_data);
149         let (size, align) = ((*tydesc).size, (*tydesc).align);
150
151         let after_tydesc = idx + mem::size_of::<*TyDesc>();
152
153         let start = round_up(after_tydesc, align);
154
155         //debug!("freeing object: idx = {}, size = {}, align = {}, done = {}",
156         //       start, size, align, is_done);
157         if is_done {
158             ((*tydesc).drop_glue)(buf.offset(start as int) as *i8);
159         }
160
161         // Find where the next tydesc lives
162         idx = round_up(start + size, mem::pref_align_of::<*TyDesc>());
163     }
164 }
165
166 // We encode whether the object a tydesc describes has been
167 // initialized in the arena in the low bit of the tydesc pointer. This
168 // is necessary in order to properly do cleanup if a failure occurs
169 // during an initializer.
170 #[inline]
171 fn bitpack_tydesc_ptr(p: *TyDesc, is_done: bool) -> uint {
172     p as uint | (is_done as uint)
173 }
174 #[inline]
175 fn un_bitpack_tydesc_ptr(p: uint) -> (*TyDesc, bool) {
176     ((p & !1) as *TyDesc, p & 1 == 1)
177 }
178
179 impl Arena {
180     fn chunk_size(&self) -> uint {
181         self.pod_head.capacity()
182     }
183     // Functions for the POD part of the arena
184     fn alloc_pod_grow(&mut self, n_bytes: uint, align: uint) -> *u8 {
185         // Allocate a new chunk.
186         let new_min_chunk_size = cmp::max(n_bytes, self.chunk_size());
187         self.chunks.set(@Cons(self.pod_head.clone(), self.chunks.get()));
188         self.pod_head =
189             chunk(num::next_power_of_two(new_min_chunk_size + 1u), true);
190
191         return self.alloc_pod_inner(n_bytes, align);
192     }
193
194     #[inline]
195     fn alloc_pod_inner(&mut self, n_bytes: uint, align: uint) -> *u8 {
196         unsafe {
197             let this = transmute_mut_region(self);
198             let start = round_up(this.pod_head.fill.get(), align);
199             let end = start + n_bytes;
200             if end > self.chunk_size() {
201                 return this.alloc_pod_grow(n_bytes, align);
202             }
203             this.pod_head.fill.set(end);
204
205             //debug!("idx = {}, size = {}, align = {}, fill = {}",
206             //       start, n_bytes, align, head.fill.get());
207
208             this.pod_head.as_ptr().offset(start as int)
209         }
210     }
211
212     #[inline]
213     fn alloc_pod<'a, T>(&'a mut self, op: || -> T) -> &'a T {
214         unsafe {
215             let ptr = self.alloc_pod_inner(mem::size_of::<T>(), mem::min_align_of::<T>());
216             let ptr: *mut T = transmute(ptr);
217             mem::move_val_init(&mut (*ptr), op());
218             return transmute(ptr);
219         }
220     }
221
222     // Functions for the non-POD part of the arena
223     fn alloc_nonpod_grow(&mut self, n_bytes: uint, align: uint)
224                          -> (*u8, *u8) {
225         // Allocate a new chunk.
226         let new_min_chunk_size = cmp::max(n_bytes, self.chunk_size());
227         self.chunks.set(@Cons(self.head.clone(), self.chunks.get()));
228         self.head =
229             chunk(num::next_power_of_two(new_min_chunk_size + 1u), false);
230
231         return self.alloc_nonpod_inner(n_bytes, align);
232     }
233
234     #[inline]
235     fn alloc_nonpod_inner(&mut self, n_bytes: uint, align: uint)
236                           -> (*u8, *u8) {
237         unsafe {
238             let start;
239             let end;
240             let tydesc_start;
241             let after_tydesc;
242
243             {
244                 let head = transmute_mut_region(&mut self.head);
245
246                 tydesc_start = head.fill.get();
247                 after_tydesc = head.fill.get() + mem::size_of::<*TyDesc>();
248                 start = round_up(after_tydesc, align);
249                 end = start + n_bytes;
250             }
251
252             if end > self.head.capacity() {
253                 return self.alloc_nonpod_grow(n_bytes, align);
254             }
255
256             let head = transmute_mut_region(&mut self.head);
257             head.fill.set(round_up(end, mem::pref_align_of::<*TyDesc>()));
258
259             //debug!("idx = {}, size = {}, align = {}, fill = {}",
260             //       start, n_bytes, align, head.fill);
261
262             let buf = self.head.as_ptr();
263             return (buf.offset(tydesc_start as int), buf.offset(start as int));
264         }
265     }
266
267     #[inline]
268     fn alloc_nonpod<'a, T>(&'a mut self, op: || -> T) -> &'a T {
269         unsafe {
270             let tydesc = get_tydesc::<T>();
271             let (ty_ptr, ptr) =
272                 self.alloc_nonpod_inner(mem::size_of::<T>(), mem::min_align_of::<T>());
273             let ty_ptr: *mut uint = transmute(ty_ptr);
274             let ptr: *mut T = transmute(ptr);
275             // Write in our tydesc along with a bit indicating that it
276             // has *not* been initialized yet.
277             *ty_ptr = transmute(tydesc);
278             // Actually initialize it
279             mem::move_val_init(&mut(*ptr), op());
280             // Now that we are done, update the tydesc to indicate that
281             // the object is there.
282             *ty_ptr = bitpack_tydesc_ptr(tydesc, true);
283
284             return transmute(ptr);
285         }
286     }
287
288     // The external interface
289     #[inline]
290     pub fn alloc<'a, T>(&'a self, op: || -> T) -> &'a T {
291         unsafe {
292             // FIXME: Borrow check
293             let this = transmute_mut(self);
294             if intrinsics::needs_drop::<T>() {
295                 this.alloc_nonpod(op)
296             } else {
297                 this.alloc_pod(op)
298             }
299         }
300     }
301 }
302
303 #[test]
304 fn test_arena_destructors() {
305     let arena = Arena::new();
306     for i in range(0u, 10) {
307         // Arena allocate something with drop glue to make sure it
308         // doesn't leak.
309         arena.alloc(|| @i);
310         // Allocate something with funny size and alignment, to keep
311         // things interesting.
312         arena.alloc(|| [0u8, 1u8, 2u8]);
313     }
314 }
315
316 #[test]
317 #[should_fail]
318 fn test_arena_destructors_fail() {
319     let arena = Arena::new();
320     // Put some stuff in the arena.
321     for i in range(0u, 10) {
322         // Arena allocate something with drop glue to make sure it
323         // doesn't leak.
324         arena.alloc(|| { @i });
325         // Allocate something with funny size and alignment, to keep
326         // things interesting.
327         arena.alloc(|| { [0u8, 1u8, 2u8] });
328     }
329     // Now, fail while allocating
330     arena.alloc::<@int>(|| {
331         // Now fail.
332         fail!();
333     });
334 }
335
336 /// An arena that can hold objects of only one type.
337 ///
338 /// Safety note: Modifying objects in the arena that have already had their
339 /// `drop` destructors run can cause leaks, because the destructor will not
340 /// run again for these objects.
341 pub struct TypedArena<T> {
342     /// A pointer to the next object to be allocated.
343     priv ptr: *T,
344
345     /// A pointer to the end of the allocated area. When this pointer is
346     /// reached, a new chunk is allocated.
347     priv end: *T,
348
349     /// A pointer to the first arena segment.
350     priv first: Option<~TypedArenaChunk<T>>,
351 }
352
353 struct TypedArenaChunk<T> {
354     /// Pointer to the next arena segment.
355     next: Option<~TypedArenaChunk<T>>,
356
357     /// The number of elements that this chunk can hold.
358     capacity: uint,
359
360     // Objects follow here, suitably aligned.
361 }
362
363 impl<T> TypedArenaChunk<T> {
364     #[inline]
365     fn new(next: Option<~TypedArenaChunk<T>>, capacity: uint) -> ~TypedArenaChunk<T> {
366         let mut size = mem::size_of::<TypedArenaChunk<T>>();
367         size = round_up(size, mem::min_align_of::<T>());
368         let elem_size = mem::size_of::<T>();
369         let elems_size = elem_size.checked_mul(&capacity).unwrap();
370         size = size.checked_add(&elems_size).unwrap();
371
372         let mut chunk = unsafe {
373             let chunk = global_heap::exchange_malloc(size);
374             let mut chunk: ~TypedArenaChunk<T> = cast::transmute(chunk);
375             mem::move_val_init(&mut chunk.next, next);
376             chunk
377         };
378
379         chunk.capacity = capacity;
380         chunk
381     }
382
383     /// Destroys this arena chunk. If the type descriptor is supplied, the
384     /// drop glue is called; otherwise, drop glue is not called.
385     #[inline]
386     unsafe fn destroy(&mut self, len: uint) {
387         // Destroy all the allocated objects.
388         if intrinsics::needs_drop::<T>() {
389             let mut start = self.start();
390             for _ in range(0, len) {
391                 read(start as *T); // run the destructor on the pointer
392                 start = start.offset(mem::size_of::<T>() as int)
393             }
394         }
395
396         // Destroy the next chunk.
397         let next_opt = mem::replace(&mut self.next, None);
398         match next_opt {
399             None => {}
400             Some(mut next) => {
401                 // We assume that the next chunk is completely filled.
402                 next.destroy(next.capacity)
403             }
404         }
405     }
406
407     // Returns a pointer to the first allocated object.
408     #[inline]
409     fn start(&self) -> *u8 {
410         let this: *TypedArenaChunk<T> = self;
411         unsafe {
412             cast::transmute(round_up(this.offset(1) as uint, mem::min_align_of::<T>()))
413         }
414     }
415
416     // Returns a pointer to the end of the allocated space.
417     #[inline]
418     fn end(&self) -> *u8 {
419         unsafe {
420             let size = mem::size_of::<T>().checked_mul(&self.capacity).unwrap();
421             self.start().offset(size as int)
422         }
423     }
424 }
425
426 impl<T> TypedArena<T> {
427     /// Creates a new arena with preallocated space for 8 objects.
428     #[inline]
429     pub fn new() -> TypedArena<T> {
430         TypedArena::with_capacity(8)
431     }
432
433     /// Creates a new arena with preallocated space for the given number of
434     /// objects.
435     #[inline]
436     pub fn with_capacity(capacity: uint) -> TypedArena<T> {
437         let chunk = TypedArenaChunk::<T>::new(None, capacity);
438         TypedArena {
439             ptr: chunk.start() as *T,
440             end: chunk.end() as *T,
441             first: Some(chunk),
442         }
443     }
444
445     /// Allocates an object into this arena.
446     #[inline]
447     pub fn alloc<'a>(&'a self, object: T) -> &'a T {
448         unsafe {
449             let this = cast::transmute_mut(self);
450             if this.ptr == this.end {
451                 this.grow()
452             }
453
454             let ptr: &'a mut T = cast::transmute(this.ptr);
455             mem::move_val_init(ptr, object);
456             this.ptr = this.ptr.offset(1);
457             let ptr: &'a T = ptr;
458             ptr
459         }
460     }
461
462     /// Grows the arena.
463     #[inline(never)]
464     fn grow(&mut self) {
465         let chunk = self.first.take_unwrap();
466         let new_capacity = chunk.capacity.checked_mul(&2).unwrap();
467         let chunk = TypedArenaChunk::<T>::new(Some(chunk), new_capacity);
468         self.ptr = chunk.start() as *T;
469         self.end = chunk.end() as *T;
470         self.first = Some(chunk)
471     }
472 }
473
474 #[unsafe_destructor]
475 impl<T> Drop for TypedArena<T> {
476     fn drop(&mut self) {
477         // Determine how much was filled.
478         let start = self.first.get_ref().start() as uint;
479         let end = self.ptr as uint;
480         let diff = (end - start) / mem::size_of::<T>();
481
482         // Pass that to the `destroy` method.
483         unsafe {
484             self.first.get_mut_ref().destroy(diff)
485         }
486     }
487 }
488
489 #[cfg(test)]
490 mod tests {
491     extern crate test;
492     use self::test::BenchHarness;
493     use super::{Arena, TypedArena};
494
495     struct Point {
496         x: int,
497         y: int,
498         z: int,
499     }
500
501     #[test]
502     pub fn test_pod() {
503         let arena = TypedArena::new();
504         for _ in range(0, 100000) {
505             arena.alloc(Point {
506                 x: 1,
507                 y: 2,
508                 z: 3,
509             });
510         }
511     }
512
513     #[bench]
514     pub fn bench_pod(bh: &mut BenchHarness) {
515         let arena = TypedArena::new();
516         bh.iter(|| {
517             arena.alloc(Point {
518                 x: 1,
519                 y: 2,
520                 z: 3,
521             })
522         })
523     }
524
525     #[bench]
526     pub fn bench_pod_nonarena(bh: &mut BenchHarness) {
527         bh.iter(|| {
528             ~Point {
529                 x: 1,
530                 y: 2,
531                 z: 3,
532             }
533         })
534     }
535
536     #[bench]
537     pub fn bench_pod_old_arena(bh: &mut BenchHarness) {
538         let arena = Arena::new();
539         bh.iter(|| {
540             arena.alloc(|| {
541                 Point {
542                     x: 1,
543                     y: 2,
544                     z: 3,
545                 }
546             })
547         })
548     }
549
550     struct Nonpod {
551         string: ~str,
552         array: ~[int],
553     }
554
555     #[test]
556     pub fn test_nonpod() {
557         let arena = TypedArena::new();
558         for _ in range(0, 100000) {
559             arena.alloc(Nonpod {
560                 string: ~"hello world",
561                 array: ~[ 1, 2, 3, 4, 5 ],
562             });
563         }
564     }
565
566     #[bench]
567     pub fn bench_nonpod(bh: &mut BenchHarness) {
568         let arena = TypedArena::new();
569         bh.iter(|| {
570             arena.alloc(Nonpod {
571                 string: ~"hello world",
572                 array: ~[ 1, 2, 3, 4, 5 ],
573             })
574         })
575     }
576
577     #[bench]
578     pub fn bench_nonpod_nonarena(bh: &mut BenchHarness) {
579         bh.iter(|| {
580             ~Nonpod {
581                 string: ~"hello world",
582                 array: ~[ 1, 2, 3, 4, 5 ],
583             }
584         })
585     }
586
587     #[bench]
588     pub fn bench_nonpod_old_arena(bh: &mut BenchHarness) {
589         let arena = Arena::new();
590         bh.iter(|| {
591             arena.alloc(|| Nonpod {
592                 string: ~"hello world",
593                 array: ~[ 1, 2, 3, 4, 5 ],
594             })
595         })
596     }
597 }