]> git.lizzy.rs Git - rust.git/blob - src/libextra/arena.rs
Change finalize -> drop.
[rust.git] / src / libextra / arena.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 // Dynamic arenas.
12
13 // Arenas are used to quickly allocate objects that share a
14 // lifetime. The arena uses ~[u8] vectors as a backing store to
15 // allocate objects from. For each allocated object, the arena stores
16 // a pointer to the type descriptor followed by the
17 // object. (Potentially with alignment padding after each of them.)
18 // When the arena is destroyed, it iterates through all of its chunks,
19 // and uses the tydesc information to trace through the objects,
20 // calling the destructors on them.
21 // One subtle point that needs to be addressed is how to handle
22 // failures while running the user provided initializer function. It
23 // is important to not run the destructor on uninitialized objects, but
24 // how to detect them is somewhat subtle. Since alloc() can be invoked
25 // recursively, it is not sufficient to simply exclude the most recent
26 // object. To solve this without requiring extra space, we use the low
27 // order bit of the tydesc pointer to encode whether the object it
28 // describes has been fully initialized.
29
30 // As an optimization, objects with destructors are stored in
31 // different chunks than objects without destructors. This reduces
32 // overhead when initializing plain-old-data and means we don't need
33 // to waste time running the destructors of POD.
34
35 #[allow(missing_doc)];
36
37 use core::prelude::*;
38
39 use list::{MutList, MutCons, MutNil};
40
41 use core::at_vec;
42 use core::cast::{transmute, transmute_mut, transmute_mut_region};
43 use core::cast;
44 use core::ptr;
45 use core::sys;
46 use core::uint;
47 use core::vec;
48 use core::unstable::intrinsics;
49 use core::unstable::intrinsics::{TyDesc};
50
51 #[cfg(not(stage0))]
52 use core::unstable::intrinsics::{get_tydesc};
53
54 #[cfg(stage0)]
55 unsafe fn get_tydesc<T>() -> *TyDesc {
56     intrinsics::get_tydesc::<T>() as *TyDesc
57 }
58
59 // The way arena uses arrays is really deeply awful. The arrays are
60 // allocated, and have capacities reserved, but the fill for the array
61 // will always stay at 0.
62 struct Chunk {
63     data: @[u8],
64     fill: uint,
65     is_pod: bool,
66 }
67
68 #[mutable]
69 pub struct Arena {
70     // The head is separated out from the list as a unbenchmarked
71     // microoptimization, to avoid needing to case on the list to
72     // access the head.
73     priv head: Chunk,
74     priv pod_head: Chunk,
75     priv chunks: @mut MutList<Chunk>,
76 }
77
78 #[unsafe_destructor]
79 impl Drop for Arena {
80     fn drop(&self) {
81         unsafe {
82             destroy_chunk(&self.head);
83             for self.chunks.each |chunk| {
84                 if !chunk.is_pod {
85                     destroy_chunk(chunk);
86                 }
87             }
88         }
89     }
90 }
91
92 fn chunk(size: uint, is_pod: bool) -> Chunk {
93     let mut v: @[u8] = @[];
94     unsafe { at_vec::raw::reserve(&mut v, size); }
95     Chunk {
96         data: unsafe { cast::transmute(v) },
97         fill: 0u,
98         is_pod: is_pod,
99     }
100 }
101
102 pub fn arena_with_size(initial_size: uint) -> Arena {
103     Arena {
104         head: chunk(initial_size, false),
105         pod_head: chunk(initial_size, true),
106         chunks: @mut MutNil,
107     }
108 }
109
110 pub fn Arena() -> Arena {
111     arena_with_size(32u)
112 }
113
114 #[inline]
115 fn round_up_to(base: uint, align: uint) -> uint {
116     (base + (align - 1)) & !(align - 1)
117 }
118
119 #[inline]
120 #[cfg(not(stage0))]
121 unsafe fn call_drop_glue(tydesc: *TyDesc, data: *i8) {
122     // This function should be inlined when stage0 is gone
123     ((*tydesc).drop_glue)(data);
124 }
125
126 #[inline]
127 #[cfg(stage0)]
128 unsafe fn call_drop_glue(tydesc: *TyDesc, data: *i8) {
129     ((*tydesc).drop_glue)(0 as **TyDesc, data);
130 }
131
132 // Walk down a chunk, running the destructors for any objects stored
133 // in it.
134 unsafe fn destroy_chunk(chunk: &Chunk) {
135     let mut idx = 0;
136     let buf = vec::raw::to_ptr(chunk.data);
137     let fill = chunk.fill;
138
139     while idx < fill {
140         let tydesc_data: *uint = transmute(ptr::offset(buf, idx));
141         let (tydesc, is_done) = un_bitpack_tydesc_ptr(*tydesc_data);
142         let (size, align) = ((*tydesc).size, (*tydesc).align);
143
144         let after_tydesc = idx + sys::size_of::<*TyDesc>();
145
146         let start = round_up_to(after_tydesc, align);
147
148         //debug!("freeing object: idx = %u, size = %u, align = %u, done = %b",
149         //       start, size, align, is_done);
150         if is_done {
151             call_drop_glue(tydesc, ptr::offset(buf, start) as *i8);
152         }
153
154         // Find where the next tydesc lives
155         idx = round_up_to(start + size, sys::pref_align_of::<*TyDesc>());
156     }
157 }
158
159 // We encode whether the object a tydesc describes has been
160 // initialized in the arena in the low bit of the tydesc pointer. This
161 // is necessary in order to properly do cleanup if a failure occurs
162 // during an initializer.
163 #[inline]
164 unsafe fn bitpack_tydesc_ptr(p: *TyDesc, is_done: bool) -> uint {
165     let p_bits: uint = transmute(p);
166     p_bits | (is_done as uint)
167 }
168 #[inline]
169 unsafe fn un_bitpack_tydesc_ptr(p: uint) -> (*TyDesc, bool) {
170     (transmute(p & !1), p & 1 == 1)
171 }
172
173 impl Arena {
174     // Functions for the POD part of the arena
175     fn alloc_pod_grow(&mut self, n_bytes: uint, align: uint) -> *u8 {
176         // Allocate a new chunk.
177         let chunk_size = at_vec::capacity(self.pod_head.data);
178         let new_min_chunk_size = uint::max(n_bytes, chunk_size);
179         self.chunks = @mut MutCons(copy self.pod_head, self.chunks);
180         self.pod_head =
181             chunk(uint::next_power_of_two(new_min_chunk_size + 1u), true);
182
183         return self.alloc_pod_inner(n_bytes, align);
184     }
185
186     #[inline]
187     fn alloc_pod_inner(&mut self, n_bytes: uint, align: uint) -> *u8 {
188         unsafe {
189             // XXX: Borrow check
190             let head = transmute_mut_region(&mut self.pod_head);
191
192             let start = round_up_to(head.fill, align);
193             let end = start + n_bytes;
194             if end > at_vec::capacity(head.data) {
195                 return self.alloc_pod_grow(n_bytes, align);
196             }
197             head.fill = end;
198
199             //debug!("idx = %u, size = %u, align = %u, fill = %u",
200             //       start, n_bytes, align, head.fill);
201
202             ptr::offset(vec::raw::to_ptr(head.data), start)
203         }
204     }
205
206     #[inline]
207     fn alloc_pod<'a, T>(&'a mut self, op: &fn() -> T) -> &'a T {
208         unsafe {
209             let tydesc = get_tydesc::<T>();
210             let ptr = self.alloc_pod_inner((*tydesc).size, (*tydesc).align);
211             let ptr: *mut T = transmute(ptr);
212             intrinsics::move_val_init(&mut (*ptr), op());
213             return transmute(ptr);
214         }
215     }
216
217     // Functions for the non-POD part of the arena
218     fn alloc_nonpod_grow(&mut self, n_bytes: uint, align: uint)
219                          -> (*u8, *u8) {
220         // Allocate a new chunk.
221         let chunk_size = at_vec::capacity(self.head.data);
222         let new_min_chunk_size = uint::max(n_bytes, chunk_size);
223         self.chunks = @mut MutCons(copy self.head, self.chunks);
224         self.head =
225             chunk(uint::next_power_of_two(new_min_chunk_size + 1u), false);
226
227         return self.alloc_nonpod_inner(n_bytes, align);
228     }
229
230     #[inline]
231     fn alloc_nonpod_inner(&mut self, n_bytes: uint, align: uint)
232                           -> (*u8, *u8) {
233         unsafe {
234             let head = transmute_mut_region(&mut self.head);
235
236             let tydesc_start = head.fill;
237             let after_tydesc = head.fill + sys::size_of::<*TyDesc>();
238             let start = round_up_to(after_tydesc, align);
239             let end = start + n_bytes;
240             if end > at_vec::capacity(head.data) {
241                 return self.alloc_nonpod_grow(n_bytes, align);
242             }
243             head.fill = round_up_to(end, sys::pref_align_of::<*TyDesc>());
244
245             //debug!("idx = %u, size = %u, align = %u, fill = %u",
246             //       start, n_bytes, align, head.fill);
247
248             let buf = vec::raw::to_ptr(head.data);
249             return (ptr::offset(buf, tydesc_start), ptr::offset(buf, start));
250         }
251     }
252
253     #[inline]
254     fn alloc_nonpod<'a, T>(&'a mut self, op: &fn() -> T) -> &'a T {
255         unsafe {
256             let tydesc = get_tydesc::<T>();
257             let (ty_ptr, ptr) =
258                 self.alloc_nonpod_inner((*tydesc).size, (*tydesc).align);
259             let ty_ptr: *mut uint = transmute(ty_ptr);
260             let ptr: *mut T = transmute(ptr);
261             // Write in our tydesc along with a bit indicating that it
262             // has *not* been initialized yet.
263             *ty_ptr = transmute(tydesc);
264             // Actually initialize it
265             intrinsics::move_val_init(&mut(*ptr), op());
266             // Now that we are done, update the tydesc to indicate that
267             // the object is there.
268             *ty_ptr = bitpack_tydesc_ptr(tydesc, true);
269
270             return transmute(ptr);
271         }
272     }
273
274     // The external interface
275     #[inline]
276     pub fn alloc<'a, T>(&'a self, op: &fn() -> T) -> &'a T {
277         unsafe {
278             // XXX: Borrow check
279             let this = transmute_mut(self);
280             if intrinsics::needs_drop::<T>() {
281                 this.alloc_nonpod(op)
282             } else {
283                 this.alloc_pod(op)
284             }
285         }
286     }
287 }
288
289 #[test]
290 fn test_arena_destructors() {
291     let arena = Arena();
292     for uint::range(0, 10) |i| {
293         // Arena allocate something with drop glue to make sure it
294         // doesn't leak.
295         do arena.alloc { @i };
296         // Allocate something with funny size and alignment, to keep
297         // things interesting.
298         do arena.alloc { [0u8, 1u8, 2u8] };
299     }
300 }
301
302 #[test]
303 #[should_fail]
304 #[ignore(cfg(windows))]
305 fn test_arena_destructors_fail() {
306     let arena = Arena();
307     // Put some stuff in the arena.
308     for uint::range(0, 10) |i| {
309         // Arena allocate something with drop glue to make sure it
310         // doesn't leak.
311         do arena.alloc { @i };
312         // Allocate something with funny size and alignment, to keep
313         // things interesting.
314         do arena.alloc { [0u8, 1u8, 2u8] };
315     }
316     // Now, fail while allocating
317     do arena.alloc::<@int> {
318         // Now fail.
319         fail!();
320     };
321 }