]> git.lizzy.rs Git - rust.git/blob - src/libstd/arena.rs
bea7935d5c3a4bc521a6b3ebfaf7427e75aa9cbf
[rust.git] / src / libstd / arena.rs
1 // Copyright 2012 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 uninitalized 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 use list::{MutList, MutCons, MutNil};
36 use list;
37
38 use core::at_vec;
39 use core::cast::{transmute, transmute_mut_region};
40 use core::cast;
41 use core::libc::size_t;
42 use core::ptr;
43 use core::sys::TypeDesc;
44 use core::sys;
45 use core::uint;
46 use core::vec;
47
48 pub mod rusti {
49     #[abi = "rust-intrinsic"]
50     pub extern "rust-intrinsic" {
51         fn move_val_init<T>(dst: &mut T, +src: T);
52         fn needs_drop<T>() -> bool;
53     }
54 }
55
56 pub mod rustrt {
57     use core::libc::size_t;
58     use core::sys::TypeDesc;
59
60     pub extern {
61         #[rust_stack]
62         unsafe fn rust_call_tydesc_glue(root: *u8,
63                                         tydesc: *TypeDesc,
64                                         field: size_t);
65     }
66 }
67
68 // This probably belongs somewhere else. Needs to be kept in sync with
69 // changes to glue...
70 static tydesc_drop_glue_index: size_t = 3 as size_t;
71
72 // The way arena uses arrays is really deeply awful. The arrays are
73 // allocated, and have capacities reserved, but the fill for the array
74 // will always stay at 0.
75 struct Chunk {
76     data: @[u8],
77     fill: uint,
78     is_pod: bool,
79 }
80
81 pub struct Arena {
82     // The head is seperated out from the list as a unbenchmarked
83     // microoptimization, to avoid needing to case on the list to
84     // access the head.
85     priv head: Chunk,
86     priv pod_head: Chunk,
87     priv chunks: @mut MutList<Chunk>,
88 }
89
90 #[unsafe_destructor]
91 impl Drop for Arena {
92     fn finalize(&self) {
93         unsafe {
94             destroy_chunk(&self.head);
95             for self.chunks.each |chunk| {
96                 if !chunk.is_pod {
97                     destroy_chunk(chunk);
98                 }
99             }
100         }
101     }
102 }
103
104 fn chunk(size: uint, is_pod: bool) -> Chunk {
105     let mut v: @[u8] = @[];
106     unsafe { at_vec::raw::reserve(&mut v, size); }
107     Chunk {
108         data: unsafe { cast::transmute(v) },
109         fill: 0u,
110         is_pod: is_pod,
111     }
112 }
113
114 pub fn arena_with_size(initial_size: uint) -> Arena {
115     Arena {
116         head: chunk(initial_size, false),
117         pod_head: chunk(initial_size, true),
118         chunks: @mut MutNil,
119     }
120 }
121
122 pub fn Arena() -> Arena {
123     arena_with_size(32u)
124 }
125
126 #[inline(always)]
127 fn round_up_to(base: uint, align: uint) -> uint {
128     (base + (align - 1)) & !(align - 1)
129 }
130
131 // Walk down a chunk, running the destructors for any objects stored
132 // in it.
133 unsafe fn destroy_chunk(chunk: &Chunk) {
134     let mut idx = 0;
135     let buf = vec::raw::to_ptr(chunk.data);
136     let fill = chunk.fill;
137
138     while idx < fill {
139         let tydesc_data: *uint = transmute(ptr::offset(buf, idx));
140         let (tydesc, is_done) = un_bitpack_tydesc_ptr(*tydesc_data);
141         let size = (*tydesc).size, align = (*tydesc).align;
142
143         let after_tydesc = idx + sys::size_of::<*TypeDesc>();
144
145         let start = round_up_to(after_tydesc, align);
146
147         //debug!("freeing object: idx = %u, size = %u, align = %u, done = %b",
148         //       start, size, align, is_done);
149         if is_done {
150             rustrt::rust_call_tydesc_glue(
151                 ptr::offset(buf, start), tydesc, tydesc_drop_glue_index);
152         }
153
154         // Find where the next tydesc lives
155         idx = round_up_to(start + size, sys::pref_align_of::<*TypeDesc>());
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(always)]
164 unsafe fn bitpack_tydesc_ptr(p: *TypeDesc, is_done: bool) -> uint {
165     let p_bits: uint = transmute(p);
166     p_bits | (is_done as uint)
167 }
168 #[inline(always)]
169 unsafe fn un_bitpack_tydesc_ptr(p: uint) -> (*TypeDesc, bool) {
170     (transmute(p & !1), p & 1 == 1)
171 }
172
173 pub impl Arena {
174     // Functions for the POD part of the arena
175     priv 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(always)]
187     priv 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(always)]
207     #[cfg(stage0)]
208     priv fn alloc_pod<T>(&mut self, op: &fn() -> T) -> &'self T {
209         unsafe {
210             let tydesc = sys::get_type_desc::<T>();
211             let ptr = self.alloc_pod_inner((*tydesc).size, (*tydesc).align);
212             let ptr: *mut T = transmute(ptr);
213             rusti::move_val_init(&mut (*ptr), op());
214             return transmute(ptr);
215         }
216     }
217
218     #[inline(always)]
219     #[cfg(stage1)]
220     #[cfg(stage2)]
221     #[cfg(stage3)]
222     priv fn alloc_pod<'a, T>(&'a mut self, op: &fn() -> T) -> &'a T {
223         unsafe {
224             let tydesc = sys::get_type_desc::<T>();
225             let ptr = self.alloc_pod_inner((*tydesc).size, (*tydesc).align);
226             let ptr: *mut T = transmute(ptr);
227             rusti::move_val_init(&mut (*ptr), op());
228             return transmute(ptr);
229         }
230     }
231
232     // Functions for the non-POD part of the arena
233     priv fn alloc_nonpod_grow(&mut self, n_bytes: uint, align: uint)
234                              -> (*u8, *u8) {
235         // Allocate a new chunk.
236         let chunk_size = at_vec::capacity(self.head.data);
237         let new_min_chunk_size = uint::max(n_bytes, chunk_size);
238         self.chunks = @mut MutCons(copy self.head, self.chunks);
239         self.head =
240             chunk(uint::next_power_of_two(new_min_chunk_size + 1u), false);
241
242         return self.alloc_nonpod_inner(n_bytes, align);
243     }
244
245     #[inline(always)]
246     priv fn alloc_nonpod_inner(&mut self, n_bytes: uint, align: uint)
247                                -> (*u8, *u8) {
248         unsafe {
249             let head = transmute_mut_region(&mut self.head);
250
251             let tydesc_start = head.fill;
252             let after_tydesc = head.fill + sys::size_of::<*TypeDesc>();
253             let start = round_up_to(after_tydesc, align);
254             let end = start + n_bytes;
255             if end > at_vec::capacity(head.data) {
256                 return self.alloc_nonpod_grow(n_bytes, align);
257             }
258             head.fill = round_up_to(end, sys::pref_align_of::<*TypeDesc>());
259
260             //debug!("idx = %u, size = %u, align = %u, fill = %u",
261             //       start, n_bytes, align, head.fill);
262
263             let buf = vec::raw::to_ptr(head.data);
264             return (ptr::offset(buf, tydesc_start), ptr::offset(buf, start));
265         }
266     }
267
268     #[inline(always)]
269     #[cfg(stage0)]
270     priv fn alloc_nonpod<T>(&mut self, op: &fn() -> T) -> &'self T {
271         unsafe {
272             let tydesc = sys::get_type_desc::<T>();
273             let (ty_ptr, ptr) =
274                 self.alloc_nonpod_inner((*tydesc).size, (*tydesc).align);
275             let ty_ptr: *mut uint = transmute(ty_ptr);
276             let ptr: *mut T = transmute(ptr);
277             // Write in our tydesc along with a bit indicating that it
278             // has *not* been initialized yet.
279             *ty_ptr = transmute(tydesc);
280             // Actually initialize it
281             rusti::move_val_init(&mut(*ptr), op());
282             // Now that we are done, update the tydesc to indicate that
283             // the object is there.
284             *ty_ptr = bitpack_tydesc_ptr(tydesc, true);
285
286             return transmute(ptr);
287         }
288     }
289
290     #[inline(always)]
291     #[cfg(stage1)]
292     #[cfg(stage2)]
293     #[cfg(stage3)]
294     priv fn alloc_nonpod<'a, T>(&'a mut self, op: &fn() -> T) -> &'a T {
295         unsafe {
296             let tydesc = sys::get_type_desc::<T>();
297             let (ty_ptr, ptr) =
298                 self.alloc_nonpod_inner((*tydesc).size, (*tydesc).align);
299             let ty_ptr: *mut uint = transmute(ty_ptr);
300             let ptr: *mut T = transmute(ptr);
301             // Write in our tydesc along with a bit indicating that it
302             // has *not* been initialized yet.
303             *ty_ptr = transmute(tydesc);
304             // Actually initialize it
305             rusti::move_val_init(&mut(*ptr), op());
306             // Now that we are done, update the tydesc to indicate that
307             // the object is there.
308             *ty_ptr = bitpack_tydesc_ptr(tydesc, true);
309
310             return transmute(ptr);
311         }
312     }
313
314     // The external interface
315     #[inline(always)]
316     #[cfg(stage0)]
317     fn alloc<T>(&mut self, op: &fn() -> T) -> &'self T {
318         unsafe {
319             // XXX: Borrow check
320             let this = transmute_mut_region(self);
321             if !rusti::needs_drop::<T>() {
322                 return this.alloc_pod(op);
323             }
324             // XXX: Borrow check
325             let this = transmute_mut_region(self);
326             this.alloc_nonpod(op)
327         }
328     }
329
330     // The external interface
331     #[inline(always)]
332     #[cfg(stage1)]
333     #[cfg(stage2)]
334     #[cfg(stage3)]
335     fn alloc<'a, T>(&'a mut self, op: &fn() -> T) -> &'a T {
336         unsafe {
337             // XXX: Borrow check
338             let this = transmute_mut_region(self);
339             if !rusti::needs_drop::<T>() {
340                 return this.alloc_pod(op);
341             }
342             // XXX: Borrow check
343             let this = transmute_mut_region(self);
344             this.alloc_nonpod(op)
345         }
346     }
347 }
348
349 #[test]
350 fn test_arena_destructors() {
351     let arena = Arena();
352     for uint::range(0, 10) |i| {
353         // Arena allocate something with drop glue to make sure it
354         // doesn't leak.
355         do arena.alloc { @i };
356         // Allocate something with funny size and alignment, to keep
357         // things interesting.
358         do arena.alloc { [0u8, 1u8, 2u8] };
359     }
360 }
361
362 #[test]
363 #[should_fail]
364 #[ignore(cfg(windows))]
365 fn test_arena_destructors_fail() {
366     let arena = Arena();
367     // Put some stuff in the arena.
368     for uint::range(0, 10) |i| {
369         // Arena allocate something with drop glue to make sure it
370         // doesn't leak.
371         do arena.alloc { @i };
372         // Allocate something with funny size and alignment, to keep
373         // things interesting.
374         do arena.alloc { [0u8, 1u8, 2u8] };
375     }
376     // Now, fail while allocating
377     do arena.alloc::<@int> {
378         // First, recursively allocate something else; that needs to
379         // get freed too.
380         do arena.alloc { @20 };
381         // Now fail.
382         fail!();
383     };
384 }