summaryrefslogtreecommitdiff
path: root/src/jit/jitgcinfo.h
blob: 193e77c172ca4f8ade2d9ff9d2e546e087f021f5 (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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//  Garbage-collector information
//  Keeps track of which variables hold pointers.
//  Generates the GC-tables

#ifndef _JITGCINFO_H_
#define _JITGCINFO_H_

#include "gcinfotypes.h"

#ifndef JIT32_GCENCODER
#include "gcinfoencoder.h"
#endif

/*****************************************************************************/

#ifndef JIT32_GCENCODER
// Shash typedefs
struct RegSlotIdKey
{
    unsigned short m_regNum;
    unsigned short m_flags;

    RegSlotIdKey()
    {
    }

    RegSlotIdKey(unsigned short regNum, unsigned short flags) : m_regNum(regNum), m_flags(flags)
    {
    }

    static unsigned GetHashCode(RegSlotIdKey rsk)
    {
        return (rsk.m_flags << (8 * sizeof(unsigned short))) + rsk.m_regNum;
    }

    static bool Equals(RegSlotIdKey rsk1, RegSlotIdKey rsk2)
    {
        return rsk1.m_regNum == rsk2.m_regNum && rsk1.m_flags == rsk2.m_flags;
    }
};

struct StackSlotIdKey
{
    int            m_offset;
    bool           m_fpRel;
    unsigned short m_flags;

    StackSlotIdKey()
    {
    }

    StackSlotIdKey(int offset, bool fpRel, unsigned short flags) : m_offset(offset), m_fpRel(fpRel), m_flags(flags)
    {
    }

    static unsigned GetHashCode(StackSlotIdKey ssk)
    {
        return (ssk.m_flags << (8 * sizeof(unsigned short))) ^ (unsigned)ssk.m_offset ^ (ssk.m_fpRel ? 0x1000000 : 0);
    }

    static bool Equals(StackSlotIdKey ssk1, StackSlotIdKey ssk2)
    {
        return ssk1.m_offset == ssk2.m_offset && ssk1.m_fpRel == ssk2.m_fpRel && ssk1.m_flags == ssk2.m_flags;
    }
};

typedef JitHashTable<RegSlotIdKey, RegSlotIdKey, GcSlotId>     RegSlotMap;
typedef JitHashTable<StackSlotIdKey, StackSlotIdKey, GcSlotId> StackSlotMap;
#endif

typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, VARSET_TP*> NodeToVarsetPtrMap;

class GCInfo
{
    friend class CodeGen;

private:
    Compiler* compiler;
    RegSet*   regSet;

public:
    GCInfo(Compiler* theCompiler);

    void gcResetForBB();

    void gcMarkRegSetGCref(regMaskTP regMask DEBUGARG(bool forceOutput = false));
    void gcMarkRegSetByref(regMaskTP regMask DEBUGARG(bool forceOutput = false));
    void gcMarkRegSetNpt(regMaskTP regMask DEBUGARG(bool forceOutput = false));
    void gcMarkRegPtrVal(regNumber reg, var_types type);

#ifdef DEBUG
    void gcDspGCrefSetChanges(regMaskTP gcRegGCrefSetNew DEBUGARG(bool forceOutput = false));
    void gcDspByrefSetChanges(regMaskTP gcRegByrefSetNew DEBUGARG(bool forceOutput = false));
#endif // DEBUG

    /*****************************************************************************/

    //-------------------------------------------------------------------------
    //
    //  The following keeps track of which registers currently hold pointer
    //  values.
    //

    regMaskTP gcRegGCrefSetCur; // current regs holding GCrefs
    regMaskTP gcRegByrefSetCur; // current regs holding Byrefs

    VARSET_TP gcTrkStkPtrLcls; // set of tracked stack ptr lcls (GCref and Byref) - no args
    VARSET_TP gcVarPtrSetCur;  // currently live part of "gcTrkStkPtrLcls"

    //-------------------------------------------------------------------------
    //
    //  The following keeps track of the lifetimes of non-register variables that
    //  hold pointers.
    //

    struct varPtrDsc
    {
        varPtrDsc* vpdNext;

        unsigned vpdVarNum; // which variable is this about?

        unsigned vpdBegOfs; // the offset where life starts
        unsigned vpdEndOfs; // the offset where life starts
    };

    varPtrDsc* gcVarPtrList;
    varPtrDsc* gcVarPtrLast;

    void gcVarPtrSetInit();

    /*****************************************************************************/

    //  'pointer value' register tracking and argument pushes/pops tracking.

    enum rpdArgType_t
    {
        rpdARG_POP,
        rpdARG_PUSH,
        rpdARG_KILL
    };

    struct regPtrDsc
    {
        regPtrDsc* rpdNext; // next entry in the list
        unsigned   rpdOffs; // the offset of the instruction

        union // 2-16 byte union (depending on architecture)
        {
            struct // 2-16 byte structure (depending on architecture)
            {
                regMaskSmall rpdAdd; // regptr bitset being added
                regMaskSmall rpdDel; // regptr bitset being removed
            } rpdCompiler;

            unsigned short rpdPtrArg; // arg offset or popped arg count
        };

#ifndef JIT32_GCENCODER
        unsigned char rpdCallInstrSize; // Length of the call instruction.
#endif

        unsigned short rpdArg : 1;     // is this an argument descriptor?
        unsigned short rpdArgType : 2; // is this an argument push,pop, or kill?
        rpdArgType_t   rpdArgTypeGet()
        {
            return (rpdArgType_t)rpdArgType;
        }

        unsigned short rpdGCtype : 2; // is this a pointer, after all?
        GCtype         rpdGCtypeGet()
        {
            return (GCtype)rpdGCtype;
        }

        unsigned short rpdIsThis : 1;                       // is it the 'this' pointer
        unsigned short rpdCall : 1;                         // is this a true call site?
        unsigned short : 1;                                 // Padding bit, so next two start on a byte boundary
        unsigned short rpdCallGCrefRegs : CNT_CALLEE_SAVED; // Callee-saved registers containing GC pointers.
        unsigned short rpdCallByrefRegs : CNT_CALLEE_SAVED; // Callee-saved registers containing byrefs.

#ifndef JIT32_GCENCODER
        bool rpdIsCallInstr()
        {
            return rpdCall && rpdCallInstrSize != 0;
        }
#endif
    };

    regPtrDsc* gcRegPtrList;
    regPtrDsc* gcRegPtrLast;
    unsigned   gcPtrArgCnt;

#ifndef JIT32_GCENCODER
    enum MakeRegPtrMode
    {
        MAKE_REG_PTR_MODE_ASSIGN_SLOTS,
        MAKE_REG_PTR_MODE_DO_WORK
    };

    // This method has two modes.  In the "assign slots" mode, it figures out what stack locations are
    // used to contain GC references, and whether those locations contain byrefs or pinning references,
    // building up mappings from tuples of <offset X byref/pinning> to the corresponding slot id.
    // In the "do work" mode, we use these slot ids to actually declare live ranges to the encoder.
    void gcMakeVarPtrTable(GcInfoEncoder* gcInfoEncoder, MakeRegPtrMode mode);

    // At instruction offset "instrOffset," the set of registers indicated by "regMask" is becoming live or dead,
    // depending on whether "newState" is "GC_SLOT_DEAD" or "GC_SLOT_LIVE".  The subset of registers whose corresponding
    // bits are set in "byRefMask" contain by-refs rather than regular GC pointers. "*pPtrRegs" is the set of
    // registers currently known to contain pointers.  If "mode" is "ASSIGN_SLOTS", computes and records slot
    // ids for the registers.  If "mode" is "DO_WORK", informs "gcInfoEncoder" about the state transition,
    // using the previously assigned slot ids, and updates "*pPtrRegs" appropriately.
    void gcInfoRecordGCRegStateChange(GcInfoEncoder* gcInfoEncoder,
                                      MakeRegPtrMode mode,
                                      unsigned       instrOffset,
                                      regMaskSmall   regMask,
                                      GcSlotState    newState,
                                      regMaskSmall   byRefMask,
                                      regMaskSmall*  pPtrRegs);

    // regPtrDsc is also used to encode writes to the outgoing argument space (as if they were pushes)
    void gcInfoRecordGCStackArgLive(GcInfoEncoder* gcInfoEncoder, MakeRegPtrMode mode, regPtrDsc* genStackPtr);

    // Walk all the pushes between genStackPtrFirst (inclusive) and genStackPtrLast (exclusive)
    // and mark them as going dead at instrOffset
    void gcInfoRecordGCStackArgsDead(GcInfoEncoder* gcInfoEncoder,
                                     unsigned       instrOffset,
                                     regPtrDsc*     genStackPtrFirst,
                                     regPtrDsc*     genStackPtrLast);

#endif

#if MEASURE_PTRTAB_SIZE
    static size_t s_gcRegPtrDscSize;
    static size_t s_gcTotalPtrTabSize;
#endif

    regPtrDsc* gcRegPtrAllocDsc();

    /*****************************************************************************/

    //-------------------------------------------------------------------------
    //
    //  If we're not generating fully interruptible code, we create a simple
    //  linked list of call descriptors.
    //

    struct CallDsc
    {
        CallDsc* cdNext;
        void*    cdBlock; // the code block of the call
        unsigned cdOffs;  // the offset     of the call
#ifndef JIT32_GCENCODER
        unsigned short cdCallInstrSize; // the size       of the call instruction.
#endif

        unsigned short cdArgCnt;

        union {
            struct // used if cdArgCnt == 0
            {
                unsigned cdArgMask;      // ptr arg bitfield
                unsigned cdByrefArgMask; // byref qualifier for cdArgMask
            } u1;

            unsigned* cdArgTable; // used if cdArgCnt != 0
        };

        regMaskSmall cdGCrefRegs;
        regMaskSmall cdByrefRegs;
    };

    CallDsc* gcCallDescList;
    CallDsc* gcCallDescLast;

    //-------------------------------------------------------------------------

    void gcCountForHeader(UNALIGNED unsigned int* untrackedCount, UNALIGNED unsigned int* varPtrTableSize);

#ifdef JIT32_GCENCODER
    size_t gcMakeRegPtrTable(BYTE* dest, int mask, const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset);
#else
    RegSlotMap*   m_regSlotMap;
    StackSlotMap* m_stackSlotMap;
    // This method has two modes.  In the "assign slots" mode, it figures out what registers and stack
    // locations are used to contain GC references, and whether those locations contain byrefs or pinning
    // references, building up mappings from tuples of <reg/offset X byref/pinning> to the corresponding
    // slot id (in the two member fields declared above).  In the "do work" mode, we use these slot ids to
    // actually declare live ranges to the encoder.
    void gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder,
                           unsigned       codeSize,
                           unsigned       prologSize,
                           MakeRegPtrMode mode,
                           unsigned*      callCntRef);
#endif

#ifdef JIT32_GCENCODER
    size_t gcPtrTableSize(const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset);
    BYTE* gcPtrTableSave(BYTE* destPtr, const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset);
#endif
    void gcRegPtrSetInit();
    /*****************************************************************************/

    // This enumeration yields the result of the analysis below, whether a store
    // requires a write barrier:
    enum WriteBarrierForm
    {
        WBF_NoBarrier,                     // No barrier is required
        WBF_BarrierUnknown,                // A barrier is required, no information on checked/unchecked.
        WBF_BarrierChecked,                // A checked barrier is required.
        WBF_BarrierUnchecked,              // An unchecked barrier is required.
        WBF_NoBarrier_CheckNotHeapInDebug, // We believe that no barrier is required because the
                                           // target is not in the heap -- but in debug build use a
                                           // barrier call that verifies this property.  (Because the
                                           // target not being in the heap relies on a convention that
                                           // might accidentally be violated in the future.)
    };

    WriteBarrierForm gcIsWriteBarrierCandidate(GenTree* tgt, GenTree* assignVal);
    bool gcIsWriteBarrierStoreIndNode(GenTree* op);

    // Returns a WriteBarrierForm decision based on the form of "tgtAddr", which is assumed to be the
    // argument of a GT_IND LHS.
    WriteBarrierForm gcWriteBarrierFormFromTargetAddress(GenTree* tgtAddr);

    //-------------------------------------------------------------------------
    //
    //  These record the info about the procedure in the info-block
    //
    CLANG_FORMAT_COMMENT_ANCHOR;

#ifdef JIT32_GCENCODER
private:
    BYTE* gcEpilogTable;

    unsigned gcEpilogPrevOffset;

    size_t gcInfoBlockHdrSave(BYTE*    dest,
                              int      mask,
                              unsigned methodSize,
                              unsigned prologSize,
                              unsigned epilogSize,
                              InfoHdr* header,
                              int*     s_cached);

public:
    static void gcInitEncoderLookupTable();

private:
    static size_t gcRecordEpilog(void* pCallBackData, unsigned offset);
#else // JIT32_GCENCODER
    void gcInfoBlockHdrSave(GcInfoEncoder* gcInfoEncoder, unsigned methodSize, unsigned prologSize);

#endif // JIT32_GCENCODER

#if !defined(JIT32_GCENCODER) || defined(WIN64EXCEPTIONS)

    // This method expands the tracked stack variables lifetimes so that any lifetimes within filters
    // are reported as pinned.
    void gcMarkFilterVarsPinned();

    // Insert a varPtrDsc to gcVarPtrList that was generated by splitting lifetimes
    void gcInsertVarPtrDscSplit(varPtrDsc* desc, varPtrDsc* begin);

#ifdef DEBUG
    void gcDumpVarPtrDsc(varPtrDsc* desc);
#endif // DEBUG

#endif // !defined(JIT32_GCENCODER) || defined(WIN64EXCEPTIONS)

#if DUMP_GC_TABLES

    void gcFindPtrsInFrame(const void* infoBlock, const void* codeBlock, unsigned offs);

#ifdef JIT32_GCENCODER
    unsigned gcInfoBlockHdrDump(const BYTE* table,
                                InfoHdr*    header,      /* OUT */
                                unsigned*   methodSize); /* OUT */

    unsigned gcDumpPtrTable(const BYTE* table, const InfoHdr& header, unsigned methodSize);

#endif // JIT32_GCENCODER
#endif // DUMP_GC_TABLES

public:
    // This method updates the appropriate reg masks when a variable is moved.
    void gcUpdateForRegVarMove(regMaskTP srcMask, regMaskTP dstMask, LclVarDsc* varDsc);

private:
    ReturnKind getReturnKind();
};

inline unsigned char encodeUnsigned(BYTE* dest, unsigned value)
{
    unsigned char size = 1;
    unsigned      tmp  = value;
    while (tmp > 0x7F)
    {
        tmp >>= 7;
        assert(size < 6); // Invariant.
        size++;
    }
    if (dest)
    {
        // write the bytes starting at the end of dest in LSB to MSB order
        BYTE* p    = dest + size;
        BYTE  cont = 0; // The last byte has no continuation flag
        while (value > 0x7F)
        {
            *--p = cont | (value & 0x7f);
            value >>= 7;
            cont = 0x80; // Non last bytes have a continuation flag
        }
        *--p = cont | (BYTE)value; // Now write the first byte
        assert(p == dest);
    }
    return size;
}

inline unsigned char encodeUDelta(BYTE* dest, unsigned value, unsigned lastValue)
{
    assert(value >= lastValue);
    return encodeUnsigned(dest, value - lastValue);
}

inline unsigned char encodeSigned(BYTE* dest, int val)
{
    unsigned char size  = 1;
    unsigned      value = val;
    BYTE          neg   = 0;
    if (val < 0)
    {
        value = -val;
        neg   = 0x40;
    }
    unsigned tmp = value;
    while (tmp > 0x3F)
    {
        tmp >>= 7;
        assert(size < 16); // Definitely sufficient for unsigned.  Fits in an unsigned char, certainly.
        size++;
    }
    if (dest)
    {
        // write the bytes starting at the end of dest in LSB to MSB order
        BYTE* p    = dest + size;
        BYTE  cont = 0; // The last byte has no continuation flag
        while (value > 0x3F)
        {
            *--p = cont | (value & 0x7f);
            value >>= 7;
            cont = 0x80; // Non last bytes have a continuation flag
        }
        *--p = neg | cont | (BYTE)value; // Now write the first byte
        assert(p == dest);
    }
    return size;
}

#endif // _JITGCINFO_H_