summaryrefslogtreecommitdiff
path: root/rts/sm/NonMovingShortcut.c
blob: 83c4857677b4fd0327b130d844ad5a6c94a41f95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/* -----------------------------------------------------------------------------
 *
 * (c) The GHC Team, 1998-2019
 *
 * Non-moving garbage collector and allocator:
 * Indirection short-cutting and the selector optimisation
 *
 * ---------------------------------------------------------------------------*/

#include "Rts.h"
#include "GC.h"
#include "SMPClosureOps.h"
#include "NonMovingMark.h"
#include "NonMovingShortcut.h"
#include "Printer.h"

#define MAX_THUNK_SELECTOR_DEPTH 16

//#define SELECTOR_OPT_DEBUG

#if defined(SELECTOR_OPT_DEBUG)
static void
print_selector_chain(StgClosure *p)
{
    debugBelch("Selector chain: %p", (void*)p);
    StgClosure *next = p->payload[0];
    while (next != NULL) {
        debugBelch(", %p", next);
        next = next->payload[0];
    }
    debugBelch("\n");
}
#endif

static void
update_selector_chain(
        StgClosure *chain,
        StgClosure **origin,
        StgSelector * const p0,
        StgClosure * const val
) {
    ASSERT(val != NULL);

    // Make sure we don't introduce non-moving-to-moving pointers here.
    ASSERT(isNonmovingClosure(val));

    // This case we can't handle because we don't know info ptr of the closure
    // before we locked it.
    ASSERT(chain != val);

#if defined(SELECTOR_OPT_DEBUG)
    if (chain != NULL) {
        print_selector_chain(chain);
        debugBelch("Value: ");
        printClosure(val);
    }
#endif

    while (chain) {
        // debugBelch("chain: %p\n", (void*)chain);

        StgClosure *next = chain->payload[0];

        // We only update closures in the non-moving heap
        ASSERT(isNonmovingClosure(chain));

        ((StgInd*)chain)->indirectee = val;
        unlockClosure((StgClosure*)chain, &stg_IND_info);

        chain = next;
    }

    if (origin != NULL && (StgClosure*)p0 != val) {
        cas((StgVolatilePtr)origin, (StgWord)p0, (StgWord)val);
    }
}

// Returns value of the selector thunk. The value is a non-moving closure. If
// it's not possible to evaluate the selector thunk the return value will be the
// selector itself.
static StgClosure*
nonmoving_eval_thunk_selector_(
        MarkQueue *queue,
        StgSelector * const p0,
        StgClosure ** const origin,
        int depth
) {
    // This function should only be called on non-moving objects.
    ASSERT(HEAP_ALLOCED_GC((P_)p0) && isNonmovingClosure((StgClosure*)p0));

    markQueuePushClosure(queue, (StgClosure*)p0, NULL);

    // INVARIANT: A non-moving object. Locked (below).
    StgClosure *p = (StgClosure*)p0;

    // Chain of non-moving selectors to update. These will be INDs to `p` when
    // we reach to a value. INVARIANT: All objects in the chain are locked, and
    // in the non-moving heap.
    StgClosure *chain = NULL;

    // Variables to update: p.
selector_changed:
    ;

    // debugBelch("Selector changed: %p\n", (void*)p);

    // Lock the selector to avoid concurrent modification in mutators
    const StgInfoTable *selector_info_ptr = lockClosure((StgClosure*)p);
    StgInfoTable *selector_info_tbl = INFO_PTR_TO_STRUCT(selector_info_ptr);

    if (selector_info_tbl->type != THUNK_SELECTOR) {
        // Selector updated in the meantime, or we reached to a value. Update
        // the chain.
        unlockClosure(p, selector_info_ptr);
        update_selector_chain(chain, origin, p0, p);
        return p;
    }

    // The closure is locked and it's a selector thunk. If the selectee is a
    // CONSTR we do the selection here and the In the selected value will be the
    // value of this selector thunk.
    //
    // Two cases:
    //
    // - If the selected value is also a selector thunk, then we loop and
    //   evaluate it. The final value will be the value of both the current
    //   selector and the selected value (which is also a selector thunk).
    //
    // - If the selectee is a selector thunk, we recursively evaluate it (up to
    //   a certain depth, specified with MAX_THUNK_SELECTOR_DEPTH), then do the
    //   selection on the value of it.

    //
    // Do the selection
    //

    uint32_t field = selector_info_tbl->layout.selector_offset;
    StgClosure *selectee = UNTAG_CLOSURE(((StgSelector*)p)->selectee);

    // Variables to update: selectee
selectee_changed:
    // debugBelch("Selectee changed: %p\n", (void*)selectee);

    if (!isNonmovingClosure(selectee)) {
        // The selectee is a moving object, and it may be moved by a concurrent
        // minor GC while we read the info table and fields, so don't try to
        // read the fields, just update the chain.
        unlockClosure(p, selector_info_ptr);
        update_selector_chain(chain, origin, p0, p);
        return p;
    }

    // Selectee is a non-moving object, mark it.
    markQueuePushClosure(queue, selectee, NULL);

    const StgInfoTable *selectee_info_tbl = get_itbl(selectee);
    switch (selectee_info_tbl->type) {
        case WHITEHOLE: {
            // Probably a loop. Abort.
            unlockClosure(p, selector_info_ptr);
            update_selector_chain(chain, origin, p0, p);
            return p;
        }

        case CONSTR:
        case CONSTR_1_0:
        case CONSTR_0_1:
        case CONSTR_2_0:
        case CONSTR_1_1:
        case CONSTR_0_2:
        case CONSTR_NOCAF: {
            // Selectee is a constructor in the non-moving heap.
            // Select the field.

            // Check that the size is in range.
            ASSERT(field < (StgWord32)(selectee_info_tbl->layout.payload.ptrs +
                                       selectee_info_tbl->layout.payload.nptrs));

            StgClosure *val = UNTAG_CLOSURE(selectee->payload[field]);

            // `val` is the value of this selector thunk. We need to check a
            // few cases:
            //
            // - If `val` is in the moving heap, we stop here and update the
            //   chain. All updated objects should be added to the mut_list.
            //   (TODO (osa): What happens if the value is evacuated as we do
            //   this?)
            //
            // - If `val` is in the non-moving heap, we check if it's also a
            //   selector. If it is we add it to the chain and loop.

            // Follow indirections. Variables to update: `val`.
        val_changed:
            if (!isNonmovingClosure(val)) {
                // The selected value is a moving object, so we won't be
                // updating the chain to this object.
                unlockClosure(p, selector_info_ptr);
                update_selector_chain(chain, origin, p0, p);
                return p;
            }

            switch (get_itbl(val)->type) {
            case IND:
            case IND_STATIC:
                ;
                // Follow the indirection
                StgClosure *indirectee = UNTAG_CLOSURE(((StgInd*)val)->indirectee);
                if (isNonmovingClosure(indirectee)) {
                    val = UNTAG_CLOSURE(((StgInd*)val)->indirectee);
                    goto val_changed;
                } else {
                    unlockClosure(p, selector_info_ptr);
                    update_selector_chain(chain, origin, p0, p);
                    return p;
                }
            case THUNK_SELECTOR:
                // Value of the selector thunk is again a selector thunk in the
                // non-moving heap. Add the current selector to the chain and
                // loop.
                p->payload[0] = chain;
                chain = p;
                p = val;
                goto selector_changed;
            default:
                // Found a value, add the current selector to the chain and
                // update it.
                p->payload[0] = chain;
                chain = p;
                update_selector_chain(chain, origin, p0, val);
                return val;
            }
        }

        case IND:
        case IND_STATIC: {
            StgClosure *indirectee = UNTAG_CLOSURE(((StgInd *)selectee)->indirectee);
            if (isNonmovingClosure(indirectee)) {
                selectee = indirectee;
                goto selectee_changed;
            } else {
                unlockClosure(p, selector_info_ptr);
                update_selector_chain(chain, origin, p0, p);
                return p;
            }
        }

        case BLACKHOLE: {
            StgClosure *indirectee = ((StgInd*)selectee)->indirectee;

            if (!isNonmovingClosure(UNTAG_CLOSURE(indirectee))) {
                unlockClosure(p, selector_info_ptr);
                update_selector_chain(chain, origin, p0, p);
                return p;
            }

            // Establish whether this BH has been updated, and is now an
            // indirection, as in evacuate().
            if (GET_CLOSURE_TAG(indirectee) == 0) {
                const StgInfoTable *i = indirectee->header.info;
                if (i == &stg_TSO_info
                    || i == &stg_WHITEHOLE_info
                    || i == &stg_BLOCKING_QUEUE_CLEAN_info
                    || i == &stg_BLOCKING_QUEUE_DIRTY_info) {
                    unlockClosure(p, selector_info_ptr);
                    update_selector_chain(chain, origin, p0, p);
                    return (StgClosure*)p;
                }
                ASSERT(i != &stg_IND_info); // TODO not sure about this part
            }

            // It's an indirection, follow it.
            selectee = UNTAG_CLOSURE(indirectee);
            goto selectee_changed;
        }

        case AP:
        case AP_STACK:
        case THUNK:
        case THUNK_1_0:
        case THUNK_0_1:
        case THUNK_2_0:
        case THUNK_1_1:
        case THUNK_0_2:
        case THUNK_STATIC: {
            // Not evaluated yet
            unlockClosure(p, selector_info_ptr);
            update_selector_chain(chain, origin, p0, p);
            return (StgClosure*)p;
        }

        case THUNK_SELECTOR: {
            // Selectee is a selector thunk. Evaluate it if we haven't reached
            // the recursion limit yet.
            if (depth < MAX_THUNK_SELECTOR_DEPTH) {
                StgClosure *new_selectee =
                    UNTAG_CLOSURE(nonmoving_eval_thunk_selector_(
                                queue, (StgSelector*)selectee, NULL, depth+1));
                ASSERT(isNonmovingClosure(new_selectee));
                if (selectee == new_selectee) {
                    unlockClosure(p, selector_info_ptr);
                    update_selector_chain(chain, origin, p0, p);
                    return (StgClosure*)p;
                } else {
                    selectee = new_selectee;
                    goto selectee_changed;
                }
            } else {
                // Recursion limit reached
                unlockClosure(p, selector_info_ptr);
                update_selector_chain(chain, origin, p0, p);
                return (StgClosure*)p;
            }
        }

        default: {
            barf("nonmoving_eval_thunk_selector: strange selectee %d",
                 (int)(selectee_info_tbl->type));
        }
    }
}

void
nonmoving_eval_thunk_selector(MarkQueue *queue, StgSelector *p, StgClosure **origin)
{
    nonmoving_eval_thunk_selector_(queue, p, origin, 0);
}