Coverage Report

Created: 2026-04-01 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/psi/isave.c
Line
Count
Source
1
/* Copyright (C) 2001-2026 Artifex Software, Inc.
2
   All Rights Reserved.
3
4
   This software is provided AS-IS with no warranty, either express or
5
   implied.
6
7
   This software is distributed under license and may not be copied,
8
   modified or distributed except as expressly authorized under the terms
9
   of the license contained in the file LICENSE in this distribution.
10
11
   Refer to licensing information at http://www.artifex.com or contact
12
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
13
   CA 94129, USA, for further information.
14
*/
15
16
17
/* Save/restore manager for Ghostscript interpreter */
18
#include "ghost.h"
19
#include "memory_.h"
20
#include "ierrors.h"
21
#include "gsexit.h"
22
#include "gsstruct.h"
23
#include "iastate.h"
24
#include "inamedef.h"
25
#include "iname.h"
26
#include "ipacked.h"
27
#include "isave.h"
28
#include "isstate.h"
29
#include "gsstate.h"
30
#include "store.h"    /* for ref_assign */
31
#include "ivmspace.h"
32
#include "igc.h"
33
#include "gsutil.h"   /* gs_next_ids prototype */
34
#include "icstate.h"
35
#include "assert.h"
36
37
/* Structure descriptor */
38
private_st_alloc_save();
39
40
/* Define the maximum amount of data we are willing to scan repeatedly -- */
41
/* see below for details. */
42
static const long max_repeated_scan = 100000;
43
44
/* Define the minimum space for creating an inner clump. */
45
/* Must be at least sizeof(clump_head_t). */
46
static const long min_inner_clump_space = sizeof(clump_head_t) + 500;
47
48
/*
49
 * The logic for saving and restoring the state is complex.
50
 * Both the changes to individual objects, and the overall state
51
 * of the memory manager, must be saved and restored.
52
 */
53
54
/*
55
 * To save the state of the memory manager:
56
 *      Save the state of the current clump in which we are allocating.
57
 *      Shrink all clumps to their inner unallocated region.
58
 *      Save and reset the free block chains.
59
 * By doing this, we guarantee that no object older than the save
60
 * can be freed.
61
 *
62
 * To restore the state of the memory manager:
63
 *      Free all clumps newer than the save, and the descriptors for
64
 *        the inner clumps created by the save.
65
 *      Make current the clump that was current at the time of the save.
66
 *      Restore the state of the current clump.
67
 *
68
 * In addition to save ("start transaction") and restore ("abort transaction"),
69
 * we support forgetting a save ("commit transation").  To forget a save:
70
 *      Reassign to the next outer save all clumps newer than the save.
71
 *      Free the descriptors for the inners clump, updating their outer
72
 *        clumps to reflect additional allocations in the inner clumps.
73
 *      Concatenate the free block chains with those of the outer save.
74
 */
75
76
/*
77
 * For saving changes to individual objects, we add an "attribute" bit
78
 * (l_new) that logically belongs to the slot where the ref is stored,
79
 * not to the ref itself.  The bit means "the contents of this slot
80
 * have been changed, or the slot was allocated, since the last save."
81
 * To keep track of changes since the save, we associate a chain of
82
 * <slot, old_contents> pairs that remembers the old contents of slots.
83
 *
84
 * When creating an object, if the save level is non-zero:
85
 *      Set l_new in all slots.
86
 *
87
 * When storing into a slot, if the save level is non-zero:
88
 *      If l_new isn't set, save the address and contents of the slot
89
 *        on the current contents chain.
90
 *      Set l_new after storing the new value.
91
 *
92
 * To do a save:
93
 *      If the save level is non-zero:
94
 *              Reset l_new in all slots on the contents chain, and in all
95
 *                objects created since the previous save.
96
 *      Push the head of the contents chain, and reset the chain to empty.
97
 *
98
 * To do a restore:
99
 *      Check all the stacks to make sure they don't contain references
100
 *        to objects created since the save.
101
 *      Restore all the slots on the contents chain.
102
 *      Pop the contents chain head.
103
 *      If the save level is now non-zero:
104
 *              Scan the newly restored contents chain, and set l_new in all
105
 *                the slots it references.
106
 *              Scan all objects created since the previous save, and set
107
 *                l_new in all the slots of each object.
108
 *
109
 * To forget a save:
110
 *      If the save level is greater than 1:
111
 *              Set l_new as for a restore, per the next outer save.
112
 *              Concatenate the next outer contents chain to the end of
113
 *                the current one.
114
 *      If the save level is 1:
115
 *              Reset l_new as for a save.
116
 *              Free the contents chain.
117
 */
118
119
/*
120
 * A consequence of the foregoing algorithms is that the cost of a save is
121
 * proportional to the total amount of data allocated since the previous
122
 * save.  If a PostScript program reads in a large amount of setup code and
123
 * then uses save/restore heavily, each save/restore will be expensive.  To
124
 * mitigate this, we check to see how much data we have scanned at this save
125
 * level: if it is large, we do a second, invisible save.  This greatly
126
 * reduces the cost of inner saves, at the expense of possibly saving some
127
 * changes twice that otherwise would only have to be saved once.
128
 */
129
130
/*
131
 * The presence of global and local VM complicates the situation further.
132
 * There is a separate save chain and contents chain for each VM space.
133
 * When multiple contexts are fully implemented, save and restore will have
134
 * the following effects, according to the privacy status of the current
135
 * context's global and local VM:
136
 *      Private global, private local:
137
 *              The outermost save saves both global and local VM;
138
 *                otherwise, save only saves local VM.
139
 *      Shared global, private local:
140
 *              Save only saves local VM.
141
 *      Shared global, shared local:
142
 *              Save only saves local VM, and suspends all other contexts
143
 *                sharing the same local VM until the matching restore.
144
 * Since we do not currently implement multiple contexts, only the first
145
 * case is relevant.
146
 *
147
 * Note that when saving the contents of a slot, the choice of chain
148
 * is determined by the VM space in which the slot is allocated,
149
 * not by the current allocation mode.
150
 */
151
152
/* Tracing printout */
153
static void
154
print_save(const char *str, uint spacen, const alloc_save_t *sav)
155
2.83M
{
156
2.83M
  if_debug5('u', "[u]%s space %u "PRI_INTPTR": cdata = "PRI_INTPTR", id = %lu\n",\
157
2.83M
            str, spacen, (intptr_t)sav, (intptr_t)sav->client_data, (ulong)sav->id);
158
2.83M
}
159
160
/* A link to igcref.c . */
161
ptr_proc_reloc(igc_reloc_ref_ptr_nocheck, ref_packed);
162
163
static
164
CLEAR_MARKS_PROC(change_clear_marks)
165
30.7M
{
166
30.7M
    alloc_change_t *const ptr = (alloc_change_t *)vptr;
167
168
30.7M
    if (r_is_packed(&ptr->contents))
169
472k
        r_clear_pmark((ref_packed *) & ptr->contents);
170
30.3M
    else
171
30.3M
        r_clear_attrs(&ptr->contents, l_mark);
172
30.7M
}
173
static
174
121M
ENUM_PTRS_WITH(change_enum_ptrs, alloc_change_t *ptr) return 0;
175
30.3M
ENUM_PTR(0, alloc_change_t, next);
176
30.3M
case 1:
177
30.3M
    if (ptr->offset >= 0)
178
2
        ENUM_RETURN((byte *) ptr->where - ptr->offset);
179
30.3M
    else
180
30.3M
        if (ptr->offset != AC_OFFSET_ALLOCATED)
181
7.90M
            ENUM_RETURN_REF(ptr->where);
182
22.4M
        else {
183
            /* Don't enumerate ptr->where, because it
184
               needs a special processing with
185
               alloc_save__filter_changes. */
186
22.4M
            ENUM_RETURN(0);
187
22.4M
        }
188
30.3M
case 2:
189
30.3M
    ENUM_RETURN_REF(&ptr->contents);
190
121M
ENUM_PTRS_END
191
11.0M
static RELOC_PTRS_WITH(change_reloc_ptrs, alloc_change_t *ptr)
192
11.0M
{
193
11.0M
    RELOC_VAR(ptr->next);
194
11.0M
    switch (ptr->offset) {
195
0
        case AC_OFFSET_STATIC:
196
0
            break;
197
7.90M
        case AC_OFFSET_REF:
198
7.90M
            RELOC_REF_PTR_VAR(ptr->where);
199
7.90M
            break;
200
3.12M
        case AC_OFFSET_ALLOCATED:
201
            /* We know that ptr->where may point to an unmarked object
202
               because change_enum_ptrs skipped it,
203
               and we know it always points to same space
204
               because we took a special care when calling alloc_save_change_alloc.
205
               Therefore we must skip the check for the mark,
206
               which would happen if we call the regular relocation function
207
               igc_reloc_ref_ptr from RELOC_REF_PTR_VAR.
208
               Calling igc_reloc_ref_ptr_nocheck instead. */
209
3.12M
            { /* A sanity check. */
210
3.12M
                obj_header_t *pre = (obj_header_t *)ptr->where - 1;
211
212
3.12M
                if (pre->o_type != &st_refs)
213
0
                    gs_abort(gcst->heap);
214
3.12M
            }
215
3.12M
            if (ptr->where != 0 && !gcst->relocating_untraced)
216
2.77M
                ptr->where = igc_reloc_ref_ptr_nocheck(ptr->where, gcst);
217
3.12M
            break;
218
2
        default:
219
2
            {
220
2
                byte *obj = (byte *) ptr->where - ptr->offset;
221
222
2
                RELOC_VAR(obj);
223
2
                ptr->where = (ref_packed *) (obj + ptr->offset);
224
2
            }
225
2
            break;
226
11.0M
    }
227
11.0M
    if (r_is_packed(&ptr->contents))
228
472k
        r_clear_pmark((ref_packed *) & ptr->contents);
229
10.5M
    else {
230
10.5M
        RELOC_REF_VAR(ptr->contents);
231
10.5M
        r_clear_attrs(&ptr->contents, l_mark);
232
10.5M
    }
233
11.0M
}
234
11.0M
RELOC_PTRS_END
235
gs_private_st_complex_only(st_alloc_change, alloc_change_t, "alloc_change",
236
                change_clear_marks, change_enum_ptrs, change_reloc_ptrs, 0);
237
238
/* Debugging printout */
239
#ifdef DEBUG
240
static void
241
alloc_save_print(const gs_memory_t *mem, alloc_change_t * cp, bool print_current)
242
{
243
    dmprintf2(mem, " "PRI_INTPTR"x: "PRI_INTPTR": ", (intptr_t) cp, (intptr_t) cp->where);
244
    if (r_is_packed(&cp->contents)) {
245
        if (print_current)
246
            dmprintf2(mem, "saved=%x cur=%x\n", *(ref_packed *) & cp->contents,
247
                      *cp->where);
248
        else
249
            dmprintf1(mem, "%x\n", *(ref_packed *) & cp->contents);
250
    } else {
251
        if (print_current)
252
            dmprintf6(mem, "saved=%x %x %lx cur=%x %x %lx\n",
253
                      r_type_attrs(&cp->contents), r_size(&cp->contents),
254
                      (ulong) cp->contents.value.intval,
255
                      r_type_attrs((ref *) cp->where),
256
                      r_size((ref *) cp->where),
257
                      (ulong) ((ref *) cp->where)->value.intval);
258
        else
259
            dmprintf3(mem, "%x %x %lx\n",
260
                      r_type_attrs(&cp->contents), r_size(&cp->contents),
261
                      (ulong) cp->contents.value.intval);
262
    }
263
}
264
#endif
265
266
/* Forward references */
267
static int  restore_resources(alloc_save_t *, gs_ref_memory_t *);
268
static void restore_free(gs_ref_memory_t *);
269
static int  save_set_new(gs_ref_memory_t * mem, bool to_new, bool set_limit, ulong *pscanned);
270
static int  save_set_new_changes(gs_ref_memory_t *, bool, bool);
271
static bool check_l_mark(void *obj);
272
273
/* Initialize the save/restore machinery. */
274
void
275
alloc_save_init(gs_dual_memory_t * dmem)
276
182k
{
277
182k
    alloc_set_not_in_save(dmem);
278
182k
}
279
280
/* Record that we are in a save. */
281
static void
282
alloc_set_masks(gs_dual_memory_t *dmem, uint new_mask, uint test_mask)
283
2.35M
{
284
2.35M
    int i;
285
2.35M
    gs_ref_memory_t *mem;
286
287
2.35M
    dmem->new_mask = new_mask;
288
2.35M
    dmem->test_mask = test_mask;
289
11.7M
    for (i = 0; i < countof(dmem->spaces.memories.indexed); ++i)
290
9.42M
        if ((mem = dmem->spaces.memories.indexed[i]) != 0) {
291
7.06M
            mem->new_mask = new_mask, mem->test_mask = test_mask;
292
7.06M
            if (mem->stable_memory != (gs_memory_t *)mem) {
293
4.71M
                mem = (gs_ref_memory_t *)mem->stable_memory;
294
4.71M
                mem->new_mask = new_mask, mem->test_mask = test_mask;
295
4.71M
            }
296
7.06M
        }
297
2.35M
}
298
void
299
alloc_set_in_save(gs_dual_memory_t *dmem)
300
1.42M
{
301
1.42M
    alloc_set_masks(dmem, l_new, l_new);
302
1.42M
}
303
304
/* Record that we are not in a save. */
305
void
306
alloc_set_not_in_save(gs_dual_memory_t *dmem)
307
926k
{
308
926k
    alloc_set_masks(dmem, 0, ~0);
309
926k
}
310
311
/* Save the state. */
312
static alloc_save_t *alloc_save_space(gs_ref_memory_t *mem,
313
                                       gs_dual_memory_t *dmem,
314
                                       ulong sid);
315
static void
316
alloc_free_save(gs_ref_memory_t *mem, alloc_save_t *save, const char *scn)
317
0
{
318
0
    gs_ref_memory_t save_mem;
319
0
    save_mem = mem->saved->state;
320
0
    gs_free_object((gs_memory_t *)mem, save, scn);
321
    /* Free any inner clump structures.  This is the easiest way to do it. */
322
0
    restore_free(mem);
323
    /* Restore the 'saved' state - this pulls our object off the linked
324
     * list of states. Without this we hit a SEGV in the gc later. */
325
0
    *mem = save_mem;
326
0
}
327
int
328
alloc_save_state(gs_dual_memory_t * dmem, void *cdata, ulong *psid)
329
1.23M
{
330
1.23M
    gs_ref_memory_t *lmem = dmem->space_local;
331
1.23M
    gs_ref_memory_t *gmem = dmem->space_global;
332
1.23M
    ulong sid = gs_next_ids((const gs_memory_t *)lmem->stable_memory, 2);
333
1.23M
    bool global =
334
1.23M
        lmem->save_level == 0 && gmem != lmem &&
335
182k
        gmem->num_contexts == 1;
336
1.23M
    alloc_save_t *gsave =
337
1.23M
        (global ? alloc_save_space(gmem, dmem, sid + 1) : (alloc_save_t *) 0);
338
1.23M
    alloc_save_t *lsave = alloc_save_space(lmem, dmem, sid);
339
340
1.23M
    if (lsave == 0 || (global && gsave == 0)) {
341
        /* Only 1 of lsave or gsave will have been allocated, but
342
         * nevertheless (in case things change in future), we free
343
         * lsave, then gsave, so they 'pop' correctly when restoring
344
         * the mem->saved states. */
345
0
        if (lsave != 0)
346
0
            alloc_free_save(lmem, lsave, "alloc_save_state(local save)");
347
0
        if (gsave != 0)
348
0
            alloc_free_save(gmem, gsave, "alloc_save_state(global save)");
349
0
        return_error(gs_error_VMerror);
350
0
    }
351
1.23M
    if (gsave != 0) {
352
182k
        gsave->client_data = 0;
353
182k
        print_save("save", gmem->space, gsave);
354
        /* Restore names when we do the local restore. */
355
182k
        lsave->restore_names = gsave->restore_names;
356
182k
        gsave->restore_names = false;
357
182k
    }
358
1.23M
    lsave->id = sid;
359
1.23M
    lsave->client_data = cdata;
360
1.23M
    print_save("save", lmem->space, lsave);
361
    /* Reset the l_new attribute in all slots.  The only slots that */
362
    /* can have the attribute set are the ones on the changes chain, */
363
    /* and ones in objects allocated since the last save. */
364
1.23M
    if (lmem->save_level > 1) {
365
1.05M
        ulong scanned;
366
1.05M
        int code = save_set_new(&lsave->state, false, true, &scanned);
367
368
1.05M
        if (code < 0)
369
0
            return code;
370
#if 0 /* Disable invisible save levels. */
371
        if ((lsave->state.total_scanned += scanned) > max_repeated_scan) {
372
            /* Do a second, invisible save. */
373
            alloc_save_t *rsave;
374
375
            rsave = alloc_save_space(lmem, dmem, 0L);
376
            if (rsave != 0) {
377
                rsave->client_data = cdata;
378
#if 0 /* Bug 688153 */
379
                rsave->id = lsave->id;
380
                print_save("save", lmem->space, rsave);
381
                lsave->id = 0;  /* mark as invisible */
382
                rsave->state.save_level--; /* ditto */
383
                lsave->client_data = 0;
384
#else
385
                rsave->id = 0;  /* mark as invisible */
386
                print_save("save", lmem->space, rsave);
387
                rsave->state.save_level--; /* ditto */
388
                rsave->client_data = 0;
389
#endif
390
                /* Inherit the allocated space count -- */
391
                /* we need this for triggering a GC. */
392
                print_save("save", lmem->space, lsave);
393
            }
394
        }
395
#endif
396
1.05M
    }
397
398
1.23M
    alloc_set_in_save(dmem);
399
1.23M
    *psid = sid;
400
1.23M
    return 0;
401
1.23M
}
402
/* Save the state of one space (global or local). */
403
static alloc_save_t *
404
alloc_save_space(gs_ref_memory_t * mem, gs_dual_memory_t * dmem, ulong sid)
405
1.41M
{
406
1.41M
    gs_ref_memory_t save_mem;
407
1.41M
    alloc_save_t *save;
408
1.41M
    clump_t *cp;
409
1.41M
    clump_t *new_cc = NULL;
410
1.41M
    clump_splay_walker sw;
411
412
1.41M
    save_mem = *mem;
413
1.41M
    alloc_close_clump(mem);
414
1.41M
    mem->cc = NULL;
415
1.41M
    gs_memory_status((gs_memory_t *) mem, &mem->previous_status);
416
1.41M
    ialloc_reset(mem);
417
418
    /* Create inner clumps wherever it's worthwhile. */
419
420
21.5M
    for (cp = clump_splay_walk_init(&sw, &save_mem); cp != 0; cp = clump_splay_walk_fwd(&sw)) {
421
20.1M
        if (cp->ctop - cp->cbot > min_inner_clump_space) {
422
            /* Create an inner clump to cover only the unallocated part. */
423
9.29M
            clump_t *inner =
424
9.29M
                gs_raw_alloc_struct_immovable(mem->non_gc_memory, &st_clump,
425
9.29M
                                              "alloc_save_space(inner)");
426
427
9.29M
            if (inner == 0)
428
0
                break;   /* maybe should fail */
429
9.29M
            alloc_init_clump(inner, cp->cbot, cp->ctop, cp->sreloc != 0, cp);
430
9.29M
            alloc_link_clump(inner, mem);
431
9.29M
            if_debug2m('u', (gs_memory_t *)mem, "[u]inner clump: cbot="PRI_INTPTR" ctop="PRI_INTPTR"\n",
432
9.29M
                       (intptr_t) inner->cbot, (intptr_t) inner->ctop);
433
9.29M
            if (cp == save_mem.cc)
434
1.25M
                new_cc = inner;
435
9.29M
        }
436
20.1M
    }
437
1.41M
    mem->cc = new_cc;
438
1.41M
    alloc_open_clump(mem);
439
440
1.41M
    save = gs_alloc_struct((gs_memory_t *) mem, alloc_save_t,
441
1.41M
                           &st_alloc_save, "alloc_save_space(save)");
442
1.41M
    if_debug2m('u', (gs_memory_t *)mem, "[u]save space %u at "PRI_INTPTR"\n",
443
1.41M
               mem->space, (intptr_t) save);
444
1.41M
    if (save == 0) {
445
        /* Free the inner clump structures.  This is the easiest way. */
446
0
        restore_free(mem);
447
0
        *mem = save_mem;
448
0
        return 0;
449
0
    }
450
1.41M
    save->client_data = NULL;
451
1.41M
    save->state = save_mem;
452
1.41M
    save->spaces = dmem->spaces;
453
1.41M
    save->restore_names = (name_memory(mem) == (gs_memory_t *) mem);
454
1.41M
    save->is_current = (dmem->current == mem);
455
1.41M
    save->id = sid;
456
1.41M
    mem->saved = save;
457
1.41M
    if_debug2m('u', (gs_memory_t *)mem, "[u%u]file_save "PRI_INTPTR"\n",
458
1.41M
               mem->space, (intptr_t) mem->streams);
459
1.41M
    mem->streams = 0;
460
1.41M
    mem->total_scanned = 0;
461
1.41M
    mem->total_scanned_after_compacting = 0;
462
1.41M
    if (sid)
463
1.41M
        mem->save_level++;
464
1.41M
    return save;
465
1.41M
}
466
467
/* Record a state change that must be undone for restore, */
468
/* and mark it as having been saved. */
469
int
470
alloc_save_change_in(gs_ref_memory_t *mem, const ref * pcont,
471
                  ref_packed * where, client_name_t cname)
472
2.39G
{
473
2.39G
    register alloc_change_t *cp;
474
475
2.39G
    if (mem->new_mask == 0)
476
2.39G
        return 0;    /* no saving */
477
6.17M
    cp = gs_alloc_struct((gs_memory_t *)mem, alloc_change_t,
478
6.17M
                         &st_alloc_change, "alloc_save_change");
479
6.17M
    if (cp == 0)
480
0
        return -1;
481
6.17M
    cp->next = mem->changes;
482
6.17M
    cp->where = where;
483
6.17M
    if (pcont == NULL)
484
0
        cp->offset = AC_OFFSET_STATIC;
485
6.17M
    else if (r_is_array(pcont) || r_has_type(pcont, t_dictionary))
486
6.17M
        cp->offset = AC_OFFSET_REF;
487
2
    else if (r_is_struct(pcont)) {
488
2
        assert ((byte *) where - (byte *) pcont->value.pstruct <= max_short && (byte *) where - (byte *) pcont->value.pstruct >= min_short);
489
2
        cp->offset = (byte *) where - (byte *) pcont->value.pstruct;
490
2
    }
491
0
    else {
492
0
        if_debug3('u', "Bad type %u for save!  pcont = "PRI_INTPTR", where = "PRI_INTPTR"\n",
493
0
                 r_type(pcont), (intptr_t) pcont, (intptr_t) where);
494
0
        gs_abort((const gs_memory_t *)mem);
495
0
    }
496
6.17M
    if (r_is_packed(where))
497
618k
        *(ref_packed *)&cp->contents = *where;
498
5.55M
    else {
499
5.55M
        ref_assign_inline(&cp->contents, (ref *) where);
500
5.55M
        r_set_attrs((ref *) where, l_new);
501
5.55M
    }
502
6.17M
    mem->changes = cp;
503
#ifdef DEBUG
504
    if (gs_debug_c('U')) {
505
        dmlprintf1((const gs_memory_t *)mem, "[U]save(%s)", client_name_string(cname));
506
        alloc_save_print((const gs_memory_t *)mem, cp, false);
507
    }
508
#endif
509
6.17M
    return 0;
510
6.17M
}
511
int
512
alloc_save_change(gs_dual_memory_t * dmem, const ref * pcont,
513
                  ref_packed * where, client_name_t cname)
514
2.39G
{
515
2.39G
    gs_ref_memory_t *mem =
516
2.39G
        (pcont == NULL ? dmem->space_local :
517
2.39G
         dmem->spaces_indexed[r_space(pcont) >> r_space_shift]);
518
519
2.39G
    return alloc_save_change_in(mem, pcont, where, cname);
520
2.39G
}
521
522
/* Allocate a structure for recording an allocation event. */
523
int
524
alloc_save_change_alloc(gs_ref_memory_t *mem, client_name_t cname, alloc_change_t **pcp)
525
245M
{
526
245M
    register alloc_change_t *cp;
527
528
245M
    if (mem->new_mask == 0)
529
214M
        return 0;    /* no saving */
530
31.1M
    cp = gs_alloc_struct((gs_memory_t *)mem, alloc_change_t,
531
31.1M
                         &st_alloc_change, "alloc_save_change");
532
31.1M
    if (cp == 0)
533
0
        return_error(gs_error_VMerror);
534
31.1M
    cp->next = mem->changes;
535
31.1M
    cp->where = 0;
536
31.1M
    cp->offset = AC_OFFSET_ALLOCATED;
537
31.1M
    make_null(&cp->contents);
538
31.1M
    *pcp = cp;
539
31.1M
    return 1;
540
31.1M
}
541
542
/* Remove an AC_OFFSET_ALLOCATED element. */
543
void
544
alloc_save_remove(gs_ref_memory_t *mem, ref_packed *obj, client_name_t cname)
545
104k
{
546
104k
    alloc_change_t **cpp = &mem->changes;
547
548
7.15M
    for (; *cpp != NULL;) {
549
7.04M
        alloc_change_t *cp = *cpp;
550
551
7.04M
        if (cp->offset == AC_OFFSET_ALLOCATED && cp->where == obj) {
552
65.7k
            if (mem->scan_limit == cp)
553
0
                mem->scan_limit = cp->next;
554
65.7k
            *cpp = cp->next;
555
65.7k
            gs_free_object((gs_memory_t *)mem, cp, "alloc_save_remove");
556
65.7k
        } else
557
6.98M
            cpp = &(*cpp)->next;
558
7.04M
    }
559
104k
}
560
561
/* Filter save change lists. */
562
static inline void
563
alloc_save__filter_changes_in_space(gs_ref_memory_t *mem)
564
7.13M
{
565
    /* This is a special function, which is called
566
       from the garbager after setting marks and before collecting
567
       unused space. Therefore it just resets marks for
568
       elements being released instead releasing them really. */
569
7.13M
    alloc_change_t **cpp = &mem->changes;
570
571
37.1M
    for (; *cpp != NULL; ) {
572
29.9M
        alloc_change_t *cp = *cpp;
573
574
29.9M
        if (cp->offset == AC_OFFSET_ALLOCATED && !check_l_mark(cp->where)) {
575
19.3M
            obj_header_t *pre = (obj_header_t *)cp - 1;
576
577
19.3M
            *cpp = cp->next;
578
19.3M
            cp->where = 0;
579
19.3M
            if (mem->scan_limit == cp)
580
120k
                mem->scan_limit = cp->next;
581
19.3M
            o_set_unmarked(pre);
582
19.3M
        } else
583
10.6M
            cpp = &(*cpp)->next;
584
29.9M
    }
585
7.13M
}
586
587
/* Filter save change lists. */
588
void
589
alloc_save__filter_changes(gs_ref_memory_t *memory)
590
1.85M
{
591
1.85M
    gs_ref_memory_t *mem = memory;
592
593
8.99M
    for  (; mem; mem = &mem->saved->state)
594
7.13M
        alloc_save__filter_changes_in_space(mem);
595
1.85M
}
596
597
/* Return (the id of) the innermost externally visible save object, */
598
/* i.e., the innermost save with a non-zero ID. */
599
ulong
600
alloc_save_current_id(const gs_dual_memory_t * dmem)
601
1.23M
{
602
1.23M
    const alloc_save_t *save = dmem->space_local->saved;
603
604
1.23M
    while (save != 0 && save->id == 0)
605
0
        save = save->state.saved;
606
1.23M
    if (save)
607
1.23M
        return save->id;
608
609
    /* This should never happen, if it does, return a totally
610
     * impossible value.
611
     */
612
0
    return (ulong)-1;
613
1.23M
}
614
alloc_save_t *
615
alloc_save_current(const gs_dual_memory_t * dmem)
616
1.23M
{
617
1.23M
    return alloc_find_save(dmem, alloc_save_current_id(dmem));
618
1.23M
}
619
620
/* Test whether a reference would be invalidated by a restore. */
621
bool
622
alloc_is_since_save(const void *vptr, const alloc_save_t * save)
623
8.12M
{
624
    /* A reference postdates a save iff it is in a clump allocated */
625
    /* since the save (including any carried-over inner clumps). */
626
627
8.12M
    const char *const ptr = (const char *)vptr;
628
8.12M
    register gs_ref_memory_t *mem = save->space_local;
629
630
8.12M
    if_debug2m('U', (gs_memory_t *)mem, "[U]is_since_save "PRI_INTPTR", "PRI_INTPTR":\n",
631
8.12M
               (intptr_t) ptr, (intptr_t) save);
632
8.12M
    if (mem->saved == 0) { /* This is a special case, the final 'restore' from */
633
        /* alloc_restore_all. */
634
182k
        return true;
635
182k
    }
636
    /* Check against clumps allocated since the save. */
637
    /* (There may have been intermediate saves as well.) */
638
7.94M
    for (;; mem = &mem->saved->state) {
639
7.94M
        if_debug1m('U', (gs_memory_t *)mem, "[U]checking mem="PRI_INTPTR"\n", (intptr_t) mem);
640
7.94M
        if (ptr_is_within_mem_clumps(ptr, mem)) {
641
35
            if_debug0m('U', (gs_memory_t *)mem, "[U+]found\n");
642
35
            return true;
643
35
        }
644
7.94M
        if_debug1m('U', (gs_memory_t *)mem, "[U-]not in any chunks belonging to "PRI_INTPTR"\n", (intptr_t) mem);
645
7.94M
        if (mem->saved == save) { /* We've checked all the more recent saves, */
646
            /* must be OK. */
647
7.94M
            break;
648
7.94M
        }
649
7.94M
    }
650
651
    /*
652
     * If we're about to do a global restore (a restore to the level 0),
653
     * and there is only one context using this global VM
654
     * (the normal case, in which global VM is saved by the
655
     * outermost save), we also have to check the global save.
656
     * Global saves can't be nested, which makes things easy.
657
     */
658
7.94M
    if (save->state.save_level == 0 /* Restoring to save level 0 - see bug 688157, 688161 */ &&
659
196k
        (mem = save->space_global) != save->space_local &&
660
196k
        save->space_global->num_contexts == 1
661
7.94M
        ) {
662
196k
        if_debug1m('U', (gs_memory_t *)mem, "[U]checking global mem="PRI_INTPTR"\n", (intptr_t) mem);
663
196k
        if (ptr_is_within_mem_clumps(ptr, mem)) {
664
0
            if_debug0m('U', (gs_memory_t *)mem, "[U+]  found\n");
665
0
            return true;
666
0
        }
667
196k
    }
668
7.94M
    return false;
669
670
7.94M
#undef ptr
671
7.94M
}
672
673
/* Test whether a name would be invalidated by a restore. */
674
bool
675
alloc_name_is_since_save(const gs_memory_t *mem,
676
                         const ref * pnref, const alloc_save_t * save)
677
327k
{
678
327k
    const name_string_t *pnstr;
679
680
327k
    if (!save->restore_names)
681
327k
        return false;
682
0
    pnstr = names_string_inline(mem->gs_lib_ctx->gs_name_table, pnref);
683
0
    if (pnstr->foreign_string)
684
0
        return false;
685
0
    return alloc_is_since_save(pnstr->string_bytes, save);
686
0
}
687
bool
688
alloc_name_index_is_since_save(const gs_memory_t *mem,
689
                               uint nidx, const alloc_save_t *save)
690
0
{
691
0
    const name_string_t *pnstr;
692
693
0
    if (!save->restore_names)
694
0
        return false;
695
0
    pnstr = names_index_string_inline(mem->gs_lib_ctx->gs_name_table, nidx);
696
0
    if (pnstr->foreign_string)
697
0
        return false;
698
0
    return alloc_is_since_save(pnstr->string_bytes, save);
699
0
}
700
701
/* Check whether any names have been created since a given save */
702
/* that might be released by the restore. */
703
bool
704
alloc_any_names_since_save(const alloc_save_t * save)
705
1.41M
{
706
1.41M
    return save->restore_names;
707
1.41M
}
708
709
/* Get the saved state with a given ID. */
710
alloc_save_t *
711
alloc_find_save(const gs_dual_memory_t * dmem, ulong sid)
712
3.17M
{
713
3.17M
    alloc_save_t *sprev = dmem->space_local->saved;
714
715
3.17M
    if (sid == 0)
716
0
        return 0;   /* invalid id */
717
77.9M
    while (sprev != 0) {
718
77.9M
        if (sprev->id == sid)
719
3.17M
            return sprev;
720
74.7M
        sprev = sprev->state.saved;
721
74.7M
    }
722
0
    return 0;
723
3.17M
}
724
725
/* Get the client data from a saved state. */
726
void *
727
alloc_save_client_data(const alloc_save_t * save)
728
1.23M
{
729
1.23M
    return save->client_data;
730
1.23M
}
731
732
/*
733
 * Do one step of restoring the state.  The client is responsible for
734
 * calling alloc_find_save to get the save object, and for ensuring that
735
 * there are no surviving pointers for which alloc_is_since_save is true.
736
 * Return true if the argument was the innermost save, in which case
737
 * this is the last (or only) step.
738
 * Note that "one step" may involve multiple internal steps,
739
 * if this is the outermost restore (which requires restoring both local
740
 * and global VM) or if we created extra save levels to reduce scanning.
741
 */
742
static void restore_finalize(gs_ref_memory_t *);
743
static void restore_space(gs_ref_memory_t *, gs_dual_memory_t *);
744
745
int
746
alloc_restore_step_in(gs_dual_memory_t *dmem, alloc_save_t * save)
747
1.23M
{
748
    /* Get save->space_* now, because the save object will be freed. */
749
1.23M
    gs_ref_memory_t *lmem = save->space_local;
750
1.23M
    gs_ref_memory_t *gmem = save->space_global;
751
1.23M
    gs_ref_memory_t *mem = lmem;
752
1.23M
    alloc_save_t *sprev;
753
1.23M
    int code;
754
755
    /* Finalize all objects before releasing resources or undoing changes. */
756
1.23M
    do {
757
1.23M
        ulong sid;
758
759
1.23M
        sprev = mem->saved;
760
1.23M
        sid = sprev->id;
761
1.23M
        restore_finalize(mem);  /* finalize objects */
762
1.23M
        mem = &sprev->state;
763
1.23M
        if (sid != 0)
764
1.23M
            break;
765
1.23M
    }
766
1.23M
    while (sprev != save);
767
1.23M
    if (mem->save_level == 0) {
768
        /* This is the outermost save, which might also */
769
        /* need to restore global VM. */
770
182k
        mem = gmem;
771
182k
        if (mem != lmem && mem->saved != 0) {
772
182k
            restore_finalize(mem);
773
182k
        }
774
182k
    }
775
776
    /* Do one (externally visible) step of restoring the state. */
777
1.23M
    mem = lmem;
778
1.23M
    do {
779
1.23M
        ulong sid;
780
781
1.23M
        sprev = mem->saved;
782
1.23M
        sid = sprev->id;
783
1.23M
        code = restore_resources(sprev, mem); /* release other resources */
784
1.23M
        if (code < 0)
785
0
            return code;
786
1.23M
        restore_space(mem, dmem); /* release memory */
787
1.23M
        if (sid != 0)
788
1.23M
            break;
789
1.23M
    }
790
1.23M
    while (sprev != save);
791
792
1.23M
    if (mem->save_level == 0) {
793
        /* This is the outermost save, which might also */
794
        /* need to restore global VM. */
795
182k
        mem = gmem;
796
182k
        if (mem != lmem && mem->saved != 0) {
797
182k
            code = restore_resources(mem->saved, mem);
798
182k
            if (code < 0)
799
0
                return code;
800
182k
            restore_space(mem, dmem);
801
182k
        }
802
182k
        alloc_set_not_in_save(dmem);
803
1.05M
    } else {     /* Set the l_new attribute in all slots that are now new. */
804
1.05M
        ulong scanned;
805
806
1.05M
        code = save_set_new(mem, true, false, &scanned);
807
1.05M
        if (code < 0)
808
0
            return code;
809
1.05M
    }
810
811
1.23M
    return sprev == save;
812
1.23M
}
813
/* Restore the memory of one space, by undoing changes and freeing */
814
/* memory allocated since the save. */
815
static void
816
restore_space(gs_ref_memory_t * mem, gs_dual_memory_t *dmem)
817
1.41M
{
818
1.41M
    alloc_save_t *save = mem->saved;
819
1.41M
    alloc_save_t saved;
820
821
1.41M
    print_save("restore", mem->space, save);
822
823
    /* Undo changes since the save. */
824
1.41M
    {
825
1.41M
        register alloc_change_t *cp = mem->changes;
826
827
19.3M
        while (cp) {
828
#ifdef DEBUG
829
            if (gs_debug_c('U')) {
830
                dmlputs((const gs_memory_t *)mem, "[U]restore");
831
                alloc_save_print((const gs_memory_t *)mem, cp, true);
832
            }
833
#endif
834
17.8M
            if (cp->offset == AC_OFFSET_ALLOCATED)
835
17.8M
                DO_NOTHING;
836
6.17M
            else
837
6.17M
            if (r_is_packed(&cp->contents))
838
618k
                *cp->where = *(ref_packed *) & cp->contents;
839
5.55M
            else
840
5.55M
                ref_assign_inline((ref *) cp->where, &cp->contents);
841
17.8M
            cp = cp->next;
842
17.8M
        }
843
1.41M
    }
844
845
    /* Free memory allocated since the save. */
846
    /* Note that this frees all clumps except the inner ones */
847
    /* belonging to this level. */
848
1.41M
    saved = *save;
849
1.41M
    restore_free(mem);
850
851
    /* Restore the allocator state. */
852
1.41M
    {
853
1.41M
        int num_contexts = mem->num_contexts; /* don't restore */
854
855
1.41M
        *mem = saved.state;
856
1.41M
        mem->num_contexts = num_contexts;
857
1.41M
    }
858
1.41M
    alloc_open_clump(mem);
859
860
    /* Make the allocator current if it was current before the save. */
861
1.41M
    if (saved.is_current) {
862
1.23M
        dmem->current = mem;
863
1.23M
        dmem->current_space = mem->space;
864
1.23M
    }
865
1.41M
}
866
867
/* Restore to the initial state, releasing all resources. */
868
/* The allocator is no longer usable after calling this routine! */
869
int
870
alloc_restore_all(i_ctx_t *i_ctx_p)
871
182k
{
872
    /*
873
     * Save the memory pointers, since freeing space_local will also
874
     * free dmem itself.
875
     */
876
182k
    gs_ref_memory_t *lmem = idmemory->space_local;
877
182k
    gs_ref_memory_t *gmem = idmemory->space_global;
878
182k
    gs_ref_memory_t *smem = idmemory->space_system;
879
880
182k
    gs_ref_memory_t *mem;
881
182k
    int code;
882
883
    /* Restore to a state outside any saves. */
884
1.29M
    while (lmem->save_level != 0) {
885
1.10M
        vm_save_t *vmsave = alloc_save_client_data(alloc_save_current(idmemory));
886
1.10M
        if (vmsave->gsave) {
887
1.10M
            gs_grestoreall_for_restore(i_ctx_p->pgs, vmsave->gsave);
888
1.10M
        }
889
1.10M
        vmsave->gsave = 0;
890
1.10M
        code = alloc_restore_step_in(idmemory, lmem->saved);
891
892
1.10M
        if (code < 0)
893
0
            return code;
894
1.10M
    }
895
896
    /* Finalize memory. */
897
182k
    restore_finalize(lmem);
898
182k
    if ((mem = (gs_ref_memory_t *)lmem->stable_memory) != lmem)
899
182k
        restore_finalize(mem);
900
182k
    if (gmem != lmem && gmem->num_contexts == 1) {
901
182k
        restore_finalize(gmem);
902
182k
        if ((mem = (gs_ref_memory_t *)gmem->stable_memory) != gmem)
903
182k
            restore_finalize(mem);
904
182k
    }
905
182k
    restore_finalize(smem);
906
907
    /* Release resources other than memory, using fake */
908
    /* save and memory objects. */
909
182k
    {
910
182k
        alloc_save_t empty_save;
911
912
182k
        empty_save.spaces = idmemory->spaces;
913
182k
        empty_save.restore_names = false; /* don't bother to release */
914
182k
        code = restore_resources(&empty_save, NULL);
915
182k
        if (code < 0)
916
0
            return code;
917
182k
    }
918
919
    /* Finally, release memory. */
920
182k
    restore_free(lmem);
921
182k
    if ((mem = (gs_ref_memory_t *)lmem->stable_memory) != lmem)
922
182k
        restore_free(mem);
923
182k
    if (gmem != lmem) {
924
182k
        if (!--(gmem->num_contexts)) {
925
182k
            restore_free(gmem);
926
182k
            if ((mem = (gs_ref_memory_t *)gmem->stable_memory) != gmem)
927
182k
                restore_free(mem);
928
182k
        }
929
182k
    }
930
182k
    restore_free(smem);
931
182k
    return 0;
932
182k
}
933
934
/*
935
 * Finalize objects that will be freed by a restore.
936
 * Note that we must temporarily disable the freeing operations
937
 * of the allocator while doing this.
938
 */
939
static void
940
restore_finalize(gs_ref_memory_t * mem)
941
2.32M
{
942
2.32M
    clump_t *cp;
943
2.32M
    clump_splay_walker sw;
944
945
2.32M
    alloc_close_clump(mem);
946
2.32M
    gs_enable_free((gs_memory_t *) mem, false);
947
39.5M
    for (cp = clump_splay_walk_bwd_init(&sw, mem); cp != 0; cp = clump_splay_walk_bwd(&sw)) {
948
308M
        SCAN_CLUMP_OBJECTS(cp)
949
308M
            DO_ALL
950
308M
            struct_proc_finalize((*finalize)) =
951
308M
            pre->o_type->finalize;
952
308M
        if (finalize != 0) {
953
10.8M
            if_debug2m('u', (gs_memory_t *)mem, "[u]restore finalizing %s "PRI_INTPTR"\n",
954
10.8M
                       struct_type_name_string(pre->o_type),
955
10.8M
                       (intptr_t) (pre + 1));
956
10.8M
            (*finalize) ((gs_memory_t *) mem, pre + 1);
957
10.8M
        }
958
308M
        END_OBJECTS_SCAN
959
37.2M
    }
960
2.32M
    gs_enable_free((gs_memory_t *) mem, true);
961
2.32M
}
962
963
/* Release resources for a restore */
964
static int
965
restore_resources(alloc_save_t * sprev, gs_ref_memory_t * mem)
966
1.59M
{
967
1.59M
    int code;
968
#ifdef DEBUG
969
    if (mem) {
970
        /* Note restoring of the file list. */
971
        if_debug4m('u', (gs_memory_t *)mem, "[u%u]file_restore "PRI_INTPTR" => "PRI_INTPTR" for "PRI_INTPTR"\n",
972
                   mem->space, (intptr_t)mem->streams,
973
                   (intptr_t)sprev->state.streams, (intptr_t)sprev);
974
    }
975
#endif
976
977
    /* Remove entries from font and character caches. */
978
1.59M
    code = font_restore(sprev);
979
1.59M
    if (code < 0)
980
0
        return code;
981
982
    /* Adjust the name table. */
983
1.59M
    if (sprev->restore_names)
984
0
        names_restore(mem->gs_lib_ctx->gs_name_table, sprev);
985
1.59M
    return 0;
986
1.59M
}
987
988
/* Release memory for a restore. */
989
static void
990
restore_free(gs_ref_memory_t * mem)
991
2.32M
{
992
    /* Free clumps allocated since the save. */
993
2.32M
    gs_free_all((gs_memory_t *) mem);
994
2.32M
}
995
996
static inline int
997
mark_allocated(void *obj, bool to_new, uint *psize)
998
15.6M
{
999
15.6M
    obj_header_t *pre = (obj_header_t *)obj - 1;
1000
15.6M
    uint size = pre_obj_contents_size(pre);
1001
15.6M
    ref_packed *prp = (ref_packed *) (pre + 1);
1002
15.6M
    ref_packed *next = (ref_packed *) ((char *)prp + size);
1003
#ifdef ALIGNMENT_ALIASING_BUG
1004
                ref *rpref;
1005
# define RP_REF(rp) (rpref = (ref *)rp, rpref)
1006
#else
1007
3.11G
# define RP_REF(rp) ((ref *)rp)
1008
15.6M
#endif
1009
1010
15.6M
    if (pre->o_type != &st_refs) {
1011
        /* Must not happen. */
1012
0
        if_debug0('u', "Wrong object type when expected a ref.\n");
1013
0
        return_error(gs_error_Fatal);
1014
0
    }
1015
    /* We know that every block of refs ends with */
1016
    /* a full-size ref, so we only need the end check */
1017
    /* when we encounter one of those. */
1018
15.6M
    if (to_new)
1019
587M
        while (1) {
1020
587M
            if (r_is_packed(prp))
1021
39.2M
                prp++;
1022
547M
            else {
1023
547M
                RP_REF(prp)->tas.type_attrs |= l_new;
1024
547M
                prp += packed_per_ref;
1025
547M
                if (prp >= next)
1026
5.08M
                    break;
1027
547M
            }
1028
587M
    } else
1029
2.75G
        while (1) {
1030
2.75G
            if (r_is_packed(prp))
1031
196M
                prp++;
1032
2.56G
            else {
1033
2.56G
                RP_REF(prp)->tas.type_attrs &= ~l_new;
1034
2.56G
                prp += packed_per_ref;
1035
2.56G
                if (prp >= next)
1036
10.5M
                    break;
1037
2.56G
            }
1038
2.75G
        }
1039
15.6M
#undef RP_REF
1040
15.6M
    *psize = size;
1041
15.6M
    return 0;
1042
15.6M
}
1043
1044
/* Check if a block contains refs marked by garbager. */
1045
static bool
1046
check_l_mark(void *obj)
1047
22.1M
{
1048
22.1M
    obj_header_t *pre = (obj_header_t *)obj - 1;
1049
22.1M
    uint size = pre_obj_contents_size(pre);
1050
22.1M
    ref_packed *prp = (ref_packed *) (pre + 1);
1051
22.1M
    ref_packed *next = (ref_packed *) ((char *)prp + size);
1052
#ifdef ALIGNMENT_ALIASING_BUG
1053
                ref *rpref;
1054
# define RP_REF(rp) (rpref = (ref *)rp, rpref)
1055
#else
1056
22.1M
# define RP_REF(rp) ((ref *)rp)
1057
22.1M
#endif
1058
1059
    /* We know that every block of refs ends with */
1060
    /* a full-size ref, so we only need the end check */
1061
    /* when we encounter one of those. */
1062
31.9G
    while (1) {
1063
31.9G
        if (r_is_packed(prp)) {
1064
577M
            if (r_has_pmark(prp))
1065
16.3k
                return true;
1066
577M
            prp++;
1067
31.3G
        } else {
1068
31.3G
            if (r_has_attr(RP_REF(prp), l_mark))
1069
2.75M
                return true;
1070
31.3G
            prp += packed_per_ref;
1071
31.3G
            if (prp >= next)
1072
19.3M
                return false;
1073
31.3G
        }
1074
31.9G
    }
1075
22.1M
#undef RP_REF
1076
22.1M
}
1077
1078
/* Set or reset the l_new attribute in every relevant slot. */
1079
/* This includes every slot on the current change chain, */
1080
/* and every (ref) slot allocated at this save level. */
1081
/* Return the number of bytes of data scanned. */
1082
static int
1083
save_set_new(gs_ref_memory_t * mem, bool to_new, bool set_limit, ulong *pscanned)
1084
2.10M
{
1085
2.10M
    ulong scanned = 0;
1086
2.10M
    int code;
1087
1088
    /* Handle the change chain. */
1089
2.10M
    code = save_set_new_changes(mem, to_new, set_limit);
1090
2.10M
    if (code < 0)
1091
0
        return code;
1092
1093
    /* Handle newly allocated ref objects. */
1094
7.61M
    SCAN_MEM_CLUMPS(mem, cp) {
1095
7.61M
        if (cp->has_refs) {
1096
1.88M
            bool has_refs = false;
1097
1.88M
            bool no_outer_clump = !(cp->outer != NULL && cp->ctop - cp->cbot > min_inner_clump_space);
1098
26.4M
            SCAN_CLUMP_OBJECTS(cp)
1099
26.4M
                DO_ALL
1100
26.4M
                if_debug3m('U', (gs_memory_t *)mem, "[U]set_new scan("PRI_INTPTR"(%u), %d)\n",
1101
26.4M
                           (intptr_t) pre, size, to_new);
1102
26.4M
            if (pre->o_type == &st_refs) {
1103
                /* These are refs, scan them. */
1104
8.13M
                ref_packed *prp = (ref_packed *) (pre + 1);
1105
8.13M
                uint size;
1106
                /* In order to avoid the garbager unnecessarily scanning for refs that may
1107
                   not exist, we reset the "has_refs" flag if we're doing a save (and leave
1108
                   it alone during a restore. This generally works because when we get here
1109
                   during a save, we've already created the inner clump, and during a restore,
1110
                   we've already restored to the outer clump.
1111
                   Where is goes wrong is when there isn't sufficient space left in the clump
1112
                   for any new allocations, so we won't have created the inner clump, and then
1113
                   the flag isn't retained. Spot that above, and only meddle with the flag here if
1114
                   an inner clump has been created.
1115
                 */
1116
8.13M
                has_refs = true && (to_new | no_outer_clump);
1117
8.13M
                code = mark_allocated(prp, to_new, &size);
1118
8.13M
                if (code < 0)
1119
0
                    return code;
1120
8.13M
                scanned += size;
1121
8.13M
            } else
1122
18.2M
                scanned += sizeof(obj_header_t);
1123
26.4M
            END_OBJECTS_SCAN
1124
1.88M
                cp->has_refs = has_refs;
1125
1.88M
        }
1126
7.61M
    }
1127
7.61M
    END_CLUMPS_SCAN
1128
2.10M
    if_debug2m('u', (gs_memory_t *)mem, "[u]set_new (%s) scanned %ld\n",
1129
2.10M
               (to_new ? "restore" : "save"), scanned);
1130
2.10M
    *pscanned = scanned;
1131
2.10M
    return 0;
1132
2.10M
}
1133
1134
/* Drop redundant elements from the changes list and set l_new. */
1135
static void
1136
drop_redundant_changes(gs_ref_memory_t * mem)
1137
17
{
1138
17
    register alloc_change_t *chp = mem->changes, *chp_back = NULL, *chp_forth;
1139
1140
    /* As we are trying to throw away redundant changes in an allocator instance
1141
       that has already been "saved", the active clump has already been "closed"
1142
       by alloc_save_space(). Using such an allocator (for example, by calling
1143
       gs_free_object() with it) can leave it in an unstable state, causing
1144
       problems for the garbage collector (specifically, the clump validator code).
1145
       So, before we might use it, open the current clump, and then close it again
1146
       when we're done.
1147
     */
1148
17
    alloc_open_clump(mem);
1149
1150
    /* First reverse the list and set all. */
1151
1.79k
    for (; chp; chp = chp_forth) {
1152
1.77k
        chp_forth = chp->next;
1153
1.77k
        if (chp->offset != AC_OFFSET_ALLOCATED) {
1154
49
            ref_packed *prp = chp->where;
1155
1156
49
            if (!r_is_packed(prp)) {
1157
45
                ref *const rp = (ref *)prp;
1158
1159
45
                rp->tas.type_attrs |= l_new;
1160
45
            }
1161
49
        }
1162
1.77k
        chp->next = chp_back;
1163
1.77k
        chp_back = chp;
1164
1.77k
    }
1165
17
    mem->changes = chp_back;
1166
17
    chp_back = NULL;
1167
    /* Then filter, reset and reverse again. */
1168
1.79k
    for (chp = mem->changes; chp; chp = chp_forth) {
1169
1.77k
        chp_forth = chp->next;
1170
1.77k
        if (chp->offset != AC_OFFSET_ALLOCATED) {
1171
49
            ref_packed *prp = chp->where;
1172
1173
49
            if (!r_is_packed(prp)) {
1174
45
                ref *const rp = (ref *)prp;
1175
1176
45
                if ((rp->tas.type_attrs & l_new) == 0) {
1177
4
                    if (mem->scan_limit == chp)
1178
0
                        mem->scan_limit = chp_back;
1179
4
                    if (mem->changes == chp)
1180
0
                        mem->changes = chp_back;
1181
4
                    gs_free_object((gs_memory_t *)mem, chp, "alloc_save_remove");
1182
4
                    continue;
1183
4
                } else
1184
41
                    rp->tas.type_attrs &= ~l_new;
1185
45
            }
1186
49
        }
1187
1.77k
        chp->next = chp_back;
1188
1.77k
        chp_back = chp;
1189
1.77k
    }
1190
17
    mem->changes = chp_back;
1191
1192
17
    alloc_close_clump(mem);
1193
17
}
1194
1195
/* Set or reset the l_new attribute on the changes chain. */
1196
static int
1197
save_set_new_changes(gs_ref_memory_t * mem, bool to_new, bool set_limit)
1198
2.10M
{
1199
2.10M
    register alloc_change_t *chp;
1200
2.10M
    register uint new = (to_new ? l_new : 0);
1201
2.10M
    ulong scanned = 0;
1202
1203
2.10M
    if (!to_new && mem->total_scanned_after_compacting > max_repeated_scan * 16) {
1204
17
        mem->total_scanned_after_compacting = 0;
1205
17
        drop_redundant_changes(mem);
1206
17
    }
1207
12.4M
    for (chp = mem->changes; chp; chp = chp->next) {
1208
10.3M
        if (chp->offset == AC_OFFSET_ALLOCATED) {
1209
7.47M
            if (chp->where != 0) {
1210
7.47M
                uint size;
1211
7.47M
                int code = mark_allocated((void *)chp->where, to_new, &size);
1212
1213
7.47M
                if (code < 0)
1214
0
                    return code;
1215
7.47M
                scanned += size;
1216
7.47M
            }
1217
7.47M
        } else {
1218
2.87M
            ref_packed *prp = chp->where;
1219
1220
2.87M
            if_debug3m('U', (gs_memory_t *)mem, "[U]set_new "PRI_INTPTR": ("PRI_INTPTR", %d)\n",
1221
2.87M
                       (intptr_t)chp, (intptr_t)prp, new);
1222
2.87M
            if (!r_is_packed(prp)) {
1223
2.85M
                ref *const rp = (ref *) prp;
1224
1225
2.85M
                rp->tas.type_attrs =
1226
2.85M
                    (rp->tas.type_attrs & ~l_new) + new;
1227
2.85M
            }
1228
2.87M
        }
1229
10.3M
        if (mem->scan_limit == chp)
1230
18.2k
            break;
1231
10.3M
    }
1232
2.10M
    if (set_limit) {
1233
1.05M
        mem->total_scanned_after_compacting += scanned;
1234
1.05M
        if (scanned  + mem->total_scanned >= max_repeated_scan) {
1235
10.0k
            mem->scan_limit = mem->changes;
1236
10.0k
            mem->total_scanned = 0;
1237
10.0k
        } else
1238
1.04M
            mem->total_scanned += scanned;
1239
1.05M
    }
1240
2.10M
    return 0;
1241
2.10M
}
1242
1243
gs_memory_t *
1244
gs_save_any_memory(const alloc_save_t *save)
1245
1.59M
{
1246
1.59M
    return((gs_memory_t *)save->space_local);
1247
1.59M
}