-
-
Notifications
You must be signed in to change notification settings - Fork 421
Conversation
Thanks for your pull request, @JinShil! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub fetch digger
dub run digger -- build "master + druntime#2264" |
src/object.d
Outdated
TTo[] __ArrayCast(TFrom, TTo)(TFrom[] from) @trusted | ||
{ | ||
auto nbytes = from.length * TFrom.sizeof; | ||
assert((nbytes % TTo.sizeof) == 0, "array cast misalignment"); |
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 probably should be a proper error. Because if someone compiles in -release
, this check is gone.
Also the error message should be more descriptive.
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 can't make this an Error
, or it won't work in @nogc
code. See this comment for a more detailed explanation.
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 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.
Because if someone compiles in -release, this check is gone.
That's a good point that I hadn't considered. Note that if the assert
is removed, this code can no longer be considered @trusted
.
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.
@wilzbach said:
Catching Error is defined to result in undefined behavior. We don't need to cater for such code. So you can use assert?
In an e-mail to me, @WalterBright said:
I agree with Seb. It should have been implemented that way the first time around.
So, now that I've been directed to staticError
(Thanks @Geod24) I can throw Error
s in @nogc
code, but given @WalterBright's response, I'm not sure what I should do.
src/object.d
Outdated
*/ | ||
TTo[] __ArrayCast(TFrom, TTo)(TFrom[] from) @trusted | ||
{ | ||
auto nbytes = from.length * TFrom.sizeof; |
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.
const nbytes = ...
src/object.d
Outdated
return *cast(TTo[]*)a; | ||
} | ||
|
||
unittest |
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.
@safe @nogc pure nothrow
src/object.d
Outdated
assert(s.length == 6); | ||
|
||
s = __ArrayCast!(int, short)(i); | ||
assert(s.length == 6); |
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'd also do an array comparison, or at the very least, a first-element comparison in addition to this length check
src/object.d
Outdated
{ | ||
const auto nbytes = from.length * TFrom.sizeof; | ||
|
||
if ((nbytes % TTo.sizeof) != 0) |
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 would additionally nest this in:
version (D_NoBoundsChecks) {} // Oh, the irony
else
{
if ((nbytes...))
...
}
but that might be up for discussion
src/object.d
Outdated
*/ | ||
TTo[] __ArrayCast(TFrom, TTo)(TFrom[] from) @trusted | ||
{ | ||
const auto nbytes = from.length * TFrom.sizeof; |
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.
A storage class is enough, so auto
is redundant
src/object.d
Outdated
|
||
i = __ArrayCast!(byte, int)(b); | ||
assert(i.length == 3); | ||
foreach(v; i) |
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.
Here and later instances are missing a space: foreach (v; i)
src/object.d
Outdated
version(D_Exceptions) | ||
{ | ||
import core.exception : onArrayMisalignmentError; | ||
onArrayMisalignmentError(); |
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 think we should pass more informations to this function.
I'd like to see a message similar to this:
Error: Cannot cast `int[5]` to `ulong[]`, as size `20` is not aligned on `8` bytes.
It does not need to allocate. If you pass the elements separately, you can then provide a toString
overload which accepts a sink.
Let's not repeat the mistake that is array bounds mismatch's error message.
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.
Is your suggestion to have the Error
's msg
say "Array cast misalignment", and then have the overloaded toString
function return the more elaborate error message?
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 forgot people might be using msg
directly... Although for Error
it's unlikely. I think your suggestion makes sense.
src/object.d
Outdated
|
||
if ((nbytes % TTo.sizeof) != 0) | ||
{ | ||
version(D_Exceptions) |
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.
Also missing space: version (
src/core/exception.d
Outdated
*/ | ||
class ArrayMisalignmentError : Error | ||
{ | ||
@safe pure nothrow @nogc this(string file = __FILE__, size_t line = __LINE__, Throwable next = 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.
attributes should go on the RHS. Also, trailing space between null
and closing )
src/core/exception.d
Outdated
{ | ||
@safe pure nothrow @nogc this(string file = __FILE__, size_t line = __LINE__, Throwable next = null ) | ||
{ | ||
super( "Array misalignment", file, line, next ); |
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.
Extra space: super(args);
src/core/exception.d
Outdated
import core.internal.string : unsignedToTempString; | ||
import core.stdc.stdlib : alloca; | ||
|
||
const(char)[][8] msgComponents = |
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.
How about this for formulating a more detailed error message. I'm trying to avoid using the C standard library (alloca
actually isn't part of the C standard library).
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 trying to avoid using the C standard library
Why ?
Also, I would suggest to just store those values as field and do the formatting in toString
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 ?
Because eventually I'd like to jettison it completely from druntime. I also need to consider using the same code for the assert
message in __ArrayCast
.
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 need to come up with a utility for creating a dynamic string on the stack.
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 ended up merging the message generation code into the __ArrayCast
implementation itself so I could use it for the assert
as well.
src/core/exception.d
Outdated
* Throws: | ||
* $(LREF Error). | ||
*/ | ||
void throwStaticError(string msg, string file = __FILE__, size_t line = __LINE__) @trusted pure nothrow @nogc |
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 just throws a generic Error
. I don't know what the policy is on strongly typed exceptions.
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.
What's the reason for this function?
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.
See the body of the function: throw staticError!Error(msg, file, line, null);
. That template instantiates a static Error
so that it can be used in @nogc
code. However, it is private
. I created the throwStaticError
function to keep the staticError
template private and still allow throwing static Error
s.
Edit: The patterns currently employed in core.exception
for throwing static Error
s don't make much sense to me. The Error
s are all strongly typed and are encapsulated behind a extern(C) void on{StronglyTypedError}()
function. I don't know why this was done; I certainly would have done it differently. It seemed so simple to just create this throwStaticError
function rather than create a new strongly typed error and another extern(C) void on{StronglyTypedError}()
wrapper.
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 able to throw an error in @nogc
.
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 created the throwStaticError function to keep the staticError template private and still allow throwing static
What's the advantage of this wrapper function compared to using staticError
directly? I don't see how introducing a new public symbol to keep an existing symbol private is an advantage. You can make the existing staticError
package
to keep it internal to druntime.
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 can't make it package
because that would only make it visible to modules within core
.
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.
Hmm, right. This is in the object
module which is not inside any package. But again, is this any better than making the existing staticError
public
?
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.
No, it's not any better IMO. If we decide to use static Error
s, I think I'll just follow the existing pattern for other static Error
s in core.exception
. That is, create a strongly typed Error
and throw it with an on{whatever}
function.
src/object.d
Outdated
} | ||
else | ||
{ | ||
assert(false, cast(string)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.
I don't know how assert(false)
behaves but assert(0)
will halt the program when assertions are turned off.
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.
assert(false
will do the same because it's the same: https://run.dlang.io/is/AVSUFK
@JinShil we typically write assert(0, ...
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.
we typically write assert(0, ...
IMO we should stop doing that. It's a condition, not a number.
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.
@jacob-carlborg I tested this out and you're right. If the first argument is 0
or false
it still runs when asserts are disabled. However, I can find no mention of that behavior in the spec. If this is by design, it'd great if you could document it, and then I could potentially remove throwing the static Error
altogether.
Kindly asking for advise from all reviewers on how to proceed.
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.
Found this in the spec.
The implementation may handle the case of the first AssignExpression evaluating at compile time to false differently in that in release mode it may simply generate a HLT instruction or equivalent.
I guess I'l just remove the static Error
and rely on assert(false)
for all implementations, then. It should keep the functions @trusted
even in release mode.
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 guess I'l just remove the static
Error
and rely onassert(false)
for all implementations
Please don't. At the very least the behavior should be the same as array bounds check.
Additionally, outright crashing the program is not something that other parts of the runtime do (and there's many arguments for that). The common usage it to throw
an Error
.
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 don't.
I'd be happy to revert back to throwing a static Error
and assert
only for betterC-like builds, but I need consensus from reviewers or decree from @WalterBright
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 know there's been some disagreement about the behavior of assert(0/false)
because not all CPUs have the HLT
instruction, but perhaps that covered with the "or equivalent" in the spec.
src/object.d
Outdated
import core.exception : throwStaticError; | ||
throwStaticError(cast(string)msg); | ||
} | ||
else |
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 looks like D is moving in C++ land here:
Divergent error handling has fractured the C++ community into incompatible dialects:
(1) C++ projects often ban even turning on compiler support for exception handling, but this means they are not using Standard C++. Exceptions are required to use central C++ standard language features (e.g., constructors) and the C++ standard library. Yet in [SC++F 2018], over half of C++ developers report that exceptions are banned in part (32%) or all (20%) of their code, which means they are using a divergent language dialect with different idioms (e.g., two-phase construction) and either a nonconforming standard library dialect or none at all. We must make it possible for all C++ projects to at least turn on exception handling support and use the standard language and library.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf
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.
Indeed. That's what happens when a language is not designed from the beginning with freestanding, resource-constrained systems and scalability in mind. The emergence of things like @nogc
, betterC, etc. shows that D is backtracking, trying to undo some mistakes of the past. GC should be an opt-in library feature and exceptions should not be rooted in Object
or Java-style class
-like types. But that's what we have. If we want D to scale to systems other than resource-abundant PCs and servers running full-featured operating systems, then we have to offer alternatives. I hope future language decisions will keep principles of minimalism in mind. Given what's possible in D, I'd prefer if classes were just deprecated, and structs were give a few more features.
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'd prefer if classes were just deprecated, and structs were give a few more features.
Why? There are still people out there that still uses classes.
src/object.d
Outdated
* Returns: | ||
* `from` reinterpreted as `TTo[]` | ||
*/ | ||
TTo[] __ArrayCast(TFrom, TTo)(TFrom[] from) @trusted |
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 also wondering about code coventions here: Should it be (TFrom, TTo)
or (TTo, TFrom)
. I took wisdom from std.algorithm.mutation.copy, but I'm not sure if any wisdom was used in deciding that convention. This will come into play in future PRs I have planned, also.
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.
Perhaps use the same order as the cast
expression.
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.
The only rule I know of is to put arguments that needs to be explicitly specified first, otherwise follow the order or arguments. Additionally, in very rare cases, you might have 2 templates argument depending on each others (e.g. via a constraint) in which case the order is self-evident.
In this case, this is never called directly by the user so that order doesn't matter much.
Personally, I find TFrom, TTo
(as currently specified) to be the clearer alternative.
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.
Shouldn't array casting be also pure
and @nogc
?
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.
Ah, the style policy on template attributes has changed. Good!
dlang/dlang.org@f95cb98#diff-2ceb1eaea6947a0a0ad321e635cec2e0
- $(LI $(I Templated) functions should $(B not) be annotated with attributes
- as the compiler can infer them.)
+ $(LI If the template arguments for a $(I templated) function affect whether
+ an attribute is appropriate, then the function should $(B not) be
+ annotated with that attribute so that the compiler can infer it.
+ However, if the attribute is not affected by the template arguments
+ (and thus would always be inferred), then the function should be
+ explicitly annotated with that attribute just like a non-templated
+ function would be.)
src/object.d
Outdated
const errorMessage = generateArrayCastErrorMessage(TFrom.stringof, fromSize, TTo.stringof, toLength * TTo.sizeof); | ||
|
||
// must be `false` or `0` to ensure the assertion remains | ||
// even when compiling with `-release` |
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 comment is a bit misleading to future readers as the assert(0)
expression gets replaced by a HLT instruction in -release
...)
onArrayCastError(TFrom.stringof, fromSize, TTo.stringof, toLength * TTo.sizeof); | ||
} | ||
|
||
struct Array |
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.
Does this need static
?
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 there is no functions, it does not
const fromSize = from.length * TFrom.sizeof; | ||
const toLength = fromSize / TTo.sizeof; | ||
|
||
if ((fromSize % TTo.sizeof) != 0) |
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.
Making sure that @Geod24's suggestion in #2264 (comment) isn't lost:
version (D_NoBoundsChecks) {}
else if ((fromSize % TTo.sizeof) != 0)
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 waiting to find out what happens with dlang/dmd#8532. I don't think it should hold up this PR, and would even be better if it were done in a followup PR.
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.
Fair.
src/object.d
Outdated
* toSize = total size in bytes of the array being cast to | ||
* Returns: | ||
* a descriptive error message describing the cast failure | ||
*/ |
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.
FWIW for the documentation headers, we try the following:
...
- Documentation comments should not use more than two stars /** or two pluses /++ in the header line.
... - Documentation comments should not have leading stars on each line.
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 looks like the rest of object.d
doesn't follow this convention. Is it more important to keep a consistent style within a document?
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.
@n8sh, you are right, and I doubt anyone would disagree with you, but @WalterBright has placed restrictions on large-scale changes to the code base, forcing us to resort to incremental changes to achieve consistent coding conventions. So, unfortunately, this is how we're going to get there.
src/object.d
Outdated
i = __ArrayCast!(byte, int)(b); | ||
assert(i.length == 3); | ||
foreach (v; i) | ||
assert(v == 0); |
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.
Maybe use something more interesting as values than zeros?
src/object.d
Outdated
* Returns: | ||
* `from` reinterpreted as `TTo[]` | ||
*/ | ||
TTo[] __ArrayCast(TFrom, TTo)(TFrom[] from) @trusted |
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.
Shouldn't array casting be also pure
and @nogc
?
src/object.d
Outdated
* Returns: | ||
* `from` reinterpreted as `TTo[]` | ||
*/ | ||
TTo[] __ArrayCast(TFrom, TTo)(TFrom[] from) @trusted |
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.
Can inout
be used here to reduce template bloat?
To speed things along I've edited the commit to incorporate minor changes requested by @wilzbach: explicit |
Sorry for the late change, but I realized I had to make |
src/object.d
Outdated
|
||
// convert discontiguous `msgComponents` to contiguous string on the stack | ||
size_t length = 0; | ||
foreach(m ; msgComponents) |
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.
foreach (
src/core/internal/string.d
Outdated
char[] unsignedToTempString(ulong value, return scope char[] buf, uint radix = 10) @safe | ||
/** | ||
Converts an unsigned integer value to a string of characters. This | ||
implemenation is a template so it can be used when compiling with -betterC |
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.
Regarding DDOC:
- The first line is a "title", so it should usually be a one liner/sentence
- I think the preferred style is to have 1 blank line between sections (e.g. before
Params:
/Returns:
) implemenation
Would pragma(inline, true)
help in this case ?
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.
Would pragma(inline, true) help in this case ?
Yeah, maybe. The source code would be available in -betterC builds. GDC doesn't currently implement pragma(inline, true)
, though I think it's coming. 🤔 Yeah, let's go with that for now. I can always change it to a template later if it doesn't work out.
EDIT: No, I can't do it. It only works when compiling with -inline
.
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.
The first line is a "title", so it should usually be a one liner/sentence
Yes everything up to the first blank line is considered the title and therefore should be short. After the blank line the description follows which can be long.
I think the preferred style is to have 1 blank line between sections (e.g. before Params: / Returns:)
I prefer that.
This is all dead code (well, except for the unittest). The idea is to verify this implementation first, merge it, then merge dlang/dmd#8531 (assuming it passes all tests).