Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Made capacity and reserve pure nothrow. #147

Merged
merged 1 commit into from almost 2 years ago

4 participants

Jonathan M Davis Steven Schveighoffer Alex Rønne Petersen Andrei Alexandrescu
Jonathan M Davis
Collaborator

I don't see any reason for these not to be pure and/or nothrow, and
their lack is problematic for pure functions (and to some extent,
nothrow functions, though try-catch can fix that as tedious as it may
be).

Steven Schveighoffer
Collaborator

I see no reason why reserve and capacity cannot be pure nothrow. Looks good to me. Anyone else? We should probably wait a few days to see if anyone comes up with compelling counter-cases in the NG.

kennytm kennytm referenced this pull request February 17, 2012
Closed

fix issue 6333 #154

Alex Rønne Petersen
Collaborator

This sounds fine to me.

Steven Schveighoffer
Collaborator

I realize, we forgot to merge this. But I also realized, due to reviewing #154 that I think this won't work for shared arrays (which should be supported, even if the function cannot be pure).

Do you want to update your patch, or should we just look at doing #154, which has some other changes?

Jonathan M Davis
Collaborator

If you think that the other pull request is better, then feel free tho close this one.

Jonathan M Davis Made capacity and reserve pure nothrow.
I don't see any reason for these not to be pure and/or nothrow, and
their lack is problematic for pure functions (and to some extent,
nothrow functions, though try-catch can fix that as tedious as it may
be).
74a596e
Jonathan M Davis
Collaborator

What about this doesn't work with shared arrays?

As pull request# 154 has been closed until those more ambitious changes can be properly sorted out, I really think that any issues with this request should be sorted out so that it can be merged, as reserve and capacity have a huge impact on our ability to make Appender and string conversion functions pure.

Steven Schveighoffer
Collaborator

If you call with a shared array (i.e. shared(int)[]), the prototype evaluates to:

size_t capacity(shared(int)[] arr) pure nothrow

Which will fail to compile. Remove pure from the templates, and I think this will be fine (pure will be inferred for types which can handle it).

Jonathan M Davis
Collaborator

It doesn't look like shared compiles with reserve regardless of whether pure is involved (including the current version of druntime), and capacity compiles just fine with both shared and pure. So, it looks like reserve is broken for shared regardless of any changes here.

What does shared have to do with pure anyway? AFAIK, they're orthogonal (though shared would prevent strong purity, since another thread could screw with the value).

Steven Schveighoffer
Collaborator
Jonathan M Davis
Collaborator

I don't know where you got the idea where pure doesn't work with shared. I've never heard that before, and it seems to work just fine. Obviously, a function with a shared argument cannot be strongly pure, but I don't see why it can't be weakly pure. Certainly, if you're right, and shared isn't supposed to work with pure, then the compiler is buggy, since capacity seems to work just fine when called with shared arguments, even when it's explicitly marked pure.

Steven Schveighoffer
Collaborator

The reason weak-pure exists at all is because shared data must be explicitly marked shared. Otherwise, a pure function that depended on shared data would not return the same result for the same input if some other thread happened to modify the shared data. If the compiler allows shared parameters to pure functions, that should be a bug.

Jonathan M Davis
Collaborator

But pure does not guarantee that you get the same results for the same arguments. Only strongly pure guarantees that. All that pure guarantees is that the function does not access mutable global or static variables and that it does not call any functions which are not pure. And yes, the compiler appears to accept shared paramters for pure functions just fine. It's just never going to make them strongly pure, so the fact that another thread may screw with the value doesn 't matter, because the function is never going to be optimized with the assumption that multiple calls got the same result anyway (because it's not strongly pure).

Steven Schveighoffer
Collaborator

What is the point of marking a function pure that accepts shared parameters? it can't be called by a strong-pure function, or if it is, the shared data passed into it will be casted to shared, and not really shared. The very definition of pure functions and shared data are at odds with each other.

And if you define "The arguments" as all the data reachable through the parameters, then yes, a pure function will return the same results (and have the same effect on its parameters) for the same arguments (as long as shared is not present).

for example:

int f(int* i) pure
{
   return ++(*i);
}

void main()
{
  int i;
  int x = f(&i); // parameter is &i *and* value of i
  int y = f(&i); // different set of parameters;
  i = 0;
  int z = f(&i); // same as first f(i) call.  Compiler knows that x == z.
  if(x == z) // can be rewritten as if(true)
  {
     ...
  }
}
Jonathan M Davis
Collaborator

With a pure function, you know that it isn't accessing any global mutable state. This is arguably the greatest benefit of pure - particularly in light of the fact that without strongly pure (which so few functions can be), the whole "same arguments result in same return value" deal doesn't actually buy you any compiler optimizations. A pure function which takes shared still has that guarantee, and if you've properly locked/synchronized that section of code (as well as any other code accessing that shared variable), then you don't even have the problem of the another thread screwing with your variable (though obviously, the compiler can't give you any additional guarantees based no that, since it would have to examine every case that the variable is accessed to verify that it was properly locked, which no compiler is going to do).

If you make a templated function explicitly pure, then you can guarantee that it won't ever access global mutable state, and you'll get compilation errors if you ever try anything of the sort. If you leave it to attribute inference, you risk it not really being pure when it's supposed to be. So, if you know that the function should always be pure, marking it as pure solves that problem. You can't do that if shared doesn't work with pure. If you have to worry about shared, then that complicates things a fair bit, whereas if you just let shared work with pure but never be able to be optimized to strongly pure, then it's a non-issue. The pure templated function works with shared. You have the guarantee that it's not access global mutable state, and you can leave it up to the compiler whether it ever manages to use that purity to optimize anything. But if shared doesn't work with pure, then you could rarely mark templated functions as pure.

Also, consider this:

struct S
{
    int i;
    shared int* g;
}

You can easily have a type like that which isn't shared itself but does contain shared data. What happens when it's passed to a pure function? It's not shared, but if the concern is that shared can't work with pure, then it would have to be disallowed as an argument to a pure function.

And what about

shared(C) func() @safe pure nothrow;

This function doesn't have any shared arguments at all, but it can allocate shared and return it just fine.

I really don't see any reason to make shared not work with pure. shared doesn't violate purity per D's definition (doesn't access mutable global or static state except through it's arguments and does not call any functions which aren't pure). It just can't be optimized much. And banning shared from pure functions would make it much harder to mark stuff as pure without crippling shared that much more.

Steven Schveighoffer
Collaborator

pure is about making multithreading easy. Within a pure function, you are pretty much guaranteed you don't have to worry about locks or another thread altering your data. There is a good reason I want to be able to mark something pure -- so I know I'm safe from multithreaded code. I should be able to dispatch pure functions onto worker threads without worry of synchronization needs. Allowing pure functions to deal with shared immensely dilutes the power of pure functions.

In response to your two examples, the struct should not be able to work with pure. I see no great benefit to allowing it.

The second example, returning shared where no shared data was passed in is simply misrepresentation -- there is no way func could return shared data, because it couldn't have possibly shared the data! A better solution is simply to allow implicit casting to shared (as we allow implicit casting to immutable), and return C instead of shared(C).

Jonathan M Davis
Collaborator

No, the data can't have been shared with another thread yet, but the type is shared. Whether it's actually been shared with another thread is irrelevant to the type system. new shared(C) will give you a new shared object even if you then only ever use it on one thread, and the type system will have to assume that it could be accessed from multiple threads.

I've never heard anyone argue that pure had anything to do with threading before, but as far as I can tell, even if pure accepts shared arguments, it still isn't an issue with multithreading, because you can't actually pass the shared variable to another thread within those functions. The only time that it would be an issue is if you passed data which had already been shared with another thread into a pure function. You'd then have to use locks at or before the call point, but internally to the pure functions, you wouldn't have to care. So, if you want to dispatch pure functions to separate threads, you can do so just fine. The only way that they'd need any kind of synchronization issues is if they were passed shared data which had already been shared, which clearly isn't what you're talking about doing if you don't want shared to work with pure. So, I don't see how allowing shared to be passed to a pure function dilutes anything.

Regardless, the current definition of pure says nothing about shared. It is not disallowed in any way shape or form per the definition, and the compiler allows shared to be used with pure functions. So, I think that if you don't want shared to work with pure, you're going to have to make a case for it to Walter and get him to change it.

Steven Schveighoffer
Collaborator

The original author of the relaxed pure rules stated that shared data must be avoided. See in this post:

http://forum.dlang.org/post/i7d60m$2smf$1@digitalmars.com

The documents were changed by Walter, and I'm not sure Walter fully understood what was intended.

And pure's ability to be safely multi-threaded is one of its main benefits (for strong-pure functions, it can even be done as an optimization).

Steven Schveighoffer
Collaborator

I should say that although I don't see any benefit to weak-pure functions accepting shared data, I don't see it as inhibiting the optimization of pure functions. I just see it as a feature that has no interesting use-case.

weak-pure functions that accept shared data simply should never participate in optimizations (even as called by strong-pure functions), which makes them pretty much useless as pure functions.

Jonathan M Davis
Collaborator

Except that pure functions using shared still have the guarantee that they're not accessing global data, so you do get some benefit from it, even if you don't get any optimizations from it. Also, if a weakly pure function is takes shared data, I don't see how that would prevent optimizations of a strongly pure function which calls it. Because it's called by a strongly pure function, the weakly pure one which takes shared can't actually be accessing any data which is actively shared across threads, so optimizing it poses no problems. As with any weakly pure function, I wouldn't expect the call to weakly pure function itself to be optimized anyway - just the call to the strongly pure one which called it.

However, a big win in letting pure operate with shared from a library perspective is simply the fact that you can mark templated stuff as pure, thereby requiring that it be pure rather than inferring it (which frequently isn't possible - or we wouldn't need attribute inference - but in some cases it definitely is). If shared didn't work with pure, then you'd have to be paranoid about using pure with templated functions or be creating additional overloads for shared.

As far as I can tell, shared doesn't prevent anything with pure other than strong purity (either conceptually or in the implementation), and a large portion of the time, weakly pure functions aren't called by strongly pure functions anyway, and denying those functions the ability to be used with shared is problematic IMHO. We have enough issues with shared not working with much as it is.

But given that shared does currently work with pure, that capacity works with shared and pure, and that reserve doesn't work with shared regardless, I don't see the shared issue as being a real impediment to these changes. As far as I can tell, it doesn't make anything which currently works stop working.

Andrei Alexandrescu
Owner

@jmdavis This seems to fail in the autotester, could you please take a look?

Jonathan M Davis
Collaborator

@andralex It's one of the periodic test fails that we get in druntime (from core.sync.condition). I'm 99.999999% certain that it has nothing to do with this pull request. For instance, the last run of the autotester for this Phobos pull request had the same failure.

Andrei Alexandrescu andralex merged commit 4631b52 into from July 08, 2012
Andrei Alexandrescu andralex closed this July 08, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Jun 02, 2012
Jonathan M Davis Made capacity and reserve pure nothrow.
I don't see any reason for these not to be pure and/or nothrow, and
their lack is problematic for pure functions (and to some extent,
nothrow functions, though try-catch can fix that as tedious as it may
be).
74a596e
This page is out of date. Refresh to see the latest.
6  import/object.di
@@ -589,15 +589,15 @@ template _isStaticArray(T)
589 589
 private
590 590
 {
591 591
     extern (C) void _d_arrayshrinkfit(TypeInfo ti, void[] arr);
592  
-    extern (C) size_t _d_arraysetcapacity(TypeInfo ti, size_t newcapacity, void *arrptr);
  592
+    extern (C) size_t _d_arraysetcapacity(TypeInfo ti, size_t newcapacity, void *arrptr) pure nothrow;
593 593
 }
594 594
 
595  
-@property size_t capacity(T)(T[] arr)
  595
+@property size_t capacity(T)(T[] arr) pure nothrow
596 596
 {
597 597
     return _d_arraysetcapacity(typeid(T[]), 0, cast(void *)&arr);
598 598
 }
599 599
 
600  
-size_t reserve(T)(ref T[] arr, size_t newcapacity)
  600
+size_t reserve(T)(ref T[] arr, size_t newcapacity) pure nothrow
601 601
 {
602 602
     return _d_arraysetcapacity(typeid(T[]), newcapacity, cast(void *)&arr);
603 603
 }
6  src/object_.d
@@ -33,7 +33,7 @@ private
33 33
     extern (C) void onOutOfMemoryError();
34 34
     extern (C) Object _d_newclass(TypeInfo_Class ci);
35 35
     extern (C) void _d_arrayshrinkfit(TypeInfo ti, void[] arr);
36  
-    extern (C) size_t _d_arraysetcapacity(TypeInfo ti, size_t newcapacity, void *arrptr);
  36
+    extern (C) size_t _d_arraysetcapacity(TypeInfo ti, size_t newcapacity, void *arrptr) pure nothrow;
37 37
     extern (C) void rt_finalize(void *data, bool det=true);
38 38
 }
39 39
 
@@ -2409,7 +2409,7 @@ version (unittest)
2409 2409
  * of elements that the array can grow to before the array must be
2410 2410
  * extended/reallocated.
2411 2411
  */
2412  
-@property size_t capacity(T)(T[] arr)
  2412
+@property size_t capacity(T)(T[] arr) pure nothrow
2413 2413
 {
2414 2414
     return _d_arraysetcapacity(typeid(T[]), 0, cast(void *)&arr);
2415 2415
 }
@@ -2422,7 +2422,7 @@ version (unittest)
2422 2422
  * The return value is the new capacity of the array (which may be larger than
2423 2423
  * the requested capacity).
2424 2424
  */
2425  
-size_t reserve(T)(ref T[] arr, size_t newcapacity)
  2425
+size_t reserve(T)(ref T[] arr, size_t newcapacity) pure nothrow
2426 2426
 {
2427 2427
     return _d_arraysetcapacity(typeid(T[]), newcapacity, cast(void *)&arr);
2428 2428
 }
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.