summaryrefslogtreecommitdiff
path: root/src/vm/gcheaputilities.cpp
blob: fda07ce675f29c91b6e88087454d56d3c2b6c174 (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
// 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.

#include "common.h"
#include "gcheaputilities.h"
#include "gcenv.ee.h"
#include "appdomain.hpp"


// These globals are variables used within the GC and maintained
// by the EE for use in write barriers. It is the responsibility
// of the GC to communicate updates to these globals to the EE through
// GCToEEInterface::StompWriteBarrierResize and GCToEEInterface::StompWriteBarrierEphemeral.
GPTR_IMPL_INIT(uint32_t, g_card_table,      nullptr);
GPTR_IMPL_INIT(uint8_t,  g_lowest_address,  nullptr);
GPTR_IMPL_INIT(uint8_t,  g_highest_address, nullptr);
GVAL_IMPL_INIT(GCHeapType, g_heap_type,     GC_HEAP_INVALID);
uint8_t* g_ephemeral_low  = (uint8_t*)1;
uint8_t* g_ephemeral_high = (uint8_t*)~0;

#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES
uint32_t* g_card_bundle_table = nullptr;
#endif

// This is the global GC heap, maintained by the VM.
GPTR_IMPL(IGCHeap, g_pGCHeap);

GcDacVars g_gc_dac_vars;
GPTR_IMPL(GcDacVars, g_gcDacGlobals);

#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP

uint8_t* g_sw_ww_table = nullptr;
bool g_sw_ww_enabled_for_gc_heap = false;

#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP

gc_alloc_context g_global_alloc_context = {};

enum GC_LOAD_STATUS {
    GC_LOAD_STATUS_BEFORE_START,
    GC_LOAD_STATUS_START,
    GC_LOAD_STATUS_DONE_LOAD,
    GC_LOAD_STATUS_GET_VERSIONINFO,
    GC_LOAD_STATUS_CALL_VERSIONINFO,
    GC_LOAD_STATUS_DONE_VERSION_CHECK,
    GC_LOAD_STATUS_GET_INITIALIZE,
    GC_LOAD_STATUS_LOAD_COMPLETE
};

// Load status of the GC. If GC loading fails, the value of this
// global indicates where the failure occured.
GC_LOAD_STATUS g_gc_load_status = GC_LOAD_STATUS_BEFORE_START;

// The version of the GC that we have loaded.
VersionInfo g_gc_version_info;

// The module that contains the GC.
HMODULE g_gc_module;

// GC entrypoints for the the linked-in GC. These symbols are invoked
// directly if we are not using a standalone GC.
extern "C" void GC_VersionInfo(/* Out */ VersionInfo* info);
extern "C" HRESULT GC_Initialize(
    /* In  */ IGCToCLR* clrToGC,
    /* Out */ IGCHeap** gcHeap,
    /* Out */ IGCHandleManager** gcHandleManager,
    /* Out */ GcDacVars* gcDacVars
);

#ifndef DACCESS_COMPILE

HMODULE GCHeapUtilities::GetGCModule()
{
    assert(g_gc_module);
    return g_gc_module;
}

namespace
{

// This block of code contains all of the state necessary to handle incoming
// EtwCallbacks before the GC has been initialized. This is a tricky problem
// because EtwCallbacks can appear at any time, even when we are just about
// finished initializing the GC.
//
// The below lock is taken by the "main" thread (the thread in EEStartup) and
// the "ETW" thread, the one calling EtwCallback. EtwCallback may or may not
// be called on the main thread.
DangerousNonHostedSpinLock g_eventStashLock;

GCEventLevel g_stashedLevel = GCEventLevel_None;
GCEventKeyword g_stashedKeyword = GCEventKeyword_None;
GCEventLevel g_stashedPrivateLevel = GCEventLevel_None;
GCEventKeyword g_stashedPrivateKeyword = GCEventKeyword_None;

BOOL g_gcEventTracingInitialized = FALSE;

// FinalizeLoad is called by the main thread to complete initialization of the GC.
// At this point, the GC has provided us with an IGCHeap instance and we are preparing
// to "publish" it by assigning it to g_pGCHeap.
//
// This function can proceed concurrently with StashKeywordAndLevel below.
void FinalizeLoad(IGCHeap* gcHeap, IGCHandleManager* handleMgr, HMODULE gcModule)
{
    g_pGCHeap = gcHeap;

    {
        DangerousNonHostedSpinLockHolder lockHolder(&g_eventStashLock);

        // Ultimately, g_eventStashLock ensures that no two threads call ControlEvents at any
        // point in time.
        g_pGCHeap->ControlEvents(g_stashedKeyword, g_stashedLevel);
        g_pGCHeap->ControlPrivateEvents(g_stashedPrivateKeyword, g_stashedPrivateLevel);
        g_gcEventTracingInitialized = TRUE;
    }

    g_pGCHandleManager = handleMgr;
    g_gcDacGlobals = &g_gc_dac_vars;
    g_gc_load_status = GC_LOAD_STATUS_LOAD_COMPLETE;
    g_gc_module = gcModule;
    LOG((LF_GC, LL_INFO100, "GC load successful\n"));
}

void StashKeywordAndLevel(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level)
{
    DangerousNonHostedSpinLockHolder lockHolder(&g_eventStashLock);
    if (!g_gcEventTracingInitialized)
    {
        if (isPublicProvider)
        {
            g_stashedKeyword = keywords;
            g_stashedLevel = level;
        }
        else
        {
            g_stashedPrivateKeyword = keywords;
            g_stashedPrivateLevel = level;
        }
    }
    else
    {
        if (isPublicProvider)
        {
            g_pGCHeap->ControlEvents(keywords, level);
        }
        else
        {
            g_pGCHeap->ControlPrivateEvents(keywords, level);
        }
    }
}

// Loads and initializes a standalone GC, given the path to the GC
// that we should load. Returns S_OK on success and the failed HRESULT
// on failure.
//
// See Documentation/design-docs/standalone-gc-loading.md for details
// on the loading protocol in use here.
HRESULT LoadAndInitializeGC(LPWSTR standaloneGcLocation)
{
    LIMITED_METHOD_CONTRACT;

#ifndef FEATURE_STANDALONE_GC
    LOG((LF_GC, LL_FATALERROR, "EE not built with the ability to load standalone GCs"));
    return E_FAIL;
#else
    LOG((LF_GC, LL_INFO100, "Loading standalone GC from path %S\n", standaloneGcLocation));
    HMODULE hMod = CLRLoadLibrary(standaloneGcLocation);
    if (!hMod)
    {
        HRESULT err = GetLastError();
        LOG((LF_GC, LL_FATALERROR, "Load of %S failed\n", standaloneGcLocation));
        return err;
    }

    // a standalone GC dispatches virtually on GCToEEInterface, so we must instantiate
    // a class for the GC to use.
    IGCToCLR* gcToClr = new (nothrow) standalone::GCToEEInterface();
    if (!gcToClr)
    {
        return E_OUTOFMEMORY;
    }

    g_gc_load_status = GC_LOAD_STATUS_DONE_LOAD;
    GC_VersionInfoFunction versionInfo = (GC_VersionInfoFunction)GetProcAddress(hMod, "GC_VersionInfo");
    if (!versionInfo)
    {
        HRESULT err = GetLastError();
        LOG((LF_GC, LL_FATALERROR, "Load of `GC_VersionInfo` from standalone GC failed\n"));
        return err;
    }

    g_gc_load_status = GC_LOAD_STATUS_GET_VERSIONINFO;
    versionInfo(&g_gc_version_info);
    g_gc_load_status = GC_LOAD_STATUS_CALL_VERSIONINFO;

    if (g_gc_version_info.MajorVersion != GC_INTERFACE_MAJOR_VERSION)
    {
        LOG((LF_GC, LL_FATALERROR, "Loaded GC has incompatible major version number (expected %d, got %d)\n",
            GC_INTERFACE_MAJOR_VERSION, g_gc_version_info.MajorVersion));
        return E_FAIL;
    }

    if (g_gc_version_info.MinorVersion < GC_INTERFACE_MINOR_VERSION)
    {
        LOG((LF_GC, LL_INFO100, "Loaded GC has lower minor version number (%d) than EE was compiled against (%d)\n",
            g_gc_version_info.MinorVersion, GC_INTERFACE_MINOR_VERSION));
    }

    LOG((LF_GC, LL_INFO100, "Loaded GC identifying itself with name `%s`\n", g_gc_version_info.Name));
    g_gc_load_status = GC_LOAD_STATUS_DONE_VERSION_CHECK;
    GC_InitializeFunction initFunc = (GC_InitializeFunction)GetProcAddress(hMod, "GC_Initialize");
    if (!initFunc)
    {
        HRESULT err = GetLastError();
        LOG((LF_GC, LL_FATALERROR, "Load of `GC_Initialize` from standalone GC failed\n"));
        return err;
    }

    g_gc_load_status = GC_LOAD_STATUS_GET_INITIALIZE;
    IGCHeap* heap;
    IGCHandleManager* manager;
    HRESULT initResult = initFunc(gcToClr, &heap, &manager, &g_gc_dac_vars);
    if (initResult == S_OK)
    {
        FinalizeLoad(heap, manager, hMod);
    }
    else
    {
        LOG((LF_GC, LL_FATALERROR, "GC initialization failed with HR = 0x%X\n", initResult));
    }

    return initResult;
#endif // FEATURE_STANDALONE_GC
}

// Initializes a non-standalone GC. The protocol for initializing a non-standalone GC
// is similar to loading a standalone one, except that the GC_VersionInfo and
// GC_Initialize symbols are linked to directory and thus don't need to be loaded.
//
// The major and minor versions are still checked in debug builds - it must be the case
// that the GC and EE agree on a shared version number because they are built from
// the same sources.
HRESULT InitializeDefaultGC()
{
    LIMITED_METHOD_CONTRACT;

    LOG((LF_GC, LL_INFO100, "Standalone GC location not provided, using provided GC\n"));

    g_gc_load_status = GC_LOAD_STATUS_DONE_LOAD;
    GC_VersionInfo(&g_gc_version_info);
    g_gc_load_status = GC_LOAD_STATUS_CALL_VERSIONINFO;

    // the default GC builds with the rest of the EE. By definition, it must have been
    // built with the same interface version.
    assert(g_gc_version_info.MajorVersion == GC_INTERFACE_MAJOR_VERSION);
    assert(g_gc_version_info.MinorVersion == GC_INTERFACE_MINOR_VERSION);
    g_gc_load_status = GC_LOAD_STATUS_DONE_VERSION_CHECK;

    IGCHeap* heap;
    IGCHandleManager* manager;
    HRESULT initResult = GC_Initialize(nullptr, &heap, &manager, &g_gc_dac_vars);
    if (initResult == S_OK)
    {
        FinalizeLoad(heap, manager, GetModuleInst());
    }
    else
    {
        LOG((LF_GC, LL_FATALERROR, "GC initialization failed with HR = 0x%X\n", initResult));
    }


    return initResult;
}

} // anonymous namespace

// Loads (if necessary) and initializes the GC. If using a standalone GC,
// it loads the library containing it and dynamically loads the GC entry point.
// If using a non-standalone GC, it invokes the GC entry point directly.
HRESULT GCHeapUtilities::LoadAndInitialize()
{
    LIMITED_METHOD_CONTRACT;

    // we should only call this once on startup. Attempting to load a GC
    // twice is an error.
    assert(g_pGCHeap == nullptr);

    // we should not have attempted to load a GC already. Attempting a
    // load after the first load already failed is an error.
    assert(g_gc_load_status == GC_LOAD_STATUS_BEFORE_START);
    g_gc_load_status = GC_LOAD_STATUS_START;

    LPWSTR standaloneGcLocation = nullptr;
    CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_GCName, &standaloneGcLocation);
    if (!standaloneGcLocation)
    {
        return InitializeDefaultGC();
    }
    else
    {
        return LoadAndInitializeGC(standaloneGcLocation);
    }
}

void GCHeapUtilities::RecordEventStateChange(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level)
{
    CONTRACTL {
      MODE_ANY;
      NOTHROW;
      GC_NOTRIGGER;
      CAN_TAKE_LOCK;
    } CONTRACTL_END;

    StashKeywordAndLevel(isPublicProvider, keywords, level);
}

#endif // DACCESS_COMPILE