Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

spinlock for GC #1447

Merged
merged 4 commits into from Mar 8, 2016
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions mak/COPY
Expand Up @@ -18,6 +18,7 @@ COPY=\
$(IMPDIR)\core\internal\abort.d \
$(IMPDIR)\core\internal\convert.d \
$(IMPDIR)\core\internal\hash.d \
$(IMPDIR)\core\internal\spinlock.d \
$(IMPDIR)\core\internal\string.d \
$(IMPDIR)\core\internal\traits.d \
\
Expand Down
1 change: 1 addition & 0 deletions mak/SRCS
Expand Up @@ -19,6 +19,7 @@ SRCS=\
src\core\internal\abort.d \
src\core\internal\convert.d \
src\core\internal\hash.d \
src\core\internal\spinlock.d \
src\core\internal\string.d \
src\core\internal\traits.d \
\
Expand Down
18 changes: 16 additions & 2 deletions src/core/exception.d
Expand Up @@ -227,7 +227,13 @@ class OutOfMemoryError : Error

override string toString() const @trusted
{
return msg.length ? (cast()super).toString() : "Memory allocation failed";
return msg.length ? (cast()this).superToString() : "Memory allocation failed";
}

// kludge to call non-const super.toString
private string superToString() @trusted
{
return super.toString();
}
}

Expand All @@ -239,6 +245,7 @@ unittest
assert(oome.line == __LINE__ - 2);
assert(oome.next is null);
assert(oome.msg == "Memory allocation failed");
assert(oome.toString.length);
}

{
Expand Down Expand Up @@ -269,7 +276,13 @@ class InvalidMemoryOperationError : Error

override string toString() const @trusted
{
return msg.length ? (cast()super).toString() : "Invalid memory operation";
return msg.length ? (cast()this).superToString() : "Invalid memory operation";
}

// kludge to call non-const super.toString
private string superToString() @trusted
{
return super.toString();
}
}

Expand All @@ -281,6 +294,7 @@ unittest
assert(oome.line == __LINE__ - 2);
assert(oome.next is null);
assert(oome.msg == "Invalid memory operation");
assert(oome.toString.length);
}

{
Expand Down
103 changes: 103 additions & 0 deletions src/core/internal/spinlock.d
@@ -0,0 +1,103 @@
/**
* SpinLock for runtime internal usage.
*
* Copyright: Copyright Digital Mars 2015 -.
* License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
* Authors: Martin Nowak
* Source: $(DRUNTIMESRC core/internal/_spinlock.d)
*/
module core.internal.spinlock;

import core.atomic, core.thread;

shared struct SpinLock
{
/// for how long is the lock usually contended
enum Contention : ubyte
{
brief,
medium,
lengthy,
}

@trusted @nogc nothrow:
this(Contention contention)
{
this.contention = contention;
}

void lock()
{
if (cas(&val, size_t(0), size_t(1)))
Copy link
Member

Choose a reason for hiding this comment

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

I think restructuring this function as a do/while so there's only one cas() might generate tighter code. But not urgent.

Copy link
Member Author

Choose a reason for hiding this comment

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

I wanted to avoid accessing the contention field.

return;
// Try to reduce the chance of another cas failure
// TTAS lock (https://en.wikipedia.org/wiki/Test_and_test-and-set)
immutable step = 1 << contention;
while (true)
{
for (size_t n; atomicLoad!(MemoryOrder.raw)(val); n += step)
yield(n);
if (cas(&val, size_t(0), size_t(1)))
return;
}
}

void unlock()
{
atomicStore!(MemoryOrder.rel)(val, size_t(0));
}

/// yield with backoff
void yield(size_t k)
{
if (k < pauseThresh)
return pause();
else if (k < 32)
return Thread.yield();
Thread.sleep(1.msecs);
}

private:
version (D_InlineAsm_X86)
enum X86 = true;
else version (D_InlineAsm_X86_64)
enum X86 = true;
else
enum X86 = false;

static if (X86)
{
enum pauseThresh = 16;
void pause()
{
asm @trusted @nogc nothrow
{
// pause instruction
rep;
nop;
}
}
}
else
{
enum pauseThresh = 4;
void pause()
{
}
}

size_t val;
Contention contention;
}

// aligned to cacheline to avoid false sharing
shared align(64) struct AlignedSpinLock
{
this(SpinLock.Contention contention)
{
impl = shared(SpinLock)(contention);
}

SpinLock impl;
alias impl this;
}
2 changes: 2 additions & 0 deletions src/core/memory.d
Expand Up @@ -145,6 +145,8 @@ private
extern (C) void gc_removeRoot( in void* p ) nothrow;
extern (C) void gc_removeRange( in void* p ) nothrow @nogc;
extern (C) void gc_runFinalizers( in void[] segment );

package extern (C) bool gc_inFinalizer();
}


Expand Down
5 changes: 5 additions & 0 deletions src/core/runtime.d
Expand Up @@ -494,6 +494,11 @@ Throwable.TraceInfo defaultTraceHandler( void* ptr = null )
else version( Solaris )
import core.sys.solaris.execinfo;

// avoid recursive GC calls in finalizer, trace handlers should be made @nogc instead
import core.memory : gc_inFinalizer;
if (gc_inFinalizer)
return null;

//printf("runtime.defaultTraceHandler()\n");
static if( __traits( compiles, backtrace ) )
{
Expand Down
6 changes: 3 additions & 3 deletions src/core/thread.d
Expand Up @@ -1091,7 +1091,7 @@ class Thread
*
* ------------------------------------------------------------------------
*/
static void sleep( Duration val ) nothrow
static void sleep( Duration val ) @nogc nothrow
in
{
assert( !val.isNegative );
Expand Down Expand Up @@ -1134,7 +1134,7 @@ class Thread
if( !nanosleep( &tin, &tout ) )
return;
if( errno != EINTR )
throw new ThreadError( "Unable to sleep for the specified duration" );
assert(0, "Unable to sleep for the specified duration");
tin = tout;
}
}
Expand All @@ -1144,7 +1144,7 @@ class Thread
/**
* Forces a context switch to occur away from the calling thread.
*/
static void yield() nothrow
static void yield() @nogc nothrow
{
version( Windows )
SwitchToThread();
Expand Down