Skip to content

std.typecons: Make RefCounted pure nothrow @nogc when possible#4832

Merged
andralex merged 1 commit intodlang:masterfrom
nordlow:pure-refcounted
Feb 1, 2017
Merged

std.typecons: Make RefCounted pure nothrow @nogc when possible#4832
andralex merged 1 commit intodlang:masterfrom
nordlow:pure-refcounted

Conversation

@nordlow
Copy link
Contributor

@nordlow nordlow commented Oct 2, 2016

This makes RefCounted!T pure/nothrow/@nogc when possible.

@nordlow nordlow changed the title Make RefCounted pure when possible std.typecons: Make RefCounted pure when possible Oct 2, 2016
std/typecons.d Outdated
// qualified C memory allocations
private @safe pure nothrow @nogc:
void* malloc(size_t size);
void free(void* ptr);
Copy link
Member

Choose a reason for hiding this comment

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

free is not and cannot be safe

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. But its usage here can be @trusted.

Copy link
Member

Choose a reason for hiding this comment

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

I see, you made it private too. Hmmm...

@andralex
Copy link
Member

andralex commented Oct 3, 2016

OK how about you keep the safe but for now drop the pure. The notion of marking malloc as pure is disquieting - it never returns the same void* for the same number. Then we'll see how we can do about pure. Thanks!

@jmdavis
Copy link
Member

jmdavis commented Oct 3, 2016

The notion of marking malloc as pure is disquieting - it never returns the same void* for the same number.

I would point out that we're in the same boat with the GC and new. It's pure and doesn't necessarily return the same value - just an equivalent one. The only real difference is that with the GC, we have something to manage freeing that memory, whereas since you have to worry about it with malloc, you have to be concerned about whether you really do get the same value so that you don't free it twice.

So, I don't know what the right answer is, but there's definitely an argument that malloc should be considered pure given what we do with new.

@andralex
Copy link
Member

andralex commented Oct 3, 2016

@jmdavis new is special so we can special case it

@nordlow
Copy link
Contributor Author

nordlow commented Oct 3, 2016

AFAICT, C-style memory allocations cannot be qualified correctly in D using pure. For instance if we declare malloc as pure the optimizer will optimize away successive calls to malloc which is incorrect. On the other hand, the return value of malloc shall not in the RefCounted case have any side-effect on the program. Only the values that are stored in it should. This semantics cannot currently be expressed in D.

Instead we have to qualify the code that it's using C-style allocation logic and privately import *alloc and free locally for each use. I believe the same holds for GC.addRange.

@aG0aep6G
Copy link
Contributor

aG0aep6G commented Oct 3, 2016

@andralex:

The notion of marking malloc as pure is disquieting - it never returns the same void* for the same number.

That's not a requirement of D's pure attribute. Or if it is, we have to fix the compiler to enforce it and probably break lots of code.

new is special so we can special case it

GC.malloc and GC.free aren't really special, though. They can be used just like C's malloc and free. Yet GC.malloc and GC.free are pure and the C functions are not. I don't see the reason for the disparity.

GC.malloc being (weakly) pure is ok because void* has a mutable indirection, so the compiler must not reuse the result (but: issue 15862). GC.free being (weakly) pure is ok because the visible effect it has is on its mutable argument. The same points apply to C's malloc and free.

See also http://forum.dlang.org/post/hrwnhprgcjlbypaecpzg@forum.dlang.org.

@nordlow:

if we declare malloc as pure the optimizer will optimize away successive calls to malloc

Actually, the compiler must not do that. It does right now, but that's issue 15862.

@andralex
Copy link
Member

andralex commented Oct 3, 2016

@aG0aep6G I think it's a slippery slope that GC.malloc and free are marked as pure. I don't like that, and I don't like that used as a precedent.

There must be some acknowledgment at a point that our type system has limitations that doesn't allow it to express certain valid programs.

@aG0aep6G
Copy link
Contributor

aG0aep6G commented Oct 3, 2016

@aG0aep6G I think it's a slippery slope that GC.malloc and free are marked as pure. I don't like that, and I don't like that used as a precedent.

This needs to be sorted out. Either it's wrong that GC.malloc and GC.free are marked pure, or we should be able to mark C's malloc and free too. Either we have a disaster waiting to happen with the GC functions, or we're needlessly preventing code from being pure.

There must be some acknowledgment at a point that our type system has limitations that doesn't allow it to express certain valid programs.

Ok, but I don't think it's a good idea to draw that line between GC.malloc and C's malloc.

@andralex
Copy link
Member

andralex commented Oct 3, 2016

Hmmm, this notion of a function that returns data with mutable indirection is very interesting. In particular, if the data returned has mutable indirections leading to values that cannot be present in the parameters, that means that's "fresh" data, i.e. newly allocated. Consider:

pure int[] fun(float[]);

Without even looking at the definition, the compiler can infer that the array of integers is freshly-allocated for the simple reason there's no other place it could come from :o). Being able to characterize "fresh" without making language addition can be a big win.

For now, I agree that fixing https://issues.dlang.org/show_bug.cgi?id=15862 would allow us to make malloc pure.

@aG0aep6G
Copy link
Contributor

aG0aep6G commented Oct 3, 2016

In particular, if the data returned has mutable indirections leading to values that cannot be present in the parameters, that means that's "fresh" data, i.e. newly allocated.

Yeah, dmd uses this to allow implicit conversion to immutable when the value comes from a pure function (and other requirements are met). This example compiles with pure and is correctly rejected without it:

int* f() pure { return new int; }
void main() { immutable p = f(); }

Consider:

pure int[] fun(float[]);

Without even looking at the definition, the compiler can infer that the array of integers is freshly-allocated for the simple reason there's no other place it could come from :o).

I'm not so sure about that example. fun is allowed to cast the argument to int[], isn't it? Then again, dmd seems to disregard that possibility, too. This is currently accepted:

pure int[] fun(float[] arg) { return cast(int[]) arg; }
void main() { float[] a = [1, 2, 3]; immutable i = fun(a); }

If dmd is correct here, that would mean casting from one array type to another is invalid (aka has undefined behavior), wouldn't it? That doesn't seem right. I'd say this is another bug in dmd, and it cannot be assumed that fun returns a unique result. Filed as issue 16585.

@nordlow
Copy link
Contributor Author

nordlow commented Oct 5, 2016

@andralex I thought you had decided upon non-safety in #4692 ...

@codecov-io
Copy link

Current coverage is 89.27% (diff: 75.00%)

Merging #4832 into master will increase coverage by <.01%

@@             master      #4832   diff @@
==========================================
  Files           121        121          
  Lines         76114      76116     +2   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits          67950      67956     +6   
+ Misses         8164       8160     -4   
  Partials          0          0          

Powered by Codecov. Last update 08c587e...1055e95

@andralex
Copy link
Member

@andralex I thought you had decided upon non-safety in #4692 ...

@nordlow not sure I understand?

@wilzbach
Copy link
Contributor

For now, I agree that fixing https://issues.dlang.org/show_bug.cgi?id=15862 would allow us to make malloc pure.

The bug has been fixed ;-)

dlang/dmd#6197

So what's still blocking this?

@nordlow
Copy link
Contributor Author

nordlow commented Dec 21, 2016

The RefCounted destructor calls free. It too has to be marked as pure. I can look into making it pure aswell and then go forward with this.

@schveiguy
Copy link
Member

schveiguy commented Dec 23, 2016

I think instead of marking free pure to make this PR successful, we can work around the purity.

Make a private binding to free, something like:

pragma(mangle, "free") extern(C) private purefree(void *mem) pure;

Then you can call it in one place where we can control the context, and not expose the whole world.

EDIT:

Well, now that I look at your PR, that's pretty much exactly what you are doing! Why would this require making the actual binding in core.stdc pure?

@nordlow
Copy link
Contributor Author

nordlow commented Jan 6, 2017

@schveiguy, It doesn't. I just thought making free in core.stdc pure was the direction @andralex wanted. Andrei?

Further, we need to do the same for gc_addRange and gc_removeRange. Is that ok too, @andralex?

@andralex
Copy link
Member

andralex commented Jan 7, 2017

Let's go with the private declaration for now. Yes, in the future we do want to make functions like free pure.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 7, 2017

  1. Ok, does that include gc_addRange and gc_removeRange?

  2. And should I use the @schveiguy version with custom mangling:

pragma(mangle, "free") extern(C) private purefree(void *mem) pure;

or my existing?

@andralex
Copy link
Member

andralex commented Jan 7, 2017

  1. yes those should be fine too (or at least subject to the same subtleties)
  2. @schveiguy 's seems a bit clearer

@nordlow
Copy link
Contributor Author

nordlow commented Jan 10, 2017

Made it so, @andralex

Copy link
Member

@andralex andralex left a comment

Choose a reason for hiding this comment

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

All good, please fix coverage.

std/typecons.d Outdated
onOutOfMemoryError();
static if (hasIndirections!T)
GC.addRange(&_store._payload, T.sizeof);
pure_gc_addRange(&_store._payload, T.sizeof);
Copy link
Member

Choose a reason for hiding this comment

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

line not covered

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

std/typecons.d Outdated
{
extern(C) private pure nothrow @nogc static // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed
{
pragma(mangle, "free") void pure_free( void *ptr );
Copy link
Member

Choose a reason for hiding this comment

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

unusual naming convention (pureFree would be preferred)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok to leave it as this?

std/typecons.d Outdated
extern(C) private pure nothrow @nogc static // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed
{
pragma(mangle, "free") void pure_free( void *ptr );
pragma(mangle, "gc_addRange") void pure_gc_addRange( in void* p, size_t sz, const TypeInfo ti = null );
Copy link
Member

Choose a reason for hiding this comment

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

Eh, apparently not that unusual :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's why I used underscore instead. Ok to leave it as this? Or should I use pureGCAddRange?

Copy link
Member

Choose a reason for hiding this comment

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

Kill the TODO, that bug has been fixed, and you still need pure anyway.

Copy link
Contributor Author

@nordlow nordlow left a comment

Choose a reason for hiding this comment

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

Feedback on local namings.

std/typecons.d Outdated
{
extern(C) private pure nothrow @nogc static // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed
{
pragma(mangle, "free") void pure_free( void *ptr );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok to leave it as this?

std/typecons.d Outdated
extern(C) private pure nothrow @nogc static // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed
{
pragma(mangle, "free") void pure_free( void *ptr );
pragma(mangle, "gc_addRange") void pure_gc_addRange( in void* p, size_t sz, const TypeInfo ti = null );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's why I used underscore instead. Ok to leave it as this? Or should I use pureGCAddRange?

@andralex
Copy link
Member

Don't worry about the name, let's get the coverage. Thx!

@nordlow nordlow changed the title std.typecons: Make RefCounted pure when possible std.typecons: Make RefCounted pure nothrow @nogc when possible Jan 10, 2017
{
struct S { int* p; }

auto s = RefCounted!S(null);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes line 5003 covered.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 12, 2017

Next up is Mallocator. Right, @andralex?

@schveiguy
Copy link
Member

I think this looks good. Let's wait and see whether dlang/druntime#1735 is merged or not. If so, then you need to have a pure_malloc as well.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 14, 2017

If dlang/druntime#1735 gets merged we can use pureMalloc instead, right?

Update: purity was reverted in dlang/druntime#1735

Should we merge this or use pureMalloc instead? If so, where should we put it (along wit pureCalloc and pureRelloc)?

@andralex
Copy link
Member

@nordlow I guess this is next!

@nordlow
Copy link
Contributor Author

nordlow commented Jan 30, 2017

Made it use pureMalloc.

What about local purity of free, gc_addRange and gc_removeRange? Shall we keep the locally/privately purefied versions of them here as is, @andralex?

@andralex
Copy link
Member

local is fine for now

@nordlow
Copy link
Contributor Author

nordlow commented Jan 31, 2017

Changed naming of local wrappers to D-style casing.

This is ready, @andralex !

Acked-by: Per Nordlöw <per.nordlow@gmail.com>
@andralex
Copy link
Member

andralex commented Feb 1, 2017

@nordlow you have style failures, see ci/circleci

@JackStouffer
Copy link
Contributor

@andralex That's actually a dscanner error. It isn't working correctly with the new DIP1000 syntax.

@nordlow
Copy link
Contributor Author

nordlow commented Feb 1, 2017

So what to do until DIP-1000 works, @andralex ?

@andralex
Copy link
Member

andralex commented Feb 1, 2017

Auto-merge toggled on

@andralex andralex merged commit 27a1553 into dlang:master Feb 1, 2017
@nordlow
Copy link
Contributor Author

nordlow commented Feb 1, 2017

Great!

@nordlow nordlow deleted the pure-refcounted branch October 19, 2021 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants