Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/lazy-gc-init.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
the garbage collector is now lazily initialized on first use

The runtime now lazily initializes the GC on first use, thus allowing applications that do not use the GC to skip its initialization.
1 change: 1 addition & 0 deletions mak/SRCS
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ SRCS=\
src\gc\proxy.d \
src\gc\impl\conservative\gc.d \
src\gc\impl\manual\gc.d \
src\gc\impl\proto\gc.d \
\
src\rt\aApply.d \
src\rt\aApplyR.d \
Expand Down
240 changes: 240 additions & 0 deletions src/gc/impl/proto/gc.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@

module gc.impl.proto.gc;

import gc.config;
import gc.gcinterface;

import rt.util.container.array;

import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc;
static import core.memory;

extern (C) void onOutOfMemoryError(void* pretend_sideffect = null) @trusted pure nothrow @nogc; /* dmd @@@BUG11461@@@ */

private
{
extern (C) void gc_init_nothrow() nothrow @nogc;
extern (C) void gc_term();

extern (C) void gc_enable() nothrow;
extern (C) void gc_disable() nothrow;

extern (C) void* gc_malloc( size_t sz, uint ba = 0, const TypeInfo = null ) pure nothrow;
extern (C) void* gc_calloc( size_t sz, uint ba = 0, const TypeInfo = null ) pure nothrow;
extern (C) BlkInfo gc_qalloc( size_t sz, uint ba = 0, const TypeInfo = null ) pure nothrow;
extern (C) void* gc_realloc( void* p, size_t sz, uint ba = 0, const TypeInfo = null ) pure nothrow;
extern (C) size_t gc_reserve( size_t sz ) nothrow;

extern (C) void gc_addRange( void* p, size_t sz, const TypeInfo ti = null ) nothrow @nogc;
extern (C) void gc_addRoot( void* p ) nothrow @nogc;
}

class ProtoGC : GC
{
Array!Root roots;
Array!Range ranges;

// Call this function when initializing the real GC
// upon ProtoGC term. This function should be called
// after the real GC is in place.
void term()
{
// Transfer all ranges
foreach (ref r; ranges)
{
// Range(p, p + sz, cast() ti)
gc_addRange(r.pbot, r.ptop - r.pbot, r.ti);
}

// Transfer all roots
foreach (ref r; roots)
{
gc_addRoot(r.proot);
}
}

this()
{
}

void Dtor()
{
}

void enable()
{
gc_init_nothrow();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better add an internal initGC method which must also replay all addRange/addRoot calls.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MartinNowak please not

gc_enable();
}

void disable()
{
gc_init_nothrow();
gc_disable();
}

void collect() nothrow
{
}

void collectNoStack() nothrow
{
}

void minimize() nothrow
{
}

uint getAttr(void* p) nothrow
{
return 0;
}

uint setAttr(void* p, uint mask) nothrow
{
return 0;
}

uint clrAttr(void* p, uint mask) nothrow
{
return 0;
}

void* malloc(size_t size, uint bits, const TypeInfo ti) nothrow
{
gc_init_nothrow();
return gc_malloc(size, bits, ti);
}

BlkInfo qalloc(size_t size, uint bits, const TypeInfo ti) nothrow
{
gc_init_nothrow();
return gc_qalloc(size, bits, ti);
}

void* calloc(size_t size, uint bits, const TypeInfo ti) nothrow
{
gc_init_nothrow();
return gc_calloc(size, bits, ti);
}

void* realloc(void* p, size_t size, uint bits, const TypeInfo ti) nothrow
{
gc_init_nothrow();
return gc_realloc(p, size, bits, ti);
}

size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow
{
return 0;
}

size_t reserve(size_t size) nothrow
{
gc_init_nothrow();
return reserve(size);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this call?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops! It should have been gc_reserve

}

void free(void* p) nothrow @nogc
{
if (p) assert(false, "Invalid memory deallocation");
}

void* addrOf(void* p) nothrow @nogc
{
return null;
}

size_t sizeOf(void* p) nothrow @nogc
{
return 0;
}

BlkInfo query(void* p) nothrow
{
return BlkInfo.init;
}

core.memory.GC.Stats stats() nothrow
{
return typeof(return).init;
}


void addRoot(void* p) nothrow @nogc
{
roots.insertBack(Root(p));
}

void removeRoot(void* p) nothrow @nogc
{
foreach (ref r; roots)
{
if (r is p)
{
r = roots.back;
roots.popBack();
return;
}
}
assert(false);
}

@property RootIterator rootIter() return @nogc
{
return &rootsApply;
}

private int rootsApply(scope int delegate(ref Root) nothrow dg)
{
foreach (ref r; roots)
{
if (auto result = dg(r))
return result;
}
return 0;
}

void addRange(void* p, size_t sz, const TypeInfo ti = null) nothrow @nogc
{
ranges.insertBack(Range(p, p + sz, cast() ti));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem thread-safe – are we sure there can never be a situation where ProtoGC sticks around long enough to be racy (e.g. after improving druntime thread startup to be nogc and using statically allocated Thread instances, or when attaching to multiple C threads)?

Has this been thoroughly thought through for race conditions in general, especially considering architectures where loading the global instance doesn't automatically have acquire semantics?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point. Likely you need to take the lock. I'm wondering if adding/removing ranges or roots should simply initialize the GC at that point.

I thought the instance should be atomically loaded, see Martin's suggestion here. But that doesn't help if you need atomic array appending.

}

void removeRange(void* p) nothrow @nogc
{
foreach (ref r; ranges)
{
if (r.pbot is p)
{
r = ranges.back;
ranges.popBack();
return;
}
}
assert(false);
}

@property RangeIterator rangeIter() return @nogc
{
return &rangesApply;
}

private int rangesApply(scope int delegate(ref Range) nothrow dg)
{
foreach (ref r; ranges)
{
if (auto result = dg(r))
return result;
}
return 0;
}

void runFinalizers(in void[] segment) nothrow
{
}

bool inFinalizer() nothrow
{
return false;
}
}
67 changes: 49 additions & 18 deletions src/gc/proxy.d
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module gc.proxy;

import gc.impl.conservative.gc;
import gc.impl.manual.gc;
import gc.impl.proto.gc;
import gc.config;
import gc.gcinterface;

Expand All @@ -25,28 +26,55 @@ private
static import core.memory;
alias BlkInfo = core.memory.GC.BlkInfo;

__gshared GC instance;
__gshared GC proxiedGC; // used to iterate roots of Windows DLLs
import core.internal.spinlock;
static SpinLock instanceLock;

__gshared bool isInstanceInit = false;
__gshared GC instance = new ProtoGC();
__gshared GC proxiedGC; // used to iterate roots of Windows DLLs
}


extern (C)
{

void gc_init()
{
config.initialize();
ManualGC.initialize(instance);
ConservativeGC.initialize(instance);
if (instance is null)
instanceLock.lock();
if (!isInstanceInit)
{
import core.stdc.stdio : fprintf, stderr;
import core.stdc.stdlib : exit;
auto protoInstance = instance;
config.initialize();
ManualGC.initialize(instance);
ConservativeGC.initialize(instance);

if (instance is protoInstance)
{
import core.stdc.stdio : fprintf, stderr;
import core.stdc.stdlib : exit;

fprintf(stderr, "No GC was initialized, please recheck the name of the selected GC ('%.*s').\n", cast(int)config.gc.length, config.gc.ptr);
instanceLock.unlock();
exit(1);

// Shouldn't get here.
assert(0);
}

// Transfer all ranges and roots to the real GC.
(cast(ProtoGC) protoInstance).term();
isInstanceInit = true;
}
instanceLock.unlock();
}

fprintf(stderr, "No GC was initialized, please recheck the name of the selected GC ('%.*s').\n", cast(int)config.gc.length, config.gc.ptr);
exit(1);
void gc_init_nothrow() nothrow
{
scope(failure)
{
import core.internal.abort;
abort("Cannot initialize the garbage collector.\n");
assert(0);
}
gc_init();
}

void gc_term()
Expand All @@ -61,11 +89,14 @@ extern (C)
// NOTE: Due to popular demand, this has been re-enabled. It still has
// the problems mentioned above though, so I guess we'll see.

instance.collectNoStack(); // not really a 'collect all' -- still scans
// static data area, roots, and ranges.
if (isInstanceInit)
{
instance.collectNoStack(); // not really a 'collect all' -- still scans
// static data area, roots, and ranges.

ManualGC.finalize(instance);
ConservativeGC.finalize(instance);
ManualGC.finalize(instance);
ConservativeGC.finalize(instance);
}
}

void gc_enable()
Expand Down Expand Up @@ -158,12 +189,12 @@ extern (C)
return instance.stats();
}

void gc_addRoot( void* p ) nothrow
void gc_addRoot( void* p ) nothrow @nogc
{
return instance.addRoot( p );
}

void gc_addRange( void* p, size_t sz, const TypeInfo ti = null ) nothrow
void gc_addRange( void* p, size_t sz, const TypeInfo ti = null ) nothrow @nogc
{
return instance.addRange( p, sz, ti );
}
Expand Down
2 changes: 1 addition & 1 deletion src/rt/dmain2.d
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ extern (C) int rt_init()
// in other druntime systems.
_d_initMonoTime();
thread_init();
gc_init();
// TODO: fixme - calls GC.addRange -> Initializes GC
Copy link
Member

@MartinNowak MartinNowak Feb 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what needs to be fixed, e.g. by storing the ranges in ProtoGC until we lazily initialize the real GC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MartinNowak what use cases is this serving? The win of ProtoGC is to get away without ever initializing the GC. But it's unlikely someone adds a bunch of ranges of interest to the GC but ends up doing no allocation. Also: Where would the array be allocated? Also: this has gotten really long in the teeth, improvements can be discussed later.

initStaticDataGC();
lifetime_init();
rt_moduleCtor();
Expand Down
4 changes: 4 additions & 0 deletions test/exceptions/src/unknown_gc.d
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import core.memory;

extern(C) __gshared string[] rt_options = [ "gcopt=gc:unknowngc" ];

void main()
{
// GC initialized upon first call -> Unknown GC error is thrown
GC.enable();
}
5 changes: 5 additions & 0 deletions test/nogc.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extern(C) __gshared string[] rt_options = [ "gcopt=gc:non-existing" ];

void main() @nogc
{
}