Conversation
39d84f2 to
081c062
Compare
|
What's wrong with the continuous-integration/jenkins/pr-merge? |
It's unrelated - it's a regression introduced in dlang/phobos#5427 |
MoritzMaxeiner
left a comment
There was a problem hiding this comment.
pureFreeneeds to save&restoreerrno- modified unittests should be marked
@system
src/core/memory.d
Outdated
|
|
||
| /// | ||
| nothrow @nogc unittest | ||
| pure nothrow @nogc unittest |
There was a problem hiding this comment.
If you modify this unittest, shouldn't it also be marked as @system ?
There was a problem hiding this comment.
Why should it be marked as @system?
src/core/memory.d
Outdated
| } | ||
|
|
||
| pure @nogc nothrow unittest | ||
| pure nothrow @nogc unittest |
There was a problem hiding this comment.
Same here w.r.t adding @system
There was a problem hiding this comment.
GH won't let me comment on unchanged lines, so I'll put add it here at the last changed line:
If you expose fakePureFree via pureFree, the needed by unittests comment at the fakePureFree declaration should be removed.
There was a problem hiding this comment.
Removed the comment at fakePureFree.
src/core/memory.d
Outdated
| * Pure variants of C's memory allocation functions `malloc`, `calloc`, and | ||
| * `realloc` and deallocation function `free`. | ||
| * | ||
| * Purity for allocation is achieved via resetting the `errno` to it's value |
There was a problem hiding this comment.
Since deallocation must also save&restore errno (see comment at pureFree):
Purity for allocation -> Purity for allocation and deallocation
There was a problem hiding this comment.
s/Purity for allocation is achieved via resetting the errno to it's value/Purity for allocation is achieved by saving and restoring the value of errno, thus behaving as if it were never changed/
| return ret; | ||
| } | ||
| /// ditto | ||
| void pureFree(void* ptr) @system pure @nogc nothrow |
There was a problem hiding this comment.
free is not guaranteed not to set errno by current standards; libc implementations may choose to set errno for both valid and invalid pointers.
POSIX.1 Issue 8 (still WIP), is going to forbid free to set errno for valid pointers, but explicitly allows setting it for an invalid pointers.
AFAIK there's no way for us to know whether the used libc will be linked to statically or dynamically and in the latter case there's no way AFAIK to know if the libc does or does not set errno.
It follows that pureFree must save&restore the errno value the same way that the allocation functions do:
const errno = fakePureGetErrno();
fakePureFree(ptr);
if (errno != 0) fakePureSetErrno(errno);There was a problem hiding this comment.
Yah, let's set and restore it for now regardless. We may change that later as an optimization. Thanks!
|
I thought we all agreed that |
This may be true for your libc, but it's not guaranteed by ISO C or POSIX. The WIP POSIX.1 Issue 8 even explicitly mentions that implementations are free to set |
FWIW it doesn't matter for the |
Linking to the previous threaD: #1746 |
That's a bug then. Can you please make a bugzilla issue? |
|
I have read this discussion and the thread @wilzbach linked to, but I saw (so far) no convincing argument as to why |
|
@Calrama Check out this discussion #1718 Ping @schveiguy |
See e.g. ilogb, but for the most part I was confusing it as |
|
@JackStouffer Thank you. AFAICT the described issue lies with people casting away |
|
Sorry, temporary insanity caused by looking at the FAQ, which still states that. |
|
@Calrama could you please post an issue against https://dlang.org/const-faq.html#invariant? Better yet, a PR fixing it. Thanks! |
andralex
left a comment
There was a problem hiding this comment.
Let's get this moving. Thanks!
| return ret; | ||
| } | ||
| /// ditto | ||
| void pureFree(void* ptr) @system pure @nogc nothrow |
There was a problem hiding this comment.
Yah, let's set and restore it for now regardless. We may change that later as an optimization. Thanks!
src/core/memory.d
Outdated
| * Pure variants of C's memory allocation functions `malloc`, `calloc`, and | ||
| * `realloc` and deallocation function `free`. | ||
| * | ||
| * Purity for allocation is achieved via resetting the `errno` to it's value |
There was a problem hiding this comment.
s/Purity for allocation is achieved via resetting the errno to it's value/Purity for allocation is achieved by saving and restoring the value of errno, thus behaving as if it were never changed/
|
cc @nordlow |
|
@andralex Sure, PR is open |
|
The issue with What is going to happen is that someone will need to free immutable or const data. They will cast away the immutable and call The only saving grace of this is that we are not actually marking the real I'm not going to continually have this argument (please see the previous PR), it seems enough people want to try this doomed experiment, so go ahead. |
|
Why should the compiler elide successive calls to a (weakly) pure function taking mutable references? Pardon my short memory, if this question has already been answered somewhere, @schveiguy @andralex I'll update this PR in a couple of hours. |
Steve, I'm hoping for a different tack on this. Yes, it is clear there is a problem. The problem stands in the way of people doing things that are useful. So we need to find a solution to the problem. Stating that the problem is not solvable and "can't do that" has low informational entropy - a bunch of folks will confirm they can't solve it, starting with the mailman ringing the door right now. What is interesting and good and what we should aim for is the high information response - a creative solution that solves the problem. What are your thoughts on that? |
|
I didn't say the problem wasn't solvable, I said that solving it this way will likely lead to subtle hard-to-detect leaks. My recommendation (as before) is to let people tackle this using private declarations of Note that this all really depends on the type, not the call. If the type is not fully immutable/const, then the compiler cannot elide We can't make such guarantees if we simply provide an easy way to do it. So we let the user make the guarantees on their own. It's like lock-free programming. There's lots of wrong ways to do it, and in most cases the wrong ways still work most of the time. It's those times it doesn't work where you are in big trouble. |
It's not the call to And it's not successive calls either, it would be all the calls. A pure function that takes no mutable references and returns nothing can simply be elided by the compiler 100% of the time. |
I see the problem, but there is one remark I have: The spec states that
From that I would assume that casting away |
If there is no other reference, and you are about to forget the last reference (i.e. you are freeing it), then how is it illegal? For all intents and purposes, that piece of memory that you no longer have access to may very well still have the value assigned to it. In fact, free may not even change the value, but just store it away for safe keeping! We are already accepting that malloc/free isn't modifying global data, even though it really is. We have to have some level of practicality that you can free immutable data as long as you no longer reference it. Note that freeing immutable objects already cast away immutable (the destructor is not marked immutable). It would be a good idea to update the spec for this. |
Because the spec - as it is currently worded - explicitly forbids the freeing of immutable objects. Once constructed they must remain alive for the remainder of the program's execution, irrespective of whether you are still using them.
If we want it to be legal for immutable objects to be freed, yes. |
Then the GC is in violation of the spec. Again, we have to put aside these kinds of requirements when talking about allocators. This is like the old question, if a tree falls in a forest but nobody is around, does it make a sound? Similarly, if nothing can access a piece of immutable memory, is it illegal to modify it? |
Then either the GC or the spec needs to be fixed with whatever we end up deciding to be the way to go forward. In case of the GC being fixed, it would have to store an additional list of memory locations where immutable objects were constructed and only collect them when the D runtime shuts down.
In my opinion yes, because
|
Sorry I thought in this context we were only talking about allocated RAM, not ROM. |
For one thing, you can have write protected RAM, using e.g. |
|
Thanks for your pull request, @nordlow! We are looking forward to reviewing it, and you should be hearing from a maintainer soon. Some tips to help speed things up:
Bear in mind that large or tricky changes may require multiple rounds of review and revision. Please see CONTRIBUTING.md for more information. 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. |
|
Updates made according to comments. |
|
It seems to me that given the issues with call elision that Steven brought up, |
|
@jmdavis a documentation PR would be very welcome - thanks! |
|
General point: pure functions cannot be elided. Total functions can be elided; pure functions are not necessarily total, ie. they do not necessarily return. For instance, this is a pure function that will change the program's behavior if elided: If a pure function is called more than once, all but the first call can be elided. |
|
It all depends on how D defines it. A pure function that never returns isn't an interesting or desirable behavior. We could simply say that pure functions that don't ever return are undefined behavior, and let the compiler assume it must return at some point, allowing the obvious optimization. We could also dictate that it must be called the first time, but this means we will waste cycles calling functions that easily could be elided. Either way, it's not really a fundamental property of purity in general that the first call must be done. After all, pure functions model mathematics, and I don't know if there's a math book that describes functions that never return ;) |
What about inferred void foo()() /* a template now */
{
while (true) {}
}The compiler will infer |
I'd be fine with the compiler assuming that pure functions always return. I'm not convinced that it's detrimental for a function that never returns to be elided if it does nothing observable inside. I'm trying to see the benefit of having an opaque function like |
|
I want to emphasize that Phobos has had actual production bugs due to confusing pure and total functions. |
|
Great, it should be easy to show then! Can you point me at the bug? The bug report I saw you posted on the forums doesn't have anything to do with strong pure functions. |
|
Ah, you're right. What happened was that the compiler removed any call returning a struct with an enum property to be accessed, which would be wrong regardless, and I had to correct the pull request to avoid the issue with pure as I went. So at no point did the compiler actually have this bug. In my defense, I'll note that the incorrect code was recommended to me multiple times, explicitly stating it was permissible due to pure, so it was a close thing. So I'd restate things as this: that we'd almost had this bug in dmd master, and it would have led to the precise issue of a function that was effectively |
|
OK, so if I can be a bit pedantic, what I think you are saying is that something like I'm not sure if I got it right, but I'm still not sure where a strong-pure function makes sense to run just because it might contain an infinite loop (which IMO is clearly a programming error). |
|
That's exactly what happens, since |
|
OK, I see what you are saying (I think):
Since all the inputs and outputs are immutable or enum, this is tagged as a strong pure function. The logic in the compiler could deduce that it must ALWAYS return that range, which indicates the value ( I see the flaw assuming that there is no infinite loop in any strong-pure function, and definitely this cannot be allowed to happen. Thanks for giving the clear example. I still am concerned with applying the same restriction to void-returning functions. In this case, the control flow of the program (assuming it returns) is not affected based on anything the function does. One cannot assume incorrectly anything based on the return value. It truly is a "do nothing" call. It's a different thing to assume the return value based on the type than it is to assume it returns at all. In one case, the two are linked, due to the immutable property of the type, but in the other case, the return value doesn't exist, so it's not relevant. Literally the ONLY thing a strong-pure void-returning function can do is either use a finite amount of time to execute, or never return. I'd prefer simply never calling a void strong-pure function to calling it only the first time. This allows generic programming that doesn't need to be hand-optimized based on some admittedly strange criteria. |
|
It does seem like a void-returning strong pure function should not fulfill any purpose. This does feel like the setup for a really subtle bug somewhere though. |
To be used in contexts such as
RefCounted-destructor instead of having to define local overloads such as here https://github.com/dlang/phobos/pull/4832/files#diff-4e008aedb3026d4a84f58323e53bf017R5087.AFAICT,
freedoesn't seterrno.