Commit | Line | Data |
---|---|---|
9f298f4f WJ |
1 | /* Copyright (C) 1989, 1992 Aladdin Enterprises. All rights reserved. |
2 | Distributed by Free Software Foundation, Inc. | |
3 | ||
4 | This file is part of Ghostscript. | |
5 | ||
6 | Ghostscript is distributed in the hope that it will be useful, but | |
7 | WITHOUT ANY WARRANTY. No author or distributor accepts responsibility | |
8 | to anyone for the consequences of using it or for whether it serves any | |
9 | particular purpose or works at all, unless he says so in writing. Refer | |
10 | to the Ghostscript General Public License for full details. | |
11 | ||
12 | Everyone is granted permission to copy, modify and redistribute | |
13 | Ghostscript, but only under the conditions described in the Ghostscript | |
14 | General Public License. A copy of this license is supposed to have been | |
15 | given to you along with Ghostscript so you can know your rights and | |
16 | responsibilities. It should be in a file named COPYING. Among other | |
17 | things, the copyright notice and this notice must be preserved on all | |
18 | copies. */ | |
19 | ||
20 | /* ialloc.c */ | |
21 | /* Memory allocator for Ghostscript interpreter */ | |
22 | #include <stdio.h> /* for NULL */ | |
23 | #include "gs.h" | |
24 | #include "memory_.h" | |
25 | #include "alloc.h" | |
26 | #include "astate.h" | |
27 | ||
28 | #ifdef DEBUG | |
29 | extern char gs_debug[128]; | |
30 | #endif | |
31 | ||
32 | /* Forward references */ | |
33 | private int alloc_add_chunk(P2(alloc_state_ptr, uint)); | |
34 | private char *alloc_large(P3(alloc_state_ptr, uint, const char *)); | |
35 | private void alloc_free_large(P3(char *, uint, const char *)); | |
36 | ||
37 | /* The only allocator instance (for now). */ | |
38 | private alloc_state as_current; | |
39 | alloc_state_ptr alloc_state_current = &as_current; | |
40 | ||
41 | /* Debugging printout */ | |
42 | #ifdef DEBUG | |
43 | # define alloc_print(rtag, tag, blk, sz)\ | |
44 | if ( gs_debug['A'] )\ | |
45 | fprintf(gs_debug_out, "[a:%c:%c:%s] %lx(%u)\n", rtag, tag,\ | |
46 | client_name, (ulong)blk, sz) | |
47 | # define alloc_print_large(rtag, tag, blk, sz)\ | |
48 | if ( gs_debug['A'] | gs_debug['a'] )\ | |
49 | fprintf(gs_debug_out, "[a:%c:%c:%s] %lx(%u)\n", rtag, tag,\ | |
50 | client_name, (ulong)blk, sz) | |
51 | #else | |
52 | # define alloc_print(rtag, tag, blk, sz) | |
53 | # define alloc_print_large(rtag, tag, blk, sz) | |
54 | #endif | |
55 | ||
56 | /* ------ Initialize/status ------ */ | |
57 | ||
58 | /* Initialize the allocator */ | |
59 | void | |
60 | alloc_init(proc_alloc_t palloc, proc_free_t pfree, uint chunk_size) | |
61 | { register alloc_state_ptr ap = &as_current; | |
62 | memset(ap, 0, sizeof(alloc_state)); /* do it all at once */ | |
63 | ap->chunk_size = chunk_size; | |
64 | ap->big_size = chunk_size / 4; | |
65 | ap->palloc = palloc; | |
66 | ap->pfree = pfree; | |
67 | ap->last_freed = 0; | |
68 | { extern void alloc_save_init(P0()); | |
69 | alloc_save_init(); | |
70 | } | |
71 | } | |
72 | ||
73 | /* Return the status of the allocator: space used, total space. */ | |
74 | void | |
75 | alloc_status(long *pused, long *ptotal) | |
76 | { register alloc_state_ptr ap = &as_current; | |
77 | *pused = (ap->cbot - ap->cbase) + (ap->climit - ap->ctop) + ap->used; | |
78 | *ptotal = ap->total; | |
79 | } | |
80 | ||
81 | /* ------ Allocation and freeing ------ */ | |
82 | ||
83 | /* Allocate an object. Return 0 if not enough room. */ | |
84 | char * | |
85 | alloc(uint num_elts, uint elt_size, const char *client_name) | |
86 | { register alloc_state_ptr ap = &as_current; | |
87 | uint size = num_elts * elt_size; | |
88 | uint block_size; | |
89 | uint left; | |
90 | if ( size >= ap->big_size ) | |
91 | { /* Large object, do a separate malloc. */ | |
92 | char *block = alloc_large(ap, size, client_name); | |
93 | if ( block != NULL ) return block; | |
94 | if ( size > ap->chunk_size ) | |
95 | return 0; /* can't alloc */ | |
96 | } | |
97 | block_size = align_round(size); | |
98 | if ( block_size <= max_chain_size ) | |
99 | { /* See if we can use a freed block. */ | |
100 | char **fptr = &ap->free[block_size >> log2_align_mod]; | |
101 | char *block = *fptr; | |
102 | if ( block != 0 ) | |
103 | { *fptr = *(char **)block; | |
104 | alloc_print('+', '#', block, size); | |
105 | return block; | |
106 | } | |
107 | } | |
108 | left = ap->ctop - ap->cbot; | |
109 | if ( block_size > left ) | |
110 | { uint csize = ap->chunk_size; | |
111 | while ( !alloc_add_chunk(ap, csize) ) | |
112 | { alloc_print('+', '?', (ulong)0, size); | |
113 | /* Things are desperate, but perhaps not hopeless. */ | |
114 | if ( (csize >>= 1) < block_size ) | |
115 | return 0; /* no hope */ | |
116 | } | |
117 | } | |
118 | if ( elt_size == 1 ) | |
119 | { /* Unaligned block */ | |
120 | ap->ctop -= size; | |
121 | alloc_print('+', '>', ap->ctop, size); | |
122 | return (char *)ap->ctop; | |
123 | } | |
124 | else | |
125 | { /* Aligned block */ | |
126 | char *block = (char *)ap->cbot; | |
127 | ap->cbot += block_size; | |
128 | alloc_print('+', '<', block, size); | |
129 | return block; | |
130 | } | |
131 | } | |
132 | ||
133 | /* Free an object, if possible. */ | |
134 | /* Note that if a save is in effect, objects in chunks older than */ | |
135 | /* the save, and objects allocated with malloc before the save, */ | |
136 | /* must not be freed. */ | |
137 | void | |
138 | alloc_free(char *cobj, uint num_elts, uint elt_size, const char *client_name) | |
139 | { register alloc_state_ptr ap = &as_current; | |
140 | uint size = num_elts * elt_size; | |
141 | uint block_size; | |
142 | if ( size >= ap->big_size ) | |
143 | { /* Object was allocated with malloc. */ | |
144 | alloc_free_large(cobj, size, client_name); | |
145 | return; | |
146 | } | |
147 | #define obj ((byte *)cobj) | |
148 | else if ( obj == ap->ctop ) | |
149 | { /* Don't free the object if we're in a save and */ | |
150 | /* this object wasn't allocated since the save. */ | |
151 | if ( ap->save_level == 0 || | |
152 | ap->current.save_level >= ap->save_level || | |
153 | /* We know the current chunk is the same as */ | |
154 | /* the one in as->saved->state */ | |
155 | obj < ap->saved_ctop | |
156 | ) | |
157 | ap->ctop += size; | |
158 | alloc_print('-', '>', obj, size); | |
159 | return; | |
160 | } | |
161 | else if ( obj + (block_size = align_round(size)) == ap->cbot ) | |
162 | { /* Freeing an aligned object. Same check. */ | |
163 | if ( ap->save_level == 0 || | |
164 | ap->current.save_level >= ap->save_level || | |
165 | obj >= ap->saved_cbot | |
166 | ) | |
167 | ap->cbot = obj; | |
168 | alloc_print('-', '<', obj, size); | |
169 | return; | |
170 | } | |
171 | else if ( !ptr_is_in_chunk(obj, &ap->current) ) | |
172 | { /* In another segment, check its save level. */ | |
173 | int level = ap->save_level; | |
174 | alloc_chunk *cp = ap->last_freed; | |
175 | if ( cp != 0 && ptr_is_in_chunk(obj, cp) ) /* cache hit */ | |
176 | { if ptr_lt(obj, cp->bot) goto pxf; | |
177 | else goto pnf; | |
178 | } | |
179 | for ( cp = ap->current.next; cp != 0; cp = cp->next ) | |
180 | { switch ( cp->save_level - level ) | |
181 | { | |
182 | case 0: | |
183 | if ( ptr_is_in_chunk(obj, cp) ) | |
184 | { if ( ptr_lt(obj, cp->bot) ) goto pbf; | |
185 | else goto pnf; | |
186 | } | |
187 | else continue; | |
188 | case -1: | |
189 | /* Might be alloc'ed since the save, */ | |
190 | /* or might not be aligned. */ | |
191 | if ( ptr_lt(obj, ap->saved_cbot) ) goto pbf; | |
192 | } | |
193 | } | |
194 | pnf: /* Older save level or unaligned, not freeable. */ | |
195 | alloc_print('-', '\\', obj, size); | |
196 | return; | |
197 | pbf: /* If we get here, OK to put the block on a free list. */ | |
198 | ap->last_freed = cp; | |
199 | pxf: ; | |
200 | } | |
201 | else if ( obj >= ap->cbot ) /* not aligned object, punt */ | |
202 | { alloc_print('-', '~', obj, size); | |
203 | return; | |
204 | } | |
205 | /* Put on a free list if small enough */ | |
206 | alloc_print('-', '#', obj, size); | |
207 | if ( block_size <= max_chain_size && block_size >= sizeof(char **) ) | |
208 | { char **fptr = &ap->free[block_size >> log2_align_mod]; | |
209 | *(char **)cobj = *fptr; | |
210 | *fptr = cobj; | |
211 | } | |
212 | #undef obj | |
213 | } | |
214 | ||
215 | /* Grow an object. This may require allocating a new copy. */ | |
216 | /* Return 0 if not enough room. */ | |
217 | /****** Note: the object must have been allocated at | |
218 | ****** the current save level. */ | |
219 | byte * | |
220 | alloc_grow(byte *obj, uint old_num, uint new_num, uint elt_size, | |
221 | const char *client_name) | |
222 | { register alloc_state_ptr ap = &as_current; | |
223 | uint old_size = old_num * elt_size; | |
224 | uint new_size = new_num * elt_size; | |
225 | byte *nobj; | |
226 | if ( new_size == old_size ) return obj; | |
227 | if ( new_size < ap->big_size ) /* try to grow in place */ | |
228 | { uint old_block_size; | |
229 | uint new_block_size; | |
230 | if ( obj == ap->ctop ) | |
231 | { /* Might be able to grow in place */ | |
232 | uint diff = new_size - old_size; | |
233 | if ( diff <= ap->ctop - ap->cbot ) | |
234 | { alloc_print('>', '>', obj, new_size); | |
235 | ap->ctop -= diff; | |
236 | memcpy(ap->ctop, obj, old_size); | |
237 | return ap->ctop; | |
238 | } | |
239 | } | |
240 | old_block_size = align_round(old_size); | |
241 | new_block_size = align_round(new_size); | |
242 | if ( obj + old_block_size == ap->cbot ) | |
243 | { /* Might be able to grow in place */ | |
244 | uint diff = new_block_size - old_block_size; | |
245 | if ( diff <= ap->ctop - ap->cbot ) | |
246 | { alloc_print('>', '<', obj, new_size); | |
247 | ap->cbot += diff; | |
248 | return obj; | |
249 | } | |
250 | } | |
251 | } | |
252 | /* Can't grow in place. Allocate a new object and copy. */ | |
253 | nobj = (byte *)alloc(new_num, elt_size, client_name); | |
254 | if ( nobj == 0 ) | |
255 | return 0; | |
256 | memcpy(nobj, obj, old_size); | |
257 | alloc_free((char *)obj, old_num, elt_size, client_name); | |
258 | alloc_print('>', '&', obj, new_size); | |
259 | return nobj; | |
260 | } | |
261 | ||
262 | /* Shrink an object. */ | |
263 | /****** Note: the object must have been allocated at | |
264 | ****** the current save level. */ | |
265 | byte * | |
266 | alloc_shrink(byte *obj, uint old_num, uint new_num, uint elt_size, | |
267 | const char *client_name) | |
268 | { register alloc_state_ptr ap = &as_current; | |
269 | uint old_size = old_num * elt_size; | |
270 | uint new_size = new_num * elt_size; | |
271 | if ( new_size == old_size ) return obj; | |
272 | if ( old_size >= ap->big_size ) | |
273 | { /* Allocate a new block. */ | |
274 | byte *nobj = (byte *)alloc(new_num, elt_size, client_name); | |
275 | if ( nobj == 0 ) return obj; /* can't shrink, leave as is */ | |
276 | memcpy(nobj, obj, new_size); | |
277 | alloc_free((char *)obj, old_num, elt_size, client_name); | |
278 | alloc_print('<', '&', obj, new_size); | |
279 | return nobj; | |
280 | } | |
281 | else if ( obj == ap->ctop ) | |
282 | { /* Move the object up in place. */ | |
283 | /* memcpy doesn't do this properly. */ | |
284 | register byte *from = obj + new_size; | |
285 | register byte *to = obj + old_size; | |
286 | while ( from > obj ) *--to = *--from; | |
287 | obj = ap->ctop = to; | |
288 | } | |
289 | else | |
290 | { uint new_block_size = align_round(new_size); | |
291 | alloc_free((char *)(obj + new_block_size), | |
292 | 1, align_round(old_size) - new_block_size, | |
293 | "alloc_shrink"); | |
294 | } | |
295 | alloc_print('<', ' ', obj, new_size); | |
296 | return obj; | |
297 | } | |
298 | ||
299 | /* ------ Private routines ------ */ | |
300 | ||
301 | /* Allocate (with malloc) an object too large to be put in a chunk. */ | |
302 | /* Return NULL on failure. */ | |
303 | private char * | |
304 | alloc_large(alloc_state_ptr ap, uint size, const char *client_name) | |
305 | { alloc_block *mblock = (alloc_block *) | |
306 | (*ap->palloc)(1, alloc_block_size + size, client_name); | |
307 | char *block; | |
308 | if ( mblock == NULL ) return NULL; | |
309 | block = (char *)mblock + alloc_block_size; | |
310 | alloc_print_large('+', '*', block, size); | |
311 | mblock->next = ap->malloc_chain; | |
312 | mblock->size = size; | |
313 | mblock->save_level = ap->save_level; | |
314 | mblock->cap = ap; | |
315 | ap->malloc_chain = mblock; | |
316 | ap->used += size; | |
317 | ap->total += size; | |
318 | return block; | |
319 | } | |
320 | ||
321 | /* Allocate a new chunk. Return true if successful. */ | |
322 | #define chunk_head_size align_round(sizeof(alloc_chunk)) | |
323 | private int | |
324 | alloc_add_chunk(register alloc_state_ptr ap, uint csize) | |
325 | { char *space = | |
326 | (*ap->palloc)(1, chunk_head_size + csize, "alloc chunk"); | |
327 | long discard; | |
328 | if ( space == NULL ) | |
329 | return 0; | |
330 | ap->num_chunks++; | |
331 | /* Accumulate statistics */ | |
332 | ap->total += csize; | |
333 | alloc_status(&ap->used, &discard); | |
334 | /* Stash the state of the old chunk */ | |
335 | if ( ap->current_ptr != 0 ) /* check for very first chunk */ | |
336 | *ap->current_ptr = ap->current; | |
337 | /* Initialize the new chunk */ | |
338 | ap->cbase = ap->cbot = (byte *)space + chunk_head_size; | |
339 | ap->climit = ap->ctop = ap->cbot + csize; | |
340 | ap->current.next = ap->current_ptr; | |
341 | ap->current.save_level = ap->save_level; | |
342 | ap->current_ptr = (alloc_chunk *)space; | |
343 | return 1; | |
344 | } | |
345 | #undef chunk_head_size | |
346 | ||
347 | /* Free a large object (allocated with malloc). */ | |
348 | private void | |
349 | alloc_free_large(char *cobj, uint size, const char *client_name) | |
350 | { alloc_block **prev; | |
351 | alloc_block *mblock = (alloc_block *)(cobj - alloc_block_size); | |
352 | alloc_state_ptr ap = mblock->cap; | |
353 | if ( mblock->save_level == ap->save_level ) | |
354 | for ( prev = &ap->malloc_chain; *prev != 0; prev = &mblock->next ) | |
355 | { mblock = *prev; | |
356 | if ( (char *)mblock + alloc_block_size == cobj ) | |
357 | { *prev = mblock->next; | |
358 | ap->used -= size; | |
359 | ap->total -= size; | |
360 | (*ap->pfree)((char *)mblock, | |
361 | 1, size + alloc_block_size, | |
362 | "large object"); | |
363 | alloc_print_large('-', '*', cobj, size); | |
364 | return; | |
365 | } | |
366 | } | |
367 | alloc_print('-', '?', cobj, size); | |
368 | } |