digitalmars.D - Very limited shared promotion
- Manu (13/13) Jun 17 2019 Is this valid?
- FeepingCreature (3/21) Jun 17 2019 Can `fun` execute a call to another nested function that accesses
- Manu (6/31) Jun 18 2019 Well, under my proposal, if the capture were actually qualified, it
- Timon Gehr (2/37) Jun 18 2019 Bullshit.
- Steven Schveighoffer (5/23) Jun 18 2019 Seems like it would be safe, but what is the use case for it? If you
- Timon Gehr (15/41) Jun 18 2019 He would write @system code that leaks the reference to other threads
- Timon Gehr (2/12) Jun 18 2019 Return type should have been int, of course...
- Timon Gehr (6/19) Jun 18 2019 And there should have been is expressions. Getting tired from pushing
- Manu (9/28) Jun 18 2019 I can't see it because the design specifically inhibits that static
- Timon Gehr (17/50) Jun 19 2019 Ok. I guess it suffices to say that it does not. (If you can promote the...
- Manu (18/48) Jun 18 2019 I'll just tweak your example one more little bit to make sure
- rikki cattermole (3/22) Jun 19 2019 Okay is everyone in agreement that this is a use case for D?
- rikki cattermole (2/2) Jun 19 2019 Never mind its already in.
- Manu (4/45) Jun 18 2019 👍
- Walter Bright (9/10) Jun 18 2019 When dealing with concurrency, that's like throwing a bunch of chemicals...
- Steven Schveighoffer (6/11) Jun 18 2019 Nope, what I wrote has nothing to do with that. I sense that you're not
- Walter Bright (2/3) Jun 19 2019 The rude remarks aren't acceptable here.
- Bart (6/10) Jun 21 2019 That is precisely what many corporations do on a daily basis. You
- Manu (3/25) Jun 18 2019 parallel foreach.
- Walter Bright (14/32) Jun 19 2019 Using the resulting value of x:
- Timon Gehr (17/37) Jun 19 2019 Probably `fun` should be `immutable` or `shared` as well. Otherwise
- Walter Bright (40/80) Jun 19 2019 `scope` prevents capture of a reference to `x`, mutable or not, that per...
- Timon Gehr (48/111) Jun 19 2019 I was talking about this:
- Timon Gehr (2/9) Jun 19 2019 (Sorry, was `fun(x)` instead of `fun(x0)`.)
- Walter Bright (19/71) Jun 19 2019 `scope` only guarantees no references to the scoped variable exist follo...
- Timon Gehr (18/107) Jun 21 2019 So in purely @safe code without any calls to @trusted functions you
- Walter Bright (12/12) Jun 21 2019 1. `scope` requires that the reference to `x` passed to fun() does not p...
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (4/13) Jun 21 2019 So you basically say that you need "x" to be typed as const when
- Manu (19/51) Jun 19 2019 Well, you're assuming that `fun()` is an unsafe function that did
- Walter Bright (4/6) Jun 21 2019 Not at all. Scope means it does not leak a reference that survives the e...
- Manu (11/17) Jun 21 2019 I agree it's 'theoretically' valid; that's why I suggested it and the
- Walter Bright (12/21) Jun 21 2019 I attempted to explain this before. The trouble does NOT come from the s...
- Manu (45/70) Jun 21 2019 And we heard you.
- matheus (13/20) Jun 21 2019 Sorry but I don't think this is true, or at least need some
- Manu (8/27) Jun 21 2019 1. This discussion only applies when writing @system code; I think
- Kagamin (5/7) Jun 21 2019 @trusted doesn't break the guarantees advertized by the type
- Meta (5/12) Jun 21 2019 @trusted is allowed to do whatever it wants internally, as long
- Kagamin (4/8) Jun 22 2019 Concurrency is indeed safe with that interface, so there's no
- Walter Bright (15/24) Jun 21 2019 Scope doesn't do that. I keep saying this. But the premise is strange, a...
- Manu (13/47) Jun 21 2019 I've asked you 5 or 6 times to show how to pass a pointer to another thr...
- Walter Bright (10/17) Jun 22 2019 void foo(scope shared(int)* p) @trusted {
- Manu (11/28) Jun 22 2019 I don't believe `passToThread` could receive a `scope shared(int)*`...
- Walter Bright (2/4) Jun 22 2019 In @system code you can do whatever you like.
- Manu (5/9) Jun 22 2019 If you engage in @system code to distribute across threads, it's a no
- Walter Bright (11/14) Jun 23 2019 It is not a no-brainer. Nowhere is it specified, and all the examples an...
- Timon Gehr (12/22) Jun 24 2019 Are you saying code like the following would have UB?
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (15/21) Jun 22 2019 I think perhaps it would be more clear if you make a distinction
- Manu (8/26) Jun 21 2019 Because it's safe to do from the calling scope's perspective, and that's
- Walter Bright (5/8) Jun 22 2019 The language does not require you to put the fence in, hence it cannot a...
- Manu (3/11) Jun 22 2019 Your code snippet is incomplete; show the implementation of passToThread...
- Walter Bright (2/3) Jun 22 2019 Doesn't make any difference.
- Paolo Invernizzi (3/7) Jun 22 2019 Oh, came on!
- Manu (5/9) Jun 22 2019 It really does. If you can't get the reference to another thread, then
- Walter Bright (4/5) Jun 23 2019 Use @system code. The programmer only has to ensure the escaped referenc...
- Timon Gehr (5/13) Jun 23 2019 You keep saying "before" and then "but no memory barriers".
- Walter Bright (9/12) Jun 23 2019 The reference to shared i does not extend past the end of the function c...
- aliak (4/19) Jun 24 2019 You can assign to a shared primitive without a compilation error?
- XavierAP (2/18) Jun 24 2019 (*) now replace this with a call to another thread to set x to 3.
- aliak (5/27) Jun 24 2019 ok so I guess you can indeed do that:
- Jonathan M Davis (47/72) Jun 24 2019 A number of us think that simply reading or assigning shared variables
- aliak (10/32) Jun 24 2019 You can have the same usability/aesthetics:
- Jonathan M Davis (20/61) Jun 24 2019 I've never seen a cast used in that manner. It's been my understanding t...
- Jonathan M Davis (41/49) Jun 22 2019 If I understand correctly, Manu and Timon are arguing that because you'r...
- Timon Gehr (3/7) Jun 22 2019 Why would @safe code not benefit? Manu's use case is @safe code calling
- Jonathan M Davis (11/19) Jun 22 2019 Because if all of the code is @safe, then the fact that it was converted...
- Manu (4/17) Jun 22 2019 Not being required to duplicate every threadsafe function is a huge
- Walter Bright (3/5) Jun 23 2019 You've suggested that all operations on shared data be done with atomic ...
- Manu (15/20) Jun 25 2019 I don't follow that logic? There may be atomics, but there are also
- Manu (33/88) Jun 22 2019 You say this as someone who has obviously never or rarely tried to use
- Kagamin (3/6) Jun 22 2019 Eh? No. You don't understand const, shared, scope, @safe and
- Timon Gehr (3/9) Jun 23 2019 This kind of statement is useless without a demonstration or
- Kagamin (4/5) Jun 24 2019 His wish is known: he has an idea about his own language and
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (55/60) Jun 23 2019 Although, it isn't quite as simple, since there are many factors
Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?
Jun 17 2019
On Monday, 17 June 2019 at 23:46:44 UTC, Manu wrote:Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Can `fun` execute a call to another nested function that accesses `x` unshared?
Jun 17 2019
On Tue, Jun 18, 2019 at 3:05 PM FeepingCreature via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Monday, 17 June 2019 at 23:46:44 UTC, Manu wrote:Well, under my proposal, if the capture were actually qualified, it wouldn't be possible to call another local function with a lesser-qualified capture; so that's another +1 for qualifying the capture! ;)Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Can `fun` execute a call to another nested function that accesses `x` unshared?
Jun 18 2019
On 18.06.19 14:00, Manu wrote:On Tue, Jun 18, 2019 at 3:05 PM FeepingCreature via Digitalmars-d <digitalmars-d puremagic.com> wrote:Bullshit.On Monday, 17 June 2019 at 23:46:44 UTC, Manu wrote:Well, under my proposal, if the capture were actually qualified, it wouldn't be possible to call another local function with a lesser-qualified capture; so that's another +1 for qualifying the capture! ;)Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Can `fun` execute a call to another nested function that accesses `x` unshared?
Jun 18 2019
On 6/17/19 7:46 PM, Manu wrote:Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Seems like it would be safe, but what is the use case for it? If you have a scope shared, which can't actually get shared with anything else, what does it buy you? -Steve
Jun 18 2019
On 18.06.19 15:29, Steven Schveighoffer wrote:On 6/17/19 7:46 PM, Manu wrote:He would write system code that leaks the reference to other threads temporarily, but ensures those references disappear once the parallel part of the computation has finished. His use case is safe parallel foreach without having to explicitly qualify the variables in the outer scope as `shared`: void sum(){ // note: just to illustrate the concept int result=0; foreach(i;iota(1000).parallel){ static assert(typeof(result)==shared(int)); result.atomic!"+="(i); } static assert(typeof(result)==int); return result; }Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Seems like it would be safe, but what is the use case for it? If you have a scope shared, which can't actually get shared with anything else, what does it buy you? -Steve
Jun 18 2019
On 18.06.19 16:13, Timon Gehr wrote:int sum(){ // note: just to illustrate the concept int result=0; foreach(i;iota(1000).parallel){ static assert(typeof(result)==shared(int)); result.atomic!"+="(i); } static assert(typeof(result)==int); return result; }Return type should have been int, of course...
Jun 18 2019
On 18.06.19 16:14, Timon Gehr wrote:On 18.06.19 16:13, Timon Gehr wrote:And there should have been is expressions. Getting tired from pushing back against all the nonsense. Just note that if you can make the above work with useless qualified capturing, you can do so with useful qualified capturing and this is so blatantly obvious that it causes me physical pain that Manu honestly does not see it.int sum(){ // note: just to illustrate the concept int result=0; foreach(i;iota(1000).parallel){ static assert(is(typeof(result)==shared(int))); result.atomic!"+="(i); } static assert(is(typeof(result)==int)); return result; }Return type should have been int, of course...
Jun 18 2019
On Wed, Jun 19, 2019 at 12:20 AM Timon Gehr via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 18.06.19 16:14, Timon Gehr wrote:I can't see it because the design specifically inhibits that static assert in the loop body. But I will revoke my opinion on this matter; arguing will only slow it down. If you know how to make that *EXACT* code you wrote above work, then we are done here and I will buy you a years supply of beer. Coupled with removing read/write access from shared, that is everything I need to go away and leave you all alone.On 18.06.19 16:13, Timon Gehr wrote:And there should have been is expressions. Getting tired from pushing back against all the nonsense. Just note that if you can make the above work with useless qualified capturing, you can do so with useful qualified capturing and this is so blatantly obvious that it causes me physical pain that Manu honestly does not see it.int sum(){ // note: just to illustrate the concept int result=0; foreach(i;iota(1000).parallel){ static assert(is(typeof(result)==shared(int))); result.atomic!"+="(i); } static assert(is(typeof(result)==int)); return result; }Return type should have been int, of course...
Jun 18 2019
On 19.06.19 05:11, Manu wrote:On Wed, Jun 19, 2019 at 12:20 AM Timon Gehr via Digitalmars-d <digitalmars-d puremagic.com> wrote:Ok. I guess it suffices to say that it does not. (If you can promote the entire stack frame to `shared` temporarily, you can just as easily promote only the part of the stack frame that your delegate actually wants to capture.)On 18.06.19 16:14, Timon Gehr wrote:I can't see it because the design specifically inhibits that static assert in the loop body.On 18.06.19 16:13, Timon Gehr wrote:And there should have been is expressions. Getting tired from pushing back against all the nonsense. Just note that if you can make the above work with useless qualified capturing, you can do so with useful qualified capturing and this is so blatantly obvious that it causes me physical pain that Manu honestly does not see it.int sum(){ // note: just to illustrate the concept int result=0; foreach(i;iota(1000).parallel){ static assert(is(typeof(result)==shared(int))); result.atomic!"+="(i); } static assert(is(typeof(result)==int)); return result; }Return type should have been int, of course...But I will revoke my opinion on this matter; arguing will only slow it down. If you know how to make that *EXACT* code you wrote above work,I don't know yet what the _most general_ implicit `shared` promotion rules could be, but I think I know how to do it in the type system for your specific case. For memory ordering, we can just say that because there is `scope` on the delegate, any trusted code that sends it to other threads temporarily is clearly already required to set up the respective memory barriers. (Because it needs to ensure that the deletion of all references to the closure context in all other threads is ordered before the owning thread returns.)then we are done here and I will buy you a years supply of beer. ...I don't drink. :)Coupled with removing read/write access from shared, that is everything I need to go away and leave you all alone.Then we are on the same page, but this will need a DIP, so it would make sense to think about it a bit more and to convince Walter that it can work.
Jun 19 2019
On Wed, Jun 19, 2019 at 1:11 PM Manu <turkeyman gmail.com> wrote:On Wed, Jun 19, 2019 at 12:20 AM Timon Gehr via Digitalmars-d <digitalmars-d puremagic.com> wrote:I'll just tweak your example one more little bit to make sure absolutely everything I care about is captured correctly: struct S { int result; void inc(int i) shared { result.atomic!"+="(i); } } int sum(){ S s; foreach(i; iota(1000).parallel){ static assert(is(typeof(s) == shared(S))); s.inc(i); } static assert(is(typeof(s) == S)); return s.result; } The combinations of primitives at work here will solve every challenge I'm aware of in our engine.On 18.06.19 16:14, Timon Gehr wrote:I can't see it because the design specifically inhibits that static assert in the loop body. But I will revoke my opinion on this matter; arguing will only slow it down. If you know how to make that *EXACT* code you wrote above work, then we are done here and I will buy you a years supply of beer. Coupled with removing read/write access from shared, that is everything I need to go away and leave you all alone.On 18.06.19 16:13, Timon Gehr wrote:And there should have been is expressions. Getting tired from pushing back against all the nonsense. Just note that if you can make the above work with useless qualified capturing, you can do so with useful qualified capturing and this is so blatantly obvious that it causes me physical pain that Manu honestly does not see it.int sum(){ // note: just to illustrate the concept int result=0; foreach(i;iota(1000).parallel){ static assert(is(typeof(result)==shared(int))); result.atomic!"+="(i); } static assert(is(typeof(result)==int)); return result; }Return type should have been int, of course...
Jun 18 2019
On 19/06/2019 3:20 PM, Manu wrote:I'll just tweak your example one more little bit to make sure absolutely everything I care about is captured correctly: struct S { int result; void inc(int i) shared { result.atomic!"+="(i); } } int sum(){ S s; foreach(i; iota(1000).parallel){ static assert(is(typeof(s) == shared(S))); s.inc(i); } static assert(is(typeof(s) == S)); return s.result; } The combinations of primitives at work here will solve every challenge I'm aware of in our engine.Okay is everyone in agreement that this is a use case for D? If so, lets get this into bugzilla!
Jun 19 2019
Never mind its already in. https://issues.dlang.org/show_bug.cgi?id=19984
Jun 19 2019
On Wed, Jun 19, 2019 at 12:15 AM Timon Gehr via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 18.06.19 15:29, Steven Schveighoffer wrote:👍 We must get here.On 6/17/19 7:46 PM, Manu wrote:He would write system code that leaks the reference to other threads temporarily, but ensures those references disappear once the parallel part of the computation has finished. His use case is safe parallel foreach without having to explicitly qualify the variables in the outer scope as `shared`: void sum(){ // note: just to illustrate the concept int result=0; foreach(i;iota(1000).parallel){ static assert(typeof(result)==shared(int)); result.atomic!"+="(i); } static assert(typeof(result)==int); return result; }Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Seems like it would be safe, but what is the use case for it? If you have a scope shared, which can't actually get shared with anything else, what does it buy you? -Steve
Jun 18 2019
On Wednesday, 19 June 2019 at 03:02:30 UTC, Manu wrote:On Wed, Jun 19, 2019 at 12:15 AM Timon Gehr via Digitalmars-d <digitalmars-d puremagic.com> wrote:How is it to know it is supposed to be shared though? opApply is already kind of a disaster with attributes.On 18.06.19 15:29, Steven Schveighoffer wrote:👍 We must get here.On 6/17/19 7:46 PM, Manu wrote:He would write system code that leaks the reference to other threads temporarily, but ensures those references disappear once the parallel part of the computation has finished. His use case is safe parallel foreach without having to explicitly qualify the variables in the outer scope as `shared`: void sum(){ // note: just to illustrate the concept int result=0; foreach(i;iota(1000).parallel){ static assert(typeof(result)==shared(int)); result.atomic!"+="(i); } static assert(typeof(result)==int); return result; }Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Seems like it would be safe, but what is the use case for it? If you have a scope shared, which can't actually get shared with anything else, what does it buy you? -Steve
Jun 18 2019
On Wed, Jun 19, 2019 at 1:15 PM Exil via Digitalmars-d < digitalmars-d puremagic.com> wrote:On Wednesday, 19 June 2019 at 03:02:30 UTC, Manu wrote:opApply receives a delegate; you would infer the delegate qualifier to the lambda as it already does for the loop counters from the arguments. Ie: opApply(scope void delegate() shared) <- infer the shared qualifier to the foreach body lambda.On Wed, Jun 19, 2019 at 12:15 AM Timon Gehr via Digitalmars-d <digitalmars-d puremagic.com> wrote:How is it to know it is supposed to be shared though? opApply is already kind of a disaster with attributes.On 18.06.19 15:29, Steven Schveighoffer wrote:=F0=9F=91=8D We must get here.On 6/17/19 7:46 PM, Manu wrote:He would write system code that leaks the reference to other threads temporarily, but ensures those references disappear once the parallel part of the computation has finished. His use case is safe parallel foreach without having to explicitly qualify the variables in the outer scope as `shared`: void sum(){ // note: just to illustrate the concept int result=3D0; foreach(i;iota(1000).parallel){ static assert(typeof(result)=3D=3Dshared(int)); result.atomic!"+=3D"(i); } static assert(typeof(result)=3D=3Dint); return result; }Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Seems like it would be safe, but what is the use case for it? If you have a scope shared, which can't actually get shared with anything else, what does it buy you? -Steve
Jun 18 2019
On Wed, Jun 19, 2019 at 1:24 PM Manu <turkeyman gmail.com> wrote:On Wed, Jun 19, 2019 at 1:15 PM Exil via Digitalmars-d < digitalmars-d puremagic.com> wrote:eOn Wednesday, 19 June 2019 at 03:02:30 UTC, Manu wrote:opApply receives a delegate; you would infer the delegate qualifier to th=On Wed, Jun 19, 2019 at 12:15 AM Timon Gehr via Digitalmars-d <digitalmars-d puremagic.com> wrote:How is it to know it is supposed to be shared though? opApply is already kind of a disaster with attributes.On 18.06.19 15:29, Steven Schveighoffer wrote:=F0=9F=91=8D We must get here.On 6/17/19 7:46 PM, Manu wrote:He would write system code that leaks the reference to other threads temporarily, but ensures those references disappear once the parallel part of the computation has finished. His use case is safe parallel foreach without having to explicitly qualify the variables in the outer scope as `shared`: void sum(){ // note: just to illustrate the concept int result=3D0; foreach(i;iota(1000).parallel){ static assert(typeof(result)=3D=3Dshared(int)); result.atomic!"+=3D"(i); } static assert(typeof(result)=3D=3Dint); return result; }Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Seems like it would be safe, but what is the use case for it? If you have a scope shared, which can't actually get shared with anything else, what does it buy you? -Stevelambda as it already does for the loop counters from the arguments. Ie: opApply(scope void delegate() shared) <- infer the shared qualifier to the foreach body lambda.The delegate is effectively a prototype for the lambda, it should be used verbatim.
Jun 18 2019
On 6/18/2019 6:29 AM, Steven Schveighoffer wrote:Seems like it would be safe,When dealing with concurrency, that's like throwing a bunch of chemicals into a vat and saying it seems safe to drink :-) Just remember that doubled checked locking fooled a lot of very smart people for many years. I got snookered by it, too. Not sure where I read about it recently, but some expert had come up with a lock free algorithm, and the bug was only discovered a couple years later. Unless you are really, really good at concurrency, it's best to stick with atomics and locks, every time.
Jun 18 2019
On 6/18/19 4:05 PM, Walter Bright wrote:On 6/18/2019 6:29 AM, Steven Schveighoffer wrote:Nope, what I wrote has nothing to do with that. I sense that you're not grasping that sending in unshared data to a place where it can't possibly be shared means it's probably safe to do. Why you would want to do this, I'm not sure. -SteveSeems like it would be safe,When dealing with concurrency, that's like throwing a bunch of chemicals into a vat and saying it seems safe to drink :-)
Jun 18 2019
On 6/18/2019 4:05 PM, Steven Schveighoffer wrote:[...]The rude remarks aren't acceptable here.
Jun 19 2019
On Tuesday, 18 June 2019 at 20:05:25 UTC, Walter Bright wrote:On 6/18/2019 6:29 AM, Steven Schveighoffer wrote:That is precisely what many corporations do on a daily basis. You bleached... you know what they use for bleaching? And I'm sure your intelligent enough to know that no reaction or cleaning process is 100%....Seems like it would be safe,When dealing with concurrency, that's like throwing a bunch of chemicals into a vat and saying it seems safe to drink :-)
Jun 21 2019
On Tue, Jun 18, 2019 at 11:30 PM Steven Schveighoffer via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 6/17/19 7:46 PM, Manu wrote:parallel foreach.Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Seems like it would be safe, but what is the use case for it? If you have a scope shared, which can't actually get shared with anything else, what does it buy you?
Jun 18 2019
On 6/17/2019 4:46 PM, Manu wrote:Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Using the resulting value of x: int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case int y = x; // add this line And now there's a problem. The compiler thinks x is thread local, so it doesn't add synchronization to the read of x. Not adding synchronization means that although fun() has terminated all the threads it spawned accessing x, it does not mean the memory caches of x are updated. While you may have coded fun() to update the caches of x before returning, the compiler can't know that, and so the compiler cannot allow the implicit conversion. In other words, adding `scope` does not guarantee SC-DRF (Sequential Consistency - Data Race Free).
Jun 19 2019
On 19.06.19 11:13, Walter Bright wrote:Using the resulting value of x: int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case int y = x; // add this line And now there's a problem. The compiler thinks x is thread local, so it doesn't add synchronization to the read of x.Probably `fun` should be `immutable` or `shared` as well. Otherwise there is no way to tell at the call site that it does not also capture `x` mutably, in which case the implicit promotion couldn't be allowed.Not adding synchronization means that although fun() has terminated all the threads it spawned accessing x, it does not mean the memory caches of x are updated. ...The trusted code that decided to send the `scope` reference to other threads has to make sure that all reads/writes to the `scope`d reference context are visible on the current thread before it returns. (Otherwise, from the perspective of the current thread, there are still copies of the `scope` reference around, even though the function call has terminated, which goes against `scope` guarantees.)While you may have coded fun() to update the caches of x before returning, the compiler can't know that, and so the compiler cannot allow the implicit conversion. ...The compiler can allow the implicit conversion because in safe code there is no way to send `scope` data to other threads and trusted code has to respect `scope` guarantees.In other words, adding `scope` does not guarantee SC-DRF (Sequential Consistency - Data Race Free).I think it does. The challenge is to ensure that the implicit promotion to `shared` doesn't result in `shared`/unshared aliasing. I don't think there is a problem with implicitly casting back to unshared, as the called function promises not to leak the references.
Jun 19 2019
On 6/19/2019 6:45 AM, Timon Gehr wrote:On 19.06.19 11:13, Walter Bright wrote:`scope` prevents capture of a reference to `x`, mutable or not, that persists after the return from `fun`.Using the resulting value of x: int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case int y = x; // add this line And now there's a problem. The compiler thinks x is thread local, so it doesn't add synchronization to the read of x.Probably `fun` should be `immutable` or `shared` as well. Otherwise there is no way to tell at the call site that it does not also capture `x` mutably, in which case the implicit promotion couldn't be allowed.The way atomic synchronization of variables works is when a write is performed, a write access fence happens after. When a read access is performed, a read access fence is performed. The last write to shared x in fun will do a write access fence, but the read access fence won't happen until there's the read of shared x, the read access fence won't happen. But the x after the return of fun is not shared, no read access fence will happen, and the read may not see the results of the last write to shared x.Not adding synchronization means that although fun() has terminated all the threads it spawned accessing x, it does not mean the memory caches of x are updated. ...The trusted code that decided to send the `scope` reference to other threads has to make sure that all reads/writes to the `scope`d reference context are visible on the current thread before it returns. (Otherwise, from the perspective of the current thread, there are still copies of the `scope` reference around, even though the function call has terminated, which goes against `scope` guarantees.)The scope guarantees are NOT sequential consistency data-race-free guarantees for multithreaded access.While you may have coded fun() to update the caches of x before returning, the compiler can't know that, and so the compiler cannot allow the implicit conversion. ...The compiler can allow the implicit conversion because in safe code there is no way to send `scope` data to other threads and trusted code has to respect `scope` guarantees.It does not. A write to an atomic is: write variable read fence A read from an atomic is: read fence read variable SC-DRF of atomics absolutely depends on this. The example code does: thread 1: write x read fence thread 2: read x This is wrong wrong wrong from what I know about atomics. The only way a local reference can be implicitly converted to a shared reference is if the compiler can prove there are no other local references to that memory location, which is essentially what Rust does. The read of `x` after `fun` returns violates that principle, and the result is a data race. --- There's another way to look at this: 1. When a politician promises a Free Lunch, you're gonna pay for it. 2. When a scientist discovers perpetual motion, he's made a mistake. 3. When a salesman sells you an effortless exercise machine, you won't get stronger. 4. When you respond to an ad for a "Get Rich Quick Through Real Estate" seminar, you're a sucker. -- and -- 5. When a data-race-free solution is found that doesn't follow fencing protocols, it doesn't work.In other words, adding `scope` does not guarantee SC-DRF (Sequential Consistency - Data Race Free).I think it does. The challenge is to ensure that the implicit promotion to `shared` doesn't result in `shared`/unshared aliasing. I don't think there is a problem with implicitly casting back to unshared, as the called function promises not to leak the references.
Jun 19 2019
On 19.06.19 23:03, Walter Bright wrote:On 6/19/2019 6:45 AM, Timon Gehr wrote:I was talking about this: int x0; void fun(scope ref shared(int) x1){ assert(&x0 is &x1); //uh, oh // ... } fun(x);On 19.06.19 11:13, Walter Bright wrote:`scope` prevents capture of a reference to `x`, mutable or not, that persists after the return from `fun`. ...Using the resulting value of x: int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case int y = x; // add this line And now there's a problem. The compiler thinks x is thread local, so it doesn't add synchronization to the read of x.Probably `fun` should be `immutable` or `shared` as well. Otherwise there is no way to tell at the call site that it does not also capture `x` mutably, in which case the implicit promotion couldn't be allowed....Right now, in ` safe` code it guarantees that there is _no_ multithreaded access, no? How do you leak a `scope` reference to another thread? If there is _no_ multithreaded access, you don't get any data races, so `scope` guarantees no data races on the `scope`d memory location given that the reference wasn't accessible from multiple threads before.The compiler can allow the implicit conversion because in safe code there is no way to send `scope` data to other threads and trusted code has to respect `scope` guarantees.The scope guarantees are NOT sequential consistency data-race-free guarantees for multithreaded access. ...Yes, what's above certainly does not work. My point was that that is not what the code would do, at least not legally. If the above happens, there would be some ` trusted` code somewhere that you can blame for it. Sending a `scope` variable to another thread is a ` system` operation. In order to ensure that `scope` guarantees are met, the ` trusted` code that performs the send needs to add memory barriers that ensure that all writes to the `scope` variable are visible on the current thread before it returns. So on thread 2 you would actually also have barriers before `x` is accessed again. Let's consider a case _without_ implicit promotion: shared(int) x=0; // x is shared all the way static void foo(scope ref shared(int) x) trusted{ // possibly send &x to other threads that mutate it and // then forget about &x // ... } foo(x); int y=x; // read x enforce(x==y); // read x again My claim is that if the enforcement fails, this indicates a bug in `foo`, because it violates guarantees you can derive from the fact that `x` is a local variable that is only passed to some other code via a `scope`d parameter: when `x` is read for the first time, we know that actually no other thread can have a reference to it which it can use for a write, so its value has to remain constant. It should therefore be possible to read `x` without any further barriers after `foo` returns. This is because `foo` already needs to ensure all writes from other threads are visible. It is possible that I am missing something (even though what you wrote is not it). Is there a possible implementation of `foo` that would be correct (in a language that does not have any implicit `shared` promotion), but for which a data race on `x` would result if we didn't add another fence before reading `x` after `foo` has returned?It does not. A write to an atomic is: write variable read fence A read from an atomic is: read fence read variable SC-DRF of atomics absolutely depends on this. The example code does: thread 1: write x read fence thread 2: read x This is wrong wrong wrong from what I know about atomics. ...In other words, adding `scope` does not guarantee SC-DRF (Sequential Consistency - Data Race Free).I think it does. The challenge is to ensure that the implicit promotion to `shared` doesn't result in `shared`/unshared aliasing. I don't think there is a problem with implicitly casting back to unshared, as the called function promises not to leak the references.... 5. When a data-race-free solution is found that doesn't follow fencing protocols, it doesn't work.We are on the same page on this. :)
Jun 19 2019
On 20.06.19 00:04, Timon Gehr wrote:int x0; void fun(scope ref shared(int) x1){ assert(&x0 is &x1); //uh, oh // ... } fun(x0);(Sorry, was `fun(x)` instead of `fun(x0)`.)
Jun 19 2019
On 6/19/2019 3:04 PM, Timon Gehr wrote:I was talking about this: int x0; void fun(scope ref shared(int) x1){ assert(&x0 is &x1); //uh, oh // ... } fun(x);I see.Right now, in ` safe` code it guarantees that there is _no_ multithreaded access, no? How do you leak a `scope` reference to another thread? If there is _no_ multithreaded access, you don't get any data races, so `scope` guarantees no data races on the `scope`d memory location given that the reference wasn't accessible from multiple threads before.`scope` only guarantees no references to the scoped variable exist following the exit of the function. What happens inside is anything goes as long as that invariant is maintained. That would include starting up a new thread that can access the scope variable, as long as the thread terminates its use of that variable before the function exits.Yes, what's above certainly does not work. My point was that that is not what the code would do, at least not legally. If the above happens, there would be some ` trusted` code somewhere that you can blame for it. Sending a `scope` variable to another thread is a ` system` operation. In order to ensure that `scope` guarantees are met, the ` trusted` code that performs the send needs to add memory barriers that ensure that all writes to the `scope` variable are visible on the current thread before it returns. So on thread 2 you would actually also have barriers before `x` is accessed again.`scope` does not offer such guarantees. It only guarantees no leaks of the reference that survive the end of the function. It does not guarantee synchronization of memory caches.Let's consider a case _without_ implicit promotion: shared(int) x=0; // x is shared all the way static void foo(scope ref shared(int) x) trusted{ // possibly send &x to other threads that mutate it and // then forget about &x // ... } foo(x); int y=x; // read x enforce(x==y); // read x again My claim is that if the enforcement fails, this indicates a bug in `foo`, because it violates guarantees you can derive from the fact that `x` is a local variable that is only passed to some other code via a `scope`d parameter: when `x` is read for the first time, we know that actually no other thread can have a reference to it which it can use for a write, so its value has to remain constant.That is not what `scope` does, but the example is still correct because the language does not say that x can be converted to a local, and so it is not converted, and the memory synchronization of x is maintained and it works.It should therefore be possible to read `x` without any further barriers after `foo` returns. This is because `foo` already needs to ensure all writes from other threads are visible.`scope` does not add such barriers. The implementer of foo may add a fence to do that, but the compiler doesn't know that and cannot rely on it.It is possible that I am missing something (even though what you wrote is not it). Is there a possible implementation of `foo` that would be correct (in a language that does not have any implicit `shared` promotion), but for which a data race on `x` would result if we didn't add another fence before reading `x` after `foo` has returned?AFAIK, another fence is required upon return of foo and before reading x. It's not just about whether an extra reference exists, it's about when the memory cache coherency happens.Phew! Good!5. When a data-race-free solution is found that doesn't follow fencing protocols, it doesn't work.We are on the same page on this. :)
Jun 19 2019
On 20.06.19 03:30, Walter Bright wrote:On 6/19/2019 3:04 PM, Timon Gehr wrote:So in purely safe code without any calls to trusted functions you can't send that reference anywhere.I was talking about this: int x0; void fun(scope ref shared(int) x1){ assert(&x0 is &x1); //uh, oh // ... } fun(x);I see.Right now, in ` safe` code it guarantees that there is _no_ multithreaded access, no? How do you leak a `scope` reference to another thread? If there is _no_ multithreaded access, you don't get any data races, so `scope` guarantees no data races on the `scope`d memory location given that the reference wasn't accessible from multiple threads before.`scope` only guarantees no references to the scoped variable exist following the exit of the function. What happens inside is anything goes as long as that invariant is maintained.That would include starting up a new thread that can access the scope variable, as long as the thread terminates its use of that variable before the function exits. ...Yes, but terms such as "before" are tricky when multiple threads are involved. Semi-formally, what you are saying is that there has to be a path from each write access in each spawned thread to the return of the function in the happens- before graph. I don't think it is possible to establish this without making it valid to access an unique reference without further memory barriers. I don't think you can allow a race to exist between the function return and any write accesses to `scope`'d parameters.My complaint is that the last two sentences appear mutually contradictory.Yes, what's above certainly does not work. My point was that that is not what the code would do, at least not legally. If the above happens, there would be some ` trusted` code somewhere that you can blame for it. Sending a `scope` variable to another thread is a ` system` operation. In order to ensure that `scope` guarantees are met, the ` trusted` code that performs the send needs to add memory barriers that ensure that all writes to the `scope` variable are visible on the current thread before it returns. So on thread 2 you would actually also have barriers before `x` is accessed again.`scope` does not offer such guarantees. It only guarantees no leaks of the reference that survive the end of the function. It does not guarantee synchronization of memory caches. ...We are using the same definition of `scope`. You haven't however provided a formal definition of what "before" means.Let's consider a case _without_ implicit promotion: shared(int) x=0; // x is shared all the way static void foo(scope ref shared(int) x) trusted{ // possibly send &x to other threads that mutate it and // then forget about &x // ... } foo(x); int y=x; // read x enforce(x==y); // read x again My claim is that if the enforcement fails, this indicates a bug in `foo`, because it violates guarantees you can derive from the fact that `x` is a local variable that is only passed to some other code via a `scope`d parameter: when `x` is read for the first time, we know that actually no other thread can have a reference to it which it can use for a write, so its value has to remain constant.That is not what `scope` does,but the example is still correct because the language does not say that x can be converted to a local, and so it is not converted, and the memory synchronization of x is maintained and it works. ...(Obviously that example works. That's not the point.)I don't think it makes any sense to claim "no other reference exists" at a specific program point if at that point you didn't properly synchronize with parties potentially still holding such a reference.It should therefore be possible to read `x` without any further barriers after `foo` returns. This is because `foo` already needs to ensure all writes from other threads are visible.`scope` does not add such barriers. The implementer of foo may add a fence to do that, but the compiler doesn't know that and cannot rely on it.It is possible that I am missing something (even though what you wrote is not it). Is there a possible implementation of `foo` that would be correct (in a language that does not have any implicit `shared` promotion), but for which a data race on `x` would result if we didn't add another fence before reading `x` after `foo` has returned?AFAIK, another fence is required upon return of foo and before reading x. It's not just about whether an extra reference exists, it's about when the memory cache coherency happens. ...
Jun 21 2019
1. `scope` requires that the reference to `x` passed to fun() does not persist past the return of fun(). If fun() is trusted, fun() can do anything it likes, including passing the reference to other threads, as long as fun() does some sort of synchronization to ensure the other threads no longer hold that reference past the return of fun(). 2. `scope` does not require memory cache coherency happen upon exit from fun(). Therefore, a non-atomic read of `x` after fun() exits is not guaranteed to have the latest value of `x`. --- A corollary: 1. D has no ` safe` mechanism to convert shared references back to thread local. In particular, `scope` isn't it.
Jun 21 2019
On Saturday, 22 June 2019 at 03:03:38 UTC, Walter Bright wrote:1. `scope` requires that the reference to `x` passed to fun() does not persist past the return of fun(). If fun() is trusted, fun() can do anything it likes, including passing the reference to other threads, as long as fun() does some sort of synchronization to ensure the other threads no longer hold that reference past the return of fun(). 2. `scope` does not require memory cache coherency happen upon exit from fun(). Therefore, a non-atomic read of `x` after fun() exits is not guaranteed to have the latest value of `x`.So you basically say that you need "x" to be typed as const when calling fun() if fun() transfers "x" to another thread. Ola
Jun 21 2019
On Wed, Jun 19, 2019 at 7:15 PM Walter Bright via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 6/17/2019 4:46 PM, Manu wrote:Well, you're assuming that `fun()` is an unsafe function that did violate the scope agreement and passed it out to worker threads. *ANY* deployment of trusted code requires that you know what you're doing. So, rather, let's assume that fun() does NOT do that, and it is in fact safe... your concern does not arise in that world where you're not manually violating the type system. So the issue you describe is only possible where you have deliberately written unsafe code to perform a very low-level operation. You're obviously incompetent if you don't implement the sequential consistency machinery at the conclusion of your low-level machine. I don't think saying "this person wasn't qualified to implement a trusted function" is a valid reason to reject an extremely useful improvement. TL;DR: I don't think your argument is valid. If it were, you would have to apply that reasoning to every trusted function out there. Language features are not required to consider that a trusted author may make a mistake; that is literally the point of trusted.Is this valid? int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case This appears to promote a thread-local to shared. The problem with such promotion is that it's not valid that a thread-local AND a shared reference to the same thing can exist at the same time. With scope, we can guarantee that the reference doesn't escape the callee. Since the argument is local to the calling thread, and since the calling thread can not be running other code at the same time as the call is executing, there is no way for any code to execute with a thread-local assumption while the callee makes shared assumptions. I think this might be safe?Using the resulting value of x: int x; void fun(scope ref shared(int) x) { ... } fun(x); // implicit promotion to shared in this case int y = x; // add this line And now there's a problem. The compiler thinks x is thread local, so it doesn't add synchronization to the read of x. Not adding synchronization means that although fun() has terminated all the threads it spawned accessing x, it does not mean the memory caches of x are updated. While you may have coded fun() to update the caches of x before returning, the compiler can't know that, and so the compiler cannot allow the implicit conversion. In other words, adding `scope` does not guarantee SC-DRF (Sequential Consistency - Data Race Free).
Jun 19 2019
On 6/19/2019 11:53 PM, Manu wrote:Well, you're assuming that `fun()` is an unsafe function that did violate the scope agreement and passed it out to worker threads.Not at all. Scope means it does not leak a reference that survives the end of the function. As long as the threads terminate before the function returns, it is valid code.
Jun 21 2019
On Fri, Jun 21, 2019 at 5:55 PM Walter Bright via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 6/19/2019 11:53 PM, Manu wrote:I agree it's 'theoretically' valid; that's why I suggested it and the construct makes sense. How would you write code that escapes the scope reference to another thread? This must require some trusted code to achieve sharing between threads; at that point, the trusted function is responsible for re-instating the thread-locality guarantees (which are trivial to implement). If you don't do trusted mischief, there's no way to migrate the value across threads, so your synchronisation issue shouldn't exist if no trusted code exists.Well, you're assuming that `fun()` is an unsafe function that did violate the scope agreement and passed it out to worker threads.Not at all. Scope means it does not leak a reference that survives the end of the function. As long as the threads terminate before the function returns, it is valid code.
Jun 21 2019
On 6/21/2019 1:17 AM, Manu wrote:I agree it's 'theoretically' valid; that's why I suggested it and the construct makes sense. How would you write code that escapes the scope reference to another thread? This must require some trusted code to achieve sharing between threads; at that point, the trusted function is responsible for re-instating the thread-locality guarantees (which are trivial to implement). If you don't do trusted mischief, there's no way to migrate the value across threads, so your synchronisation issue shouldn't exist if no trusted code exists.I attempted to explain this before. The trouble does NOT come from the shared function escaping a reference. It comes from the lack of memory coherency between threads. Scope says NOTHING about synchronizing access to shared memory. NOTHING. Just because there's no longer a reference to the shared memory location from another thread does not mean all the memory caches are synchronized. For that, synchronization is needed, and your load of thread local `x` does NOT include the necessary synchronization. If `x` was types as shared, the compiler WOULD include the necessary synchronization when reading it. That's what atomic reads are for. It's the entire point of atomic reads.
Jun 21 2019
On Fri, Jun 21, 2019 at 7:05 PM Walter Bright via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 6/21/2019 1:17 AM, Manu wrote:And we heard you.I agree it's 'theoretically' valid; that's why I suggested it and the construct makes sense. How would you write code that escapes the scope reference to another thread? This must require some trusted code to achieve sharing between threads; at that point, the trusted function is responsible for re-instating the thread-locality guarantees (which are trivial to implement). If you don't do trusted mischief, there's no way to migrate the value across threads, so your synchronisation issue shouldn't exist if no trusted code exists.I attempted to explain this before.The trouble does NOT come from the shared function escaping a reference. It comes from the lack of memory coherency between threads.There is no "between threads" unless you have already entered trusted land. The reason for my proposal in the OP is precisely to prevent passing data to other threads, that's the key that makes this promotion safe. Can you show how you managed to get a ref to another thread without entering trusted?Scope says NOTHING about synchronizing access to shared memory.Yes, obviously. But the point is to prevent that situation occurring in the first place. There's no possibility of the synchronisation issue arising unless there's some way to escape a scope ref to another thread, which scope prevents. Can you show how to escape a scope ref to another thread?NOTHING.No really, we get it. This isn't a revelation to anyone.Just because there's no longer a reference to the shared memory location from another thread does not mean all the memory caches are synchronized. For that, synchronization is needed, and your load of thread local `x` does NOT include the necessary synchronization.So, as I attempted to explain before; there ARE NO OTHER THREADS unless you engage in trusted activity. There are no synchronisation issues unless there are other threads, and there are no other threads because you couldn't possibly get a reference to another thread. Can you show how you escaped a reference to another thread without entering trusted territory? Now, assuming we intend to write a trusted machine to implement a parallel for, it's trivial to insert the 4 memory barriers to maintain data consistency. This is fine, we're in unsafe land, and there is no way to get to this situation without going unsafe.If `x` was types as shared, the compiler WOULD include the necessary synchronization when reading it.It had better not. I don't believe it can know how to do that correctly. I don't think a type qualifier is even remotely enough information for the compiler to implement data fencing correctly and efficiently.That's what atomic reads are for. It's the entire point of atomic reads.We're not talking about atomic reads here, we're talking about memory barriers. `shared` just needs to separate thread-local from shared, and make transfer between those two states unsafe. Then we can write the libraries we need. Remove all access from shared, then the only way we can do anything with shared is to cast it away, which is a system operation and as such, places responsibility on us as library authors to do the right stuff. Novices don't write threading libraries. They tend to be very small in surface area, and written once. It'll be fine, and it gives us the control we need. Anything more from the language at this very early stage is an over-reach. If you want to explore safe expansions to shared, then propose them as follow up, because they will be very controversial, whereas the foundation stuff is uncomplicated and mostly non-controversial. `shared` the language primitive is just not safe, and let us write the libraries.
Jun 21 2019
On Friday, 21 June 2019 at 12:07:06 UTC, Manu wrote:Novices don't write threading libraries. They tend to be very small in surface area, and written once. It'll be fine, and it gives us the control we need. Anything more from the language at this very early stage is an over-reach.Sorry but I don't think this is true, or at least need some facts/numbers. I've seen "novices" or different type of people doing things that they shouldn't. For example, in the last 2 places that I worked, I saw management put developers (Like Desktop) without any experience in web to write Web APIs and the result was terrible, but they need to do otherwise they would be fired. So just say novices barely it's to shallow. By the way even experienced programmers can commit mistakes, and they way you talk you assume they not, and bugs are out there in most software and even from big companies. Matheus.
Jun 21 2019
On Fri, Jun 21, 2019 at 11:27 PM matheus via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Friday, 21 June 2019 at 12:07:06 UTC, Manu wrote:1. This discussion only applies when writing system code; I think it's assumed that you take responsibility for your mistakes, no? 2. I'm not against safe improvements, I just think that's VERY hard to design, and we should have a useful low-level core in the language before spending years arguing about some semantics for safe interactions (which I'm not sure exist; I think they are libraries).Novices don't write threading libraries. They tend to be very small in surface area, and written once. It'll be fine, and it gives us the control we need. Anything more from the language at this very early stage is an over-reach.Sorry but I don't think this is true, or at least need some facts/numbers. I've seen "novices" or different type of people doing things that they shouldn't. For example, in the last 2 places that I worked, I saw management put developers (Like Desktop) without any experience in web to write Web APIs and the result was terrible, but they need to do otherwise they would be fired. So just say novices barely it's to shallow. By the way even experienced programmers can commit mistakes, and they way you talk you assume they not, and bugs are out there in most software and even from big companies.
Jun 21 2019
On Friday, 21 June 2019 at 12:07:06 UTC, Manu wrote:There is no "between threads" unless you have already entered trusted land.trusted doesn't break the guarantees advertized by the type system; when it does, it's a bug that should be fixed (in code, not in language). It's only the compiler is not smart enough to verify that trusted code uphold the type system.
Jun 21 2019
On Friday, 21 June 2019 at 17:32:08 UTC, Kagamin wrote:On Friday, 21 June 2019 at 12:07:06 UTC, Manu wrote:trusted is allowed to do whatever it wants internally, as long as it provides a *safe* interface. I believe that's what Manu's getting at in regards to his safe/ trusted distinction in regards to threading.There is no "between threads" unless you have already entered trusted land.trusted doesn't break the guarantees advertized by the type system; when it does, it's a bug that should be fixed (in code, not in language). It's only the compiler is not smart enough to verify that trusted code uphold the type system.
Jun 21 2019
On Friday, 21 June 2019 at 18:11:38 UTC, Meta wrote:trusted is allowed to do whatever it wants internally, as long as it provides a *safe* interface. I believe that's what Manu's getting at in regards to his safe/ trusted distinction in regards to threading.Concurrency is indeed safe with that interface, so there's no reason to assume that it doesn't happen, and implicit conversion from thread local to shared is illegal.
Jun 22 2019
On 6/21/2019 5:07 AM, Manu wrote:There is no "between threads" unless you have already entered trusted land. The reason for my proposal in the OP is precisely to prevent passing data to other threads, that's the key that makes this promotion safe.Scope doesn't do that. I keep saying this. But the premise is strange, anyway, as why would you want to convert a reference to shared if not to bass it to another thread?Can you show how you managed to get a ref to another thread without entering trusted?There's no such thing as a ref to another thread. You can pass a ref to another thread in a scope function, as long as the ref doesn't survive the scope of foo(). The compiler cannot check that, though, and so cannot bless the implicit conversion to shared. I've said this 4 or 5 times now.There's no possibility of the synchronisation issue arising unless there's some way to escape a scope ref to another thread, which scope prevents.You're inventing semantics for scope that I've repeatedly told you are not there.We're not talking about atomic reads here, we're talking about memory barriers.I recommend watching: https://www.youtube.com/watch?v=A8eCGOqgvH4 https://www.youtube.com/watch?v=KeLBd2EJLOU as you and I are not speaking the same language. In particular the sections on how atomics create sequentially consistent data-race-free reads and writes of variables. Not needing to read x atomically after foo() ends is just plain wrong.
Jun 21 2019
On Sat., 22 Jun. 2019, 9:40 am Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote:On 6/21/2019 5:07 AM, Manu wrote:I've asked you 5 or 6 times to show how to pass a pointer to another thread without violating scope. Can you show it? You keep saying you can.There is no "between threads" unless you have already entered trustedland.The reason for my proposal in the OP is precisely to prevent passing data to other threads, that's the key that makes this promotion safe.Scope doesn't do that. I keep saying this. But the premise is strange, anyway, as why would you want to convert a reference to shared if not to bass it to another thread?Can you show how you managed to get a ref to another thread without entering trusted?There's no such thing as a ref to another thread. You can pass a ref to another thread in a scope function, as long as the ref doesn't survive the scope of foo(). The compiler cannot check that, though, and so cannot bless the implicit conversion to shared. I've said this 4 or 5 times now.There's no possibility of the synchronisation issue arising unless there's some way to escape a scope ref to another thread, which scope prevents.You're inventing semantics for scope that I've repeatedly told you are not there.We're not talking about atomic reads here, we're talking about memory barriers. I recommend watching: https://www.youtube.com/watch?v=A8eCGOqgvH4 https://www.youtube.com/watch?v=KeLBd2EJLOU as you and I are not speaking the same language. In particular the sections on how atomics create sequentially consistent data-race-free reads and writes of variables. Not needing to read x atomically after foo() ends is just plain wrong.You need an acquire fence before the function returns, it's trivial. In many cases though you don't need one, because you may have already required one to determine that the workload is complete in the first place. Don't attempt this in the system language. Just let it be, we know what we're doing. If you want safe expansion, that's future work, and I personally couldn't care less about that work. I can create safe libraries with trusted functions, and I could do it _right now_.
Jun 21 2019
On 6/21/2019 5:26 PM, Manu wrote:I've asked you 5 or 6 times to show how to pass a pointer to another thread without violating scope. Can you show it? You keep saying you can.void foo(scope shared(int)* p) trusted { auto t = startThread(); passToThread(t, p); waitForThreadToExit(t); }You need an acquire fence before the function returns, it's trivial.That's right it is. But the compiler doesn't know you put one there, and scope does not cause it to be put there, and safe does not, either.If you want safe expansion, that's future work, and I personally couldn't care less about that work. I can create safe libraries with trusted functions, and I could do it _right now_.You're assuming semantics for scope and safe that are not there, and so the compiler cannot assume them, either.
Jun 22 2019
On Sat, Jun 22, 2019 at 5:57 PM Walter Bright via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 6/21/2019 5:26 PM, Manu wrote:I don't believe `passToThread` could receive a `scope shared(int)*`... can you show an implementation of passToThread?I've asked you 5 or 6 times to show how to pass a pointer to another thread without violating scope. Can you show it? You keep saying you can.void foo(scope shared(int)* p) trusted { auto t = startThread(); passToThread(t, p); waitForThreadToExit(t); }There couldn't have been cross-thread communication if it was safe.You need an acquire fence before the function returns, it's trivial.That's right it is. But the compiler doesn't know you put one there, and scope does not cause it to be put there, and safe does not, either.The compiler can assume that you didn't pass a scope value to another thread, because it would be impossible to do. Show how to do it? Your example above shows passToThread, but I'm just going to ask you to show how that function works, and we'll recurse until either the value reaches another thread, or you agree that scope prevents a value from escaping...If you want safe expansion, that's future work, and I personally couldn't care less about that work. I can create safe libraries with trusted functions, and I could do it _right now_.You're assuming semantics for scope and safe that are not there, and so the compiler cannot assume them, either.
Jun 22 2019
On 6/22/2019 1:02 AM, Manu wrote:I don't believe `passToThread` could receive a `scope shared(int)*`... can you show an implementation of passToThread?In system code you can do whatever you like.
Jun 22 2019
On Sat., 22 Jun. 2019, 6:40 pm Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote:On 6/22/2019 1:02 AM, Manu wrote:If you engage in system code to distribute across threads, it's a no brainer to expect that code to handle cache coherency measures. It would be broken if it didn't.I don't believe `passToThread` could receive a `scope shared(int)*`... can you show an implementation of passToThread?In system code you can do whatever you like.
Jun 22 2019
On 6/22/2019 4:24 PM, Manu wrote:If you engage in system code to distribute across threads, it's a no brainer to expect that code to handle cache coherency measures. It would be broken if it didn't.It is not a no-brainer. Nowhere is it specified, and all the examples and tutorials I've seen say DO NOT follow an atomic write with a non-atomic read of the same variable in another thread. Not only that, it's usually a CENTRAL THEME of these expositions. The only synchronization required (will be) for atomics, and that assumes atomic write followed by atomic read. Not atomic write followed by non-atomic read of the same memory. The code may appear to work on the x86 because Intel CPUs do some additional not-required synchronization on reads. But it'll be leaving a nightmare for some poor sap who tries to port the code to the ARM.
Jun 23 2019
On 24.06.19 04:44, Walter Bright wrote:On 6/22/2019 4:24 PM, Manu wrote:Are you saying code like the following would have UB? __gshared int x=0, y=0; void thread1(){ x.write(1,release); y.write(1,release); } void thread2(){ if(y.read(acquire)==1){ assert(x==1); // plain read } }If you engage in system code to distribute across threads, it's a no brainer to expect that code to handle cache coherency measures. It would be broken if it didn't.It is not a no-brainer. Nowhere is it specified, and all the examples and tutorials I've seen say DO NOT follow an atomic write with a non-atomic read of the same variable in another thread. Not only that, it's usually a CENTRAL THEME of these expositions. ...
Jun 24 2019
On Saturday, 22 June 2019 at 07:53:49 UTC, Walter Bright wrote:On 6/21/2019 5:26 PM, Manu wrote:I think perhaps it would be more clear if you make a distinction between: 1. fences that inform the compiler that "virtual registers" could be stale (conceptual entities at compile time) 2. fences targeting cache coherency (that hardware caches are stale at runtime). If fun() is separately compiled then there should be no distinction. The calling context must assume that fun() might have changed the values. So it does not affect (1). And if fun() flushes the caches explicitly or explictly loads the values changed by other threads after they are done, then it should not affect (2) either. Right? Ola.You need an acquire fence before the function returns, it's trivial.That's right it is. But the compiler doesn't know you put one there, and scope does not cause it to be put there, and safe does not, either.
Jun 22 2019
On Sat., 22 Jun. 2019, 9:40 am Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote:On 6/21/2019 5:07 AM, Manu wrote:Because it's safe to do from the calling scope's perspective, and that's the needed entry vector into a trusted solution. It is also useful too in some cases to reduce needlessly overloading methods which have a threadsafe implementation.There is no "between threads" unless you have already entered trustedland.The reason for my proposal in the OP is precisely to prevent passing data to other threads, that's the key that makes this promotion safe.Scope doesn't do that. I keep saying this. But the premise is strange, anyway, as why would you want to convert a reference to shared if not to bass it to another thread?Can you show how you managed to get a ref to another thread withoutCompiler is not required to check the users work in trusted code. It assumes the user conformed with language requirements.entering trusted?There's no such thing as a ref to another thread. You can pass a ref to another thread in a scope function, as long as the ref doesn't survive the scope of foo(). The compiler cannot check that, though, and so cannot bless the implicit conversion to shared. I've said this 4 or 5 times now.
Jun 21 2019
On 6/21/2019 5:33 PM, Manu wrote:Because it's safe to do from the calling scope's perspective,No, it is not.Compiler is not required to check the users work in trusted code. It assumes the user conformed with language requirements.The language does not require you to put the fence in, hence it cannot assume you did. I don't know how else to explain it to you. Your example does not work.
Jun 22 2019
On Sat, Jun 22, 2019 at 6:00 PM Walter Bright via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 6/21/2019 5:33 PM, Manu wrote:Your code snippet is incomplete; show the implementation of passToThread()...Because it's safe to do from the calling scope's perspective,No, it is not.Compiler is not required to check the users work in trusted code. It assumes the user conformed with language requirements.The language does not require you to put the fence in, hence it cannot assume you did. I don't know how else to explain it to you. Your example does not work.
Jun 22 2019
On 6/22/2019 1:03 AM, Manu wrote:Your code snippet is incomplete; show the implementation of passToThread()...Doesn't make any difference.
Jun 22 2019
On Saturday, 22 June 2019 at 08:36:18 UTC, Walter Bright wrote:On 6/22/2019 1:03 AM, Manu wrote:Oh, came on! /PYour code snippet is incomplete; show the implementation of passToThread()...Doesn't make any difference.
Jun 22 2019
On Sat., 22 Jun. 2019, 6:40 pm Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote:On 6/22/2019 1:03 AM, Manu wrote:It really does. If you can't get the reference to another thread, then everything you say is moot, and that's the whole point. Show how to escape the pointer when it is scope...?Your code snippet is incomplete; show the implementation ofpassToThread()... Doesn't make any difference.
Jun 22 2019
On 6/22/2019 4:22 PM, Manu wrote:Show how to escape the pointer when it is scope...?Use system code. The programmer only has to ensure the escaped reference ceases before the function returns to fulfill the 'scope' semantics. The scope semantics do not include memory barriers, though.
Jun 23 2019
On 24.06.19 04:31, Walter Bright wrote:On 6/22/2019 4:22 PM, Manu wrote:You keep saying "before" and then "but no memory barriers". I really don't understand this "before" without memory barriers. Without memory barriers, there is no "before". How to formalize this? What is it exactly that "scope" guarantees?Show how to escape the pointer when it is scope...?Use system code. The programmer only has to ensure the escaped reference ceases before the function returns to fulfill the 'scope' semantics. The scope semantics do not include memory barriers, though.
Jun 23 2019
On 6/23/2019 7:42 PM, Timon Gehr wrote:I really don't understand this "before" without memory barriers. Without memory barriers, there is no "before". How to formalize this? What is it exactly that "scope" guarantees?The reference to shared i does not extend past the end of the function call. That doesn't mean the cached value of i is propagated to all other threads. int x; void fun(scope ref shared(int) x) { x = 3; // (*) } // end of reference to x is guaranteed assert(x == 3); // all caches not updated, assert is not guaranteed (*) now replace this with a call to another thread to set x to 3.
Jun 23 2019
On Monday, 24 June 2019 at 02:55:39 UTC, Walter Bright wrote:On 6/23/2019 7:42 PM, Timon Gehr wrote:You can assign to a shared primitive without a compilation error? Does the compiler do the necessary synchronization under the hood? I would've assumed you'd have to use atomicStore or the like?I really don't understand this "before" without memory barriers. Without memory barriers, there is no "before". How to formalize this? What is it exactly that "scope" guarantees?The reference to shared i does not extend past the end of the function call. That doesn't mean the cached value of i is propagated to all other threads. int x; void fun(scope ref shared(int) x) { x = 3; // (*) } // end of reference to x is guaranteed assert(x == 3); // all caches not updated, assert is not guaranteed (*) now replace this with a call to another thread to set x to 3.
Jun 24 2019
On Monday, 24 June 2019 at 11:38:29 UTC, aliak wrote:On Monday, 24 June 2019 at 02:55:39 UTC, Walter Bright wrote:(*) now replace this with a call to another thread to set x to 3.int x; void fun(scope ref shared(int) x) { x = 3; // (*) } // end of reference to x is guaranteed assert(x == 3); // all caches not updated, assert is not guaranteed (*) now replace this with a call to another thread to set x to 3.You can assign to a shared primitive without a compilation error? Does the compiler do the necessary synchronization under the hood? I would've assumed you'd have to use atomicStore or the like?
Jun 24 2019
On Monday, 24 June 2019 at 11:38:29 UTC, aliak wrote:On Monday, 24 June 2019 at 02:55:39 UTC, Walter Bright wrote:ok so I guess you can indeed do that: https://d.godbolt.org/z/Ge7bTc Is that intended behavior? I would've expected an `xchg` or lock xchg or something?On 6/23/2019 7:42 PM, Timon Gehr wrote:You can assign to a shared primitive without a compilation error? Does the compiler do the necessary synchronization under the hood? I would've assumed you'd have to use atomicStore or the like?[...]The reference to shared i does not extend past the end of the function call. That doesn't mean the cached value of i is propagated to all other threads. int x; void fun(scope ref shared(int) x) { x = 3; // (*) } // end of reference to x is guaranteed assert(x == 3); // all caches not updated, assert is not guaranteed (*) now replace this with a call to another thread to set x to 3.
Jun 24 2019
On Monday, June 24, 2019 5:38:29 AM MDT aliak via Digitalmars-d wrote:On Monday, 24 June 2019 at 02:55:39 UTC, Walter Bright wrote:A number of us think that simply reading or assigning shared variables should be illegal, because the compiler doesn't do the necessary synchronization stuff for you. Currently, the compiler prevents it for ++ and -- on than basis, but that's it. However, one issue with that from a usability perspective is that even if you're doing all of the right stuff like protecting access to the shared variable via a mutex and then temporarily casting it to thread-local to operate on it while the mutex is locked, if you want to actually assign anything to the shared variable before releasing the mutex, you'd have to use a pointer to it that had been cast to thread-local, whereas right now, you're actually allowed to just assign to it. e.g. shared MyClass obj; synchronized(mutex) { obj = cast(shared)someObj; } works right now, but if writing to shared variables were illegal like it arguably should be, then you'd have to do something ugly like shared MyClass obj; synchronized(mutex) { auto ptr = cast(MyClass*)&obj; *ptr = someObj; } Either way, it's quite clear that as things stand, reading or writing a shared variable without doing something with threading primitives is non-atomic and won't be synchronized properly. So, arguably, the type system shouldn't allow it (and that was started by disalowing ++ and --, but it wasn't ever finished). However, when this was discussed at this last dconf, Walter and Andrei didn't want to make any changes along those lines until we'd nailed down the exact semantics of how shared is supposed to work. Right now, shared is pretty much just defined as not being thread-local without the finer details really being ironed out. So, for now, shared does prevent you from accidentally converting between thread-local and shared, but on the whole, it doesn't actually do anything to prevent you from doing stuff to a shared variable that isn't going to be atomic or thread-safe, and it doesn't introduce stuff like fences to synchronize anything across threads. It's entirely up to the programmer to use it correctly with almost no help from the compiler. All it's really doing is segregating the shared data from the thread-local data and preventing you from crossing that barrier without casting, making such code system. And that's definitely something useful, but the specification needs to be fully nailed down, and there are clearly some improvements that need to be made - though of course, a lot of the arguing is over the changes that should be made. Jonathan M DavisOn 6/23/2019 7:42 PM, Timon Gehr wrote:You can assign to a shared primitive without a compilation error? Does the compiler do the necessary synchronization under the hood? I would've assumed you'd have to use atomicStore or the like?I really don't understand this "before" without memory barriers. Without memory barriers, there is no "before". How to formalize this? What is it exactly that "scope" guarantees?The reference to shared i does not extend past the end of the function call. That doesn't mean the cached value of i is propagated to all other threads. int x; void fun(scope ref shared(int) x) { x = 3; // (*) } // end of reference to x is guaranteed assert(x == 3); // all caches not updated, assert is not guaranteed (*) now replace this with a call to another thread to set x to 3.
Jun 24 2019
On Monday, 24 June 2019 at 13:20:23 UTC, Jonathan M Davis wrote:However, one issue with that from a usability perspective is that even if you're doing all of the right stuff like protecting access to the shared variable via a mutex and then temporarily casting it to thread-local to operate on it while the mutex is locked, if you want to actually assign anything to the shared variable before releasing the mutex, you'd have to use a pointer to it that had been cast to thread-local, whereas right now, you're actually allowed to just assign to it. e.g. shared MyClass obj; synchronized(mutex) { obj = cast(shared)someObj; } works right now, but if writing to shared variables were illegal like it arguably should be, then you'd have to do something ugly like shared MyClass obj; synchronized(mutex) { auto ptr = cast(MyClass*)&obj; *ptr = someObj; }You can have the same usability/aesthetics: synchronized { cast(MyClass)obj = someObj; } Doesn't that work? Ideally though, I think you'd want: class MyClass { void opAssign(MyClass c) shared { ... } }
Jun 24 2019
On Monday, June 24, 2019 4:06:03 PM MDT aliak via Digitalmars-d wrote:On Monday, 24 June 2019 at 13:20:23 UTC, Jonathan M Davis wrote:I've never seen a cast used in that manner. It's been my understanding that casts always result in rvalues, but it's quite possible that your example works, and I've just made a wrong assumption about how casts work with regards to lvalues. Right or wrong though, I'm not the only one who has thought that, since this issue has been brought up before. But even if it doesn't work, it would be a possible improvement to make dealing with shared more palatable.However, one issue with that from a usability perspective is that even if you're doing all of the right stuff like protecting access to the shared variable via a mutex and then temporarily casting it to thread-local to operate on it while the mutex is locked, if you want to actually assign anything to the shared variable before releasing the mutex, you'd have to use a pointer to it that had been cast to thread-local, whereas right now, you're actually allowed to just assign to it. e.g. shared MyClass obj; synchronized(mutex) { obj = cast(shared)someObj; } works right now, but if writing to shared variables were illegal like it arguably should be, then you'd have to do something ugly like shared MyClass obj; synchronized(mutex) { auto ptr = cast(MyClass*)&obj; *ptr = someObj; }You can have the same usability/aesthetics: synchronized { cast(MyClass)obj = someObj; } Doesn't that work?Ideally though, I think you'd want: class MyClass { void opAssign(MyClass c) shared { ... } }Sure, but if you're doing that, that means that the class itself is handling its own synchronization, which doesn't work in all cases, and even if it did, it's just pushing the problem into the class. The implementation for opAssign is going to have deal with assigning to types that don't have shared opAssign where either atomic writes or mutexes with casts or whatnot are going to be needed. In general, encapsulating such code is great where possible, but at some point, the built-in types are going to need to be manipulated, and if the type is a user-defined type that's designed to be thread-local but works as shared with mutexes and casts, then it's not going to have a shared opAssign any more than an int or string will. So, such an opAssign is just encapsulating the problem, not making it go away. - Jonathan M Davis
Jun 24 2019
On Saturday, June 22, 2019 1:57:22 AM MDT Walter Bright via Digitalmars-d wrote:On 6/21/2019 5:33 PM, Manu wrote:If I understand correctly, Manu and Timon are arguing that because you're dealing with scope with only thread-local variables, the compiler knows that the state of the thread-local variable is properly up-to-date, because in safe code, it's impossible for it to be otherwise. And then if your trusted code ensures that the data on the original thread is up-to-date after it does its thing with using the object on other threads, then it all works. And as such, they think that it should be fine for the compiler to implicitly convert to shared with scope, because it's up to the programmer to make sure that all of the normal guarantees are in place and none of the guarantees are violated without trusted being involved. This makes sense to me as long as the programmer is doing the casting, because then safe still isn't doing anything with shared. It's just the programmer doing what trusted needs to do and making sure that the normal guarantees are maintained. The problem is that if the compiler then implicitly converts to scope shared in safe code, then all of a sudden, safe needs to worry about whether the variable is properly up-to-date, since it's no longer just assuming it based on it being thread-local. At best, it's assuming that the scope shared variable will never actually be shared across threads, since without trusted, it wouldn't be possible to share it across threads. And if it's assuming that, then why on earth would it implicitly convert the variable to shared? This whole idea seems to rest on the premise that the compiler will do a conversion for you, because it can't be a problem in safe code without trusted being involved, but it's also completely useless without trusted being involved, and the compiler doesn't make assumptions based on trusted. It makes them based on safe. trusted is just the way for the programmer to provide something that the compiler treats as safe even though it's the programmer making sure that it is instead of the compiler being able to actually verify it. It's not going to treat an trusted function any differently from an safe one aside from the mangling of the function name, and arguably, safe and trusted should have had the same name mangling in the first place, since there is no difference from the standpoint of the caller. The difference is only in how the implementation is checked by the compiler. As long as safe code doesn't actually benefit from the implicit conversion to scope shared, it makes no sense to me to have it happen. The entire point is for it to be used with trusted stuff. And if that's the case, then why not just have the cast be explicit and trusted like it normally would be? - Jonathan M DavisBecause it's safe to do from the calling scope's perspective,No, it is not.Compiler is not required to check the users work in trusted code. It assumes the user conformed with language requirements.The language does not require you to put the fence in, hence it cannot assume you did. I don't know how else to explain it to you. Your example does not work.
Jun 22 2019
On 22.06.19 22:32, Jonathan M Davis wrote:As long as safe code doesn't actually benefit from the implicit conversion to scope shared, it makes no sense to me to have it happen. The entire point is for it to be used with trusted stuff. And if that's the case, then why not just have the cast be explicit and trusted like it normally would be?Why would safe code not benefit? Manu's use case is safe code calling into a trusted library.
Jun 22 2019
On Saturday, June 22, 2019 2:52:33 PM MDT Timon Gehr via Digitalmars-d wrote:On 22.06.19 22:32, Jonathan M Davis wrote:Because if all of the code is safe, then the fact that it was converted to scope shared gains you nothing. It's not really shared across threads, and it doesn't enable any useful operations. The only time that you can do anything extra with it is when you then have trusted code within the safe code, and the trusted code then does something like temporarily pass a reference to another thread. And if the only time that the implicit conversion to scope shared is actually useful is when you have trusted code, then why not just have the cast be explicit and trusted? - Jonathan M DavisAs long as safe code doesn't actually benefit from the implicit conversion to scope shared, it makes no sense to me to have it happen. The entire point is for it to be used with trusted stuff. And if that's the case, then why not just have the cast be explicit and trusted like it normally would be?Why would safe code not benefit? Manu's use case is safe code calling into a trusted library.
Jun 22 2019
On Sun., 23 Jun. 2019, 7:08 am Jonathan M Davis via Digitalmars-d, < digitalmars-d puremagic.com> wrote:On Saturday, June 22, 2019 2:52:33 PM MDT Timon Gehr via Digitalmars-d wrote:Not being required to duplicate every threadsafe function is a huge advantage. We went on about this for weeks 6 months ago.On 22.06.19 22:32, Jonathan M Davis wrote:Because if all of the code is safe, then the fact that it was converted to scope shared gains you nothing. It's not really shared across threads, and it doesn't enable any useful operations.As long as safe code doesn't actually benefit from the implicit conversion to scope shared, it makes no sense to me to have it happen. The entire point is for it to be used with trusted stuff. And if that's the case, then why not just have the cast be explicit and trusted like it normally would be?Why would safe code not benefit? Manu's use case is safe code calling into a trusted library.
Jun 22 2019
On 6/22/2019 4:44 PM, Manu wrote:Not being required to duplicate every threadsafe function is a huge advantage. We went on about this for weeks 6 months ago.You've suggested that all operations on shared data be done with atomic function calls rather than operators. This implies the function bodies should be different.
Jun 23 2019
On Mon, Jun 24, 2019 at 12:50 PM Walter Bright via Digitalmars-d <digitalmars-d puremagic.com> wrote:On 6/22/2019 4:44 PM, Manu wrote:I don't follow that logic? There may be atomics, but there are also cast-away-shared patterns that may be used. It's all very situational. Whatever a shared function does is its own business, I'm interested in the API from the callers perspective here. Whatever a shared method does, it simply must be threadsafe. Such a function is still threadsafe whether there are many threads with a reference to the shared object, or just one. A method that performs a threadsafe operation on some object can safely perform it thread-locally, and we should be able to allow that assuming the callee does NOT retain a shared reference to the object beyond the life of the call. We should be able to rely on `scope` to guarantee references are not retained beyond the life of the function. This would be extremely useful.Not being required to duplicate every threadsafe function is a huge advantage. We went on about this for weeks 6 months ago.You've suggested that all operations on shared data be done with atomic function calls rather than operators. This implies the function bodies should be different.
Jun 25 2019
On Sun., 23 Jun. 2019, 6:32 am Jonathan M Davis via Digitalmars-d, < digitalmars-d puremagic.com> wrote:On Saturday, June 22, 2019 1:57:22 AM MDT Walter Bright via Digitalmars-d wrote:You say this as someone who has obviously never or rarely tried to use shared (as I suspect basically everyone here is among, possibly including Walter). It's quite annoying to implement many identical shared and unshared method overloads. Most shared methods are threadsafe, and equally valid to call on thread-local data too. The hypothetical on trial is about trusted code, but there are many useful cases where no trusted code need be present for the mechanic to be useful and save a lot of noise. void atomicInc(ref scope shared(int) x); int x; atomicInc(x); // <-- perfectly safe That's a boring example, but I shouldn't need to write a second copy of the function when it is threadsafe. This whole idea seems to rest on the premise that the compiler will do aOn 6/21/2019 5:33 PM, Manu wrote:If I understand correctly, Manu and Timon are arguing that because you're dealing with scope with only thread-local variables, the compiler knows that the state of the thread-local variable is properly up-to-date, because in safe code, it's impossible for it to be otherwise. And then if your trusted code ensures that the data on the original thread is up-to-date after it does its thing with using the object on other threads, then it all works. And as such, they think that it should be fine for the compiler to implicitly convert to shared with scope, because it's up to the programmer to make sure that all of the normal guarantees are in place and none of the guarantees are violated without trusted being involved. This makes sense to me as long as the programmer is doing the casting, because then safe still isn't doing anything with shared. It's just the programmer doing what trusted needs to do and making sure that the normal guarantees are maintained. The problem is that if the compiler then implicitly converts to scope shared in safe code, then all of a sudden, safe needs to worry about whether the variable is properly up-to-date, since it's no longer just assuming it based on it being thread-local. At best, it's assuming that the scope shared variable will never actually be shared across threads, since without trusted, it wouldn't be possible to share it across threads. And if it's assuming that, then why on earth would it implicitly convert the variable to shared?Because it's safe to do from the calling scope's perspective,No, it is not.Compiler is not required to check the users work in trusted code. It assumes the user conformed with language requirements.The language does not require you to put the fence in, hence it cannot assume you did. I don't know how else to explain it to you. Your example does not work.conversion for you, because it can't be a problem in safe code without trusted being involved, but it's also completely useless without trusted being involved,Wrong. I wish people that have never tried to use shared would just let those of us that have, and want to make the language feature useful have some authority on the matter. and the compiler doesn't make assumptions based on trusted.It makes them based on safe. trusted is just the way for the programmer to provide something that the compiler treats as safe even though it's the programmer making sure that it is instead of the compiler being able to actually verify it.And in this case, the compiler may treat the scope ref as if it remained thread local and freely ignore the possibility of synchronisation issues, because that's the reality made possible by safe. Any trusted code must maintain those assumptions. It'snot going to treat an trusted function any differently from an safe one aside from the mangling of the function name, and arguably, safe and trusted should have had the same name mangling in the first place, since there is no difference from the standpoint of the caller. The difference is only in how the implementation is checked by the compiler. As long as safe code doesn't actually benefit from the implicit conversion to scope shared,It does, because DRY and mitigating redundancy. itmakes no sense to me to have it happen. The entire point is for it to be used with trusted stuff.No, that's just this one example on trial. We had a huge weeks-long discussion 6 months ago about how the promotion is useful without trusted code. And if that's the case, then whynot just have the cast be explicit and trusted like it normally would be?Because you've overlooked the 99% case.
Jun 22 2019
On Saturday, 22 June 2019 at 23:41:49 UTC, Manu wrote:Wrong. I wish people that have never tried to use shared would just let those of us that have, and want to make the language feature useful have some authority on the matter.Eh? No. You don't understand const, shared, scope, safe and trusted. What authority?
Jun 22 2019
On 23.06.19 08:27, Kagamin wrote:On Saturday, 22 June 2019 at 23:41:49 UTC, Manu wrote:This kind of statement is useless without a demonstration or explanation. Also, I think Manu argued correctly in this thread.Wrong. I wish people that have never tried to use shared would just let those of us that have, and want to make the language feature useful have some authority on the matter.Eh? No. You don't understand const, shared, scope, safe and trusted.
Jun 23 2019
On Sunday, 23 June 2019 at 22:15:18 UTC, Timon Gehr wrote:Also, I think Manu argued correctly in this thread.His wish is known: he has an idea about his own language and wants a compiler for it. We already had this very argument about const and shared before, now it's about scope and safe.
Jun 24 2019
On Saturday, 22 June 2019 at 23:41:49 UTC, Manu wrote:And in this case, the compiler may treat the scope ref as if it remained thread local and freely ignore the possibility of synchronisation issues, because that's the reality made possible by safe. Any trusted code must maintain those assumptions.Although, it isn't quite as simple, since there are many factors that play into this. Including the specifics of the compiler optimization level and the concrete hardware (x86 does a lot to maintain cache coherency in hardware). So without language support you risk the compiler repeating all the sync work you do in your trusted code. Or you risk it breaking if you use a compiler with whole program optimization. What works with separate compilation does not necessarily work with full flow analysis. But this really depends on the details of the language semantics and how far the compiler can go with optimization. Any competitor to C++ ought to do better than C++ when it comes to optimization. Concurrency is an area where there is ample opportunity, as concurrency features in C++ are bolted on as an after thought. (Very primitive.) For instance, is the compiler allowed to elide atomic access if it marked as nonshared or immutable? It should be allowed to do it (assume it is known that the memory range is nonvolatile). So, if trusted means you can do anything, then optimization opportunities evaporates. In my view the type system should be very strict. I don't think trusted (or even system) should be allowed to break the core guarantees of type system without clearly marking the section and how it breaks it using designated language features. So trusted marks that you access language features that are not allowed in safe that could be used in a way that breaks memory safety. What you should ask for then is something similar for thread safety. What you might want to ask for is a rendezvous like language feature that temporarily turns nonshared to shared within its scope. Then you can implement parallel for with rendezvous as a primitive that the optimizer has to account for. That would be much safer than the programmer bypassing the type system without any proof that what they do is correct. Proving correctness for concurrency is very hard, you usually have to account for all possible combinations, so to do it in a reasonably convincing manner you'll have to build a model that proves all combinations. It isn't something that can be done on the back of an envelope. There are languages/tools for this (some use term rewriting languages to do such proofs), but I think it is safe to assume that few D programmers know how to do it or are willing to do it even if they know how. In C++ the type system is somewhat non-strict for historical and cultural reasons, but as the result the compiler can make few assumptions based on types, like with const. (Although they have made union more strict than i C). That means that other languages in theory can provide better optimization and code gen than C++. I think languages that want to compete with C++ should focus real hard on the weak spots of the C++ type system and find new opportunities in that area. So what you want is a stronger type system (e.g. language features like rendezvous), not to allow the programmer to bypass and weaken the type system. Ola.
Jun 23 2019