-
-
Notifications
You must be signed in to change notification settings - Fork 417
Conversation
if( _assertHandler is null ) | ||
throw new AssertError( file, line ); | ||
_assertHandler( file, line, null); | ||
onAssertErrorMsg(file, line, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should really be done on the compiler level one day
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mathias-lang-sociomantic yes, and it will
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@WalterBright : Having two function seems unnecessary, as the case of assert(cond)
could just be treated as assert(cond, null)
. That's one less function between DMD and druntime, so less complication.
Also it's a common source of frustration that when debugging we need to break both on _d_assert
and _d_assert_msg
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yah, that's what was happening anyway (with duplicated code)
@@ -421,6 +432,22 @@ deprecated void setAssertHandler( AssertHandler h ) @trusted nothrow @nogc | |||
// Overridable Callbacks | |||
/////////////////////////////////////////////////////////////////////////////// | |||
|
|||
private C singleton(C)() if (is(C == class) && is(typeof(new C))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation and unittests please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unittests are there... documentation not needed for private functions :)
Added support for destruction via |
,,, @mathias-lang-sociomantic and dox :) |
assert(typeid(C).initializer.length == b.length * size_t.sizeof); | ||
import core.stdc.string; | ||
memcpy(b.ptr, typeid(C).init.ptr, b.length); | ||
result.__ctor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't that mean that the ctor
can be @system
and do bad stuff ? ™️
Ditto for the dtor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right brb
When I read that, I immediately wondered about the potential race condition if multiple threads were to
Is it correct to refer to an object as a "singleton" when there's actually one per thread? It's very confusing, etymologically speaking. |
@tsbockman good point thx, changed |
@@ -416,12 +427,48 @@ deprecated void setAssertHandler( AssertHandler h ) @trusted nothrow @nogc | |||
assertHandler = h; | |||
} | |||
|
|||
/* | |||
Returns a perThreadInstance object of class type `C`, i.e. repeated calls to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the standard Ddoc function documentation style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point the function is private so I just added a casual comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added nicer dox
}(); | ||
result.__ctor; | ||
import core.internal.traits; | ||
static if (hasElaborateDestructor!C) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to a quick test:
import std.traits, core.exception;
static assert(hasElaborateDestructor!AssertError);
// Error: static assert (hasElaborateDestructor!(AssertError)) is false
So it would be nice to have this line covered. Otherwise, LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I think we can defer coverage of that to the time we get to use this private function elsewhere.
@mathias-lang-sociomantic added coverage |
I think other Error should be singleton too. |
}(); | ||
result.__ctor; | ||
import core.internal.traits; | ||
static if (hasElaborateDestructor!C) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasElaborateDestructor!C == false
if C is not a struct
. That's why I proposed dlang/phobos#4119.
static size_t[1 + (__traits(classInstanceSize, C) - 1) / size_t.sizeof] b; | ||
static bool initialized = false; | ||
auto result = () @trusted { return cast(C) b.ptr; }(); | ||
if (!initialized) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not do an early exit:
if (initialized)
return result;
Is it just because you don't want to type return result;
twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad you're giving the same advice I often do :). I'd say when the if is short things are fine either way.
|
||
Throws: If the default constructor of `C` throws. | ||
*/ | ||
private C perThreadInstance(C)() if (is(C == class) && is(typeof(new C))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There already is a staticError
which covers pretty much the same use case. Perhaps you can reuse it / replace it / unify the implementations?
{ | ||
static size_t[1 + (__traits(classInstanceSize, C) - 1) / size_t.sizeof] b; | ||
static bool initialized = false; | ||
auto result = () @trusted { return cast(C) b.ptr; }(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're druntime, I think it's worth going the extra mile (i.e. do the necessary safe-ty hacks - see line 760) in order to return immutable C
or tag the function with pure
. @nogc
, pure
and nothrow
are orthogonal in the language and therefore I think it's a good idea to allow @nogc pure
functions to throw staticError
s. (To me personally, throw staicError!RangeError
sound better, than throw perThreadInstance!RangeError
, but naming is not that important at this stage.)
static if (is(typeof(result.__ctor()))) | ||
result.__ctor; | ||
import core.internal.traits; | ||
static if (hasElaborateDestructor!C) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasElaborateDestructor!C
always returns false if C
is not a struct
(even the one in druntime). That's why I proposed dlang/phobos#4119.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm... I think just remove this static if. There is always a destructor for a class (i.e. rt_finalize2 will always call destructors, even for Object).
import core.stdc.stdlib; | ||
static void destroy() | ||
{ | ||
(cast(C) b.ptr).__dtor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't want to leverage object.destroy
, you should at least use rt_finalize2
, because that's what object.destroy
does for classes.
The problem with calling obj.__dtor
directly is that:
- It does a non-virtual call (see 1 and 2) to
C.~this()
, without calling any ofC
's base classes' dtors. - It does not destroy
C
's__monitor
field. - It does not clear the memory, causing the GC to attempt destroy (this time "properly") the object again - see this (reproduces perfectly on newer compilers too).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, this was actually a problem with destroy before we fixed it.
*/ | ||
private C perThreadInstance(C)() if (is(C == class) && is(typeof(new C))) | ||
{ | ||
static size_t[1 + (__traits(classInstanceSize, C) - 1) / size_t.sizeof] b; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would have been simpler to do ubyte[__traits(classInstanceSize, C] b
. Why do you want to round up to a multiple of size_t
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alignment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be sufficiently paranoid, better add a check for the instance alignment not being larger than size_t then (SIMD vectors, …).
import core.stdc.string; | ||
() @trusted | ||
{ | ||
memcpy(b.ptr, typeid(C).initializer.ptr, b.length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory the following could be more efficient auto ci = typeid(C); b[0 .. ci.initializer.length] = ci.initializer[]
(assuming b
is of type ubyte[]
), because the compiler knows at compile time typeid(C).initializer.length
and can make a better decision about the codegen (e.g. potentially inline the memcpy for smaller types).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this code will be executed once per run really
memcpy(b.ptr, typeid(C).initializer.ptr, b.length); | ||
}(); | ||
static if (is(typeof(result.__ctor()))) | ||
result.__ctor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may need to make sure that the BlkAttr
for this piece of memory has the BlkAttr.FINALIZE
bit unset, otherwise the GC may attempt to call the dtors again at the end of the program.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... this is not GC-allocated memory so it does not have block attributes. No?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andralex correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use/adopt staticError, it was build for that purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Just need some tweaks to destruction, then it should be good to go.
memcpy(b.ptr, typeid(C).initializer.ptr, b.length); | ||
}(); | ||
static if (is(typeof(result.__ctor()))) | ||
result.__ctor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andralex correct.
import core.stdc.stdlib; | ||
static void destroy() | ||
{ | ||
(cast(C) b.ptr).__dtor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, this was actually a problem with destroy before we fixed it.
static if (is(typeof(result.__ctor()))) | ||
result.__ctor; | ||
import core.internal.traits; | ||
static if (hasElaborateDestructor!C) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm... I think just remove this static if. There is always a destructor for a class (i.e. rt_finalize2 will always call destructors, even for Object).
Note, this will close https://issues.dlang.org/show_bug.cgi?id=4587 |
Hm... I think actually @andralex, looking at how |
It already does, see #1798. For the buffer to still be scanned with proper alignment, it should be an array of void*. |
I think that there should also be a test showing what happens if an assert triggers during unwinding an AssertError. It doesn't have to support perfect chaining, but it should somehow pass the initial error along. |
superseded |
This makes AssertError a singleton, which allows people to use
assert
in higher-level applications without needing to link in the garbage collector.This could break code that calls
onAssertError
repeatedly and assumes it returns separate objects. That's quite an unusual way to go about things, and it seems we need not guarantee it.If this approach works we can (a) publish
singleton
so as to make it available to clients, and (b) convertRangeError
to a singleton as well so we can throw range errors without worrying about memory allocations.