shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
Okay, so I've been thinking on this for a while... I think I have a
pretty good feel for how shared is meant to be.

1. shared should behave exactly like const, except in addition to
inhibiting write access, it also inhibits read access.

I think this is the foundation for a useful definition for shared, and
it's REALLY easy to understand and explain.

Current situation where you can arbitrarily access shared members
undermines any value it has. Shared must assure you don't access
members unsafely, and the only way to do that with respect to data
members, is to inhibit access completely.
I think shared is just const without read access.

Assuming this world... how do you use shared?

1. traditional; assert that the object become thread-local by
acquiring a lock, cast shared away
2. object may have shared methods; such methods CAN be called on
shared instances. such methods may internally implement
synchronisation to perform their function. perhaps methods of a
lock-free queue structure for instance, or operator overloads on
`Atomic!int`, etc.

In practise, there is no functional change in usage from the current
implementation, except we disallow unsafe accesses (which will make
the thing useful).

>From there, it opens up another critical opportunity; T* -> shared(T)*
promotion.
Const would be useless without T* -> const(T)* promotion. Shared
suffers a similar problem.
If you write a lock-free queue for instance, and all the methods are
`shared` (ie, threadsafe), then under the current rules, you can't
interact with the object when it's not shared, and that's fairly
useless.

Assuming the rules above: "can't read or write to members", and the
understanding that `shared` methods are expected to have threadsafe
implementations (because that's the whole point), what are the risks
from allowing T* -> shared(T)* conversion?

All the risks that I think have been identified previously assume that
you can arbitrarily modify the data. That's insanity... assume we fix
that... I think the promotion actually becomes safe now...?

Destroy...


Re: shared - i need it to be useful

2018-10-15 Thread Peter Alexander via Digitalmars-d

On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:

2. object may have shared methods; such methods CAN be called on
shared instances. such methods may internally implement
synchronisation to perform their function. perhaps methods of a
lock-free queue structure for instance, or operator overloads on
`Atomic!int`, etc.


Just checking my understanding: are you saying here that shared 
methods can effectively do anything and the burden of correctness 
is on the author? Or do you still have to cast the shared away 
first?






Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 12:15 PM Peter Alexander via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
> > 2. object may have shared methods; such methods CAN be called on
> > shared instances. such methods may internally implement
> > synchronisation to perform their function. perhaps methods of a
> > lock-free queue structure for instance, or operator overloads on
> > `Atomic!int`, etc.
>
> Just checking my understanding: are you saying here that shared
> methods can effectively do anything and the burden of correctness
> is on the author? Or do you still have to cast the shared away
> first?

Burden of correctness that a `shared` method is indeed threadsafe is
absolutely on the author; there's nothing we can do to guarantee this
(multithreading is hard!), but in this new world, a user of an API
will be able to see shared methods and assume that they're threadsafe,
since that would be (and should be!) the whole point.

Since `shared` has no read or write access, at the bottom of the
stack, it may be necessary that the function cast shared away to
implement its magic. If you lock a mutex for instance, then you
naturally need to cast it away; but that's solving the problem with a
sledge-hammer.
In my experience, most such functions are implemented with atomics;
and the atomic API already receives shared args, ie:
https://github.com/dlang/druntime/blob/master/src/core/atomic.d#L379
So, if you do your work with atomics, then you don't need any casts.
Also, if the object is a composite, then a `shared` method is able to
call `shared` methods on its members, and in that case, you are able
to do useful aggregate work without casting.

I think the interactions I describe above are all correct.


Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 12:15 PM Peter Alexander via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
> > 2. object may have shared methods; such methods CAN be called on
> > shared instances. such methods may internally implement
> > synchronisation to perform their function. perhaps methods of a
> > lock-free queue structure for instance, or operator overloads on
> > `Atomic!int`, etc.
>
> Just checking my understanding: are you saying here that shared
> methods can effectively do anything and the burden of correctness
> is on the author? Or do you still have to cast the shared away
> first?

Just to be clear:

> are you saying here that shared methods can effectively do anything

No, quite the opposite. I am saying that shared objects have neither
read nor write access to members. Shared means "no read or write
access". You can only call shared methods.


Re: shared - i need it to be useful

2018-10-15 Thread Nicholas Wilson via Digitalmars-d

On Monday, 15 October 2018 at 19:14:58 UTC, Peter Alexander wrote:

On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
2. object may have shared methods; such methods CAN be called 
on

shared instances. such methods may internally implement
synchronisation to perform their function. perhaps methods of a
lock-free queue structure for instance, or operator overloads 
on

`Atomic!int`, etc.


Just checking my understanding: are you saying here that shared 
methods can effectively do anything and the burden of 
correctness is on the author? Or do you still have to cast the 
shared away first?


Well `this` is still shared so you would be prevented from doing 
anything non-atomically or lock cast away shared, but that will 
be enforced by the type system. So the burden of  correctness is 
on the author, but the complier enforces correct behaviour.


Re: shared - i need it to be useful

2018-10-15 Thread jmh530 via Digitalmars-d

On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
Okay, so I've been thinking on this for a while... I think I 
have a pretty good feel for how shared is meant to be.


1. shared should behave exactly like const, except in addition 
to inhibiting write access, it also inhibits read access.




Are you familiar with reference capabilities[1] in the pony 
language? They describe many of them in terms of read/write 
uniqueness. Another way they describe them [2] is in denying 
aliases, like deny global read alias.



[1] 
https://tutorial.ponylang.io/capabilities/reference-capabilities.html

[2] See page 12-14: http://www.doc.ic.ac.uk/~scd/Pony-WG2.16.pdf


Re: shared - i need it to be useful

2018-10-15 Thread Nicholas Wilson via Digitalmars-d

On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:

Destroy...


Keep in mind immutable is implicitly shared (i.e. not in tls) 
because nobody can change it. It should stay readable for this 
reason.


Re: shared - i need it to be useful

2018-10-15 Thread Peter Alexander via Digitalmars-d

On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:

Destroy...


What you describe sounds better than what we currently have.

I have at least two concerns:

1. A single producer, single consumer (SPSC) queue is necessarily 
shared, but is only safe if there is one writing thread and one 
reading thread. Is it ok if shared also requires user discipline 
and/or runtime checks to ensure correct usage?


2. In your scheme (as I understand), a struct composed entirely 
of atomics would be able to implement shared methods without any 
casts, but also be completely thread *unsafe*. Is this okay?


Example of #2:

struct TwoInts {
  Atomic!int x, y;

  void swap() shared {
int z = x.load;
x.store(y.load);
y.store(z);
  }
}


Re: shared - i need it to be useful

2018-10-15 Thread Stanislav Blinov via Digitalmars-d

On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:

Assuming the rules above: "can't read or write to members", and 
the understanding that `shared` methods are expected to have 
threadsafe implementations (because that's the whole point), 
what are the risks from allowing T* -> shared(T)* conversion?


All the risks that I think have been identified previously 
assume that you can arbitrarily modify the data. That's 
insanity... assume we fix that... I think the promotion 
actually becomes safe now...?


You're still talking about implicit promotion? No, it does not 
become safe no matter what restrictions you put on `shared` 
instances, because the caller of any function that takes `shared` 
arguments remains blissfully unaware of this promotion.


Re: shared - i need it to be useful

2018-10-15 Thread 12345swordy via Digitalmars-d

On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
If you write a lock-free queue for instance, and all the 
methods are
`shared` (ie, threadsafe), then under the current rules, you 
can't

interact with the object when it's not shared, and that's fairly
useless.
Unless the compiler can show that it is ok to implicit/explicity 
convert the object to share without any unintended consequences. 
It should reject it.
It seems that the better solution would to implement a 
implicit/explict covertion system similar to c# conversion 
Operators

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/using-conversion-operators

But that itself requires an DIP itself.

-Alex




Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 12:45 PM Nicholas Wilson via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
> > Destroy...
>
> Keep in mind immutable is implicitly shared (i.e. not in tls)
> because nobody can change it. It should stay readable for this
> reason.

Are you saying `is(immutable(int) == shared) == true)` ??


Re: shared - i need it to be useful

2018-10-15 Thread ag0aep6g via Digitalmars-d

On 10/15/2018 08:46 PM, Manu wrote:

1. traditional; assert that the object become thread-local by
acquiring a lock, cast shared away
2. object may have shared methods; such methods CAN be called on
shared instances. such methods may internally implement
synchronisation to perform their function. perhaps methods of a
lock-free queue structure for instance, or operator overloads on
`Atomic!int`, etc.

[...]

Assuming the rules above: "can't read or write to members", and the
understanding that `shared` methods are expected to have threadsafe
implementations (because that's the whole point), what are the risks
from allowing T* -> shared(T)* conversion?


As far as I understand, the rule "can't read or write to members" is for 
the compiler, right? I can still read and write members, but I have to 
cast `shared` away and ensure thread-safety myself?


If that's so, then my example from the last thread might still apply:


struct Bob
{
  int* p;
  void doThing() shared
  {
p = &s; /* Might need a cast or two here, and a lock or an atomic 
store or whatever. */

  }
}
shared int s;


When the needed casts etc. are added, is `doThing` allowed? If not, I 
think you have to specify more precisely what a method can and can't do.


If `doThing` is ok, you can't allow T* -> shared(T)*. You'd be allowing 
aliasing an unqualified int* with a shared(int*):



void main()
{
  Bob* b = new Bob;
  shared(Bob)* sb = b; /* You'd allow this line. */
  sb.doThing();
  /* Now the unqualified int* b.p points to the shared int s. */
}



Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 1:05 PM Peter Alexander via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
> > Destroy...
>
> What you describe sounds better than what we currently have.
>
> I have at least two concerns:
>
> 1. A single producer, single consumer (SPSC) queue is necessarily
> shared, but is only safe if there is one writing thread and one
> reading thread. Is it ok if shared also requires user discipline
> and/or runtime checks to ensure correct usage?

I think you can model this differently... perhaps rather than a single
object, it's a coupled pair.
For instance, a thread-local enque object, and a separate thread-local
deque object. Those would express the 2 thread-local points of access
to queue and dequeue, and the implementation shunts between them in a
threadsafe way. I mean, it's not *really* a threadsafe primitive, so
for the whole object to be 'shared', I think that might be a
design-fail.

> 2. In your scheme (as I understand), a struct composed entirely
> of atomics would be able to implement shared methods without any
> casts, but also be completely thread *unsafe*. Is this okay?

It would be as safe as the design intends.
A struct with a bunch of public atomics effectively presents a set of
distinct atomics, and each one is thread-safe relative to eachother.
If the members are actually coupled into aggregate state, then you
make then private and methods implement the state transitions in such
a way guaranteeing atomicity.
Like I say before, the language can't "make it threadsafe" for you...
be producing a shared method, you have a responsibility to make sure
it works right.

> Example of #2:
>
> struct TwoInts {
>Atomic!int x, y;
>
>void swap() shared {
>  int z = x.load;
>  x.store(y.load);
>  y.store(z);
>}
> }

Your swap function is plain broken; it doesn't do what the API promises.
You can write all sorts of broken code, and this is a good example of
just plain broken code.
Also, `x` and `y` probably shouldn't be public, or it effectively
communicates that they're de-coupled state.


Re: shared - i need it to be useful

2018-10-15 Thread jmh530 via Digitalmars-d

On Monday, 15 October 2018 at 20:44:35 UTC, Manu wrote:

snip

Are you saying `is(immutable(int) == shared) == true)` ??


From the spec:
"Applying any qualifier to immutable T results in immutable T. 
This makes immutable a fixed point of qualifier combinations and 
makes types such as const(immutable(shared T)) impossible to 
create."


Example:

import std.stdio : writeln;

void main()
{
writeln(is(immutable(int) == shared immutable(int)) == true); 
//prints true

}


Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 1:15 PM Stanislav Blinov via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
>
> > Assuming the rules above: "can't read or write to members", and
> > the understanding that `shared` methods are expected to have
> > threadsafe implementations (because that's the whole point),
> > what are the risks from allowing T* -> shared(T)* conversion?
> >
> > All the risks that I think have been identified previously
> > assume that you can arbitrarily modify the data. That's
> > insanity... assume we fix that... I think the promotion
> > actually becomes safe now...?
>
> You're still talking about implicit promotion?

Absolutely. This is critical to make shared useful, and I think
there's a path to make it work.

> No, it does not
> become safe no matter what restrictions you put on `shared`
> instances, because the caller of any function that takes `shared`
> arguments remains blissfully unaware of this promotion.

It doesn't matter... what danger is there of distributing a shared pointer?
Are you concerned that someone with a shared reference might call a
threadsafe method? If that's an invalid operation, then the method has
broken it's basic agreed contract.

And I agree that it's conceivable that you could contrive a bad
program, but you can contrive a bad program with literally any
language feature!


Re: shared - i need it to be useful

2018-10-15 Thread Nicholas Wilson via Digitalmars-d

On Monday, 15 October 2018 at 20:54:12 UTC, jmh530 wrote:

On Monday, 15 October 2018 at 20:44:35 UTC, Manu wrote:

snip

Are you saying `is(immutable(int) == shared) == true)` ??


From the spec:
"Applying any qualifier to immutable T results in immutable T. 
This makes immutable a fixed point of qualifier combinations 
and makes types such as const(immutable(shared T)) impossible 
to create."


Example:

import std.stdio : writeln;

void main()
{
writeln(is(immutable(int) == shared immutable(int)) == 
true); //prints true

}


The philosophy of this is that: the value never changes, 
therefore only one copy of the variable needs to exist (i.e. 
immutable variables declared at module scope are _not_ thread 
local) and can be shared between threads with no race conditions.


I'm saying that while what you propose sounds very reasonable, we 
should make sure that reading immutable variables is still good ;)


Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 2:05 PM Nicholas Wilson via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 20:54:12 UTC, jmh530 wrote:
> > On Monday, 15 October 2018 at 20:44:35 UTC, Manu wrote:
> >> snip
> >>
> >> Are you saying `is(immutable(int) == shared) == true)` ??
> >
> > From the spec:
> > "Applying any qualifier to immutable T results in immutable T.
> > This makes immutable a fixed point of qualifier combinations
> > and makes types such as const(immutable(shared T)) impossible
> > to create."
> >
> > Example:
> >
> > import std.stdio : writeln;
> >
> > void main()
> > {
> > writeln(is(immutable(int) == shared immutable(int)) ==
> > true); //prints true
> > }
>
> The philosophy of this is that: the value never changes,
> therefore only one copy of the variable needs to exist (i.e.
> immutable variables declared at module scope are _not_ thread
> local) and can be shared between threads with no race conditions.
>
> I'm saying that while what you propose sounds very reasonable, we
> should make sure that reading immutable variables is still good ;)

I don't think what I describe affects immutable in any way;
`is(immutable(int) == shared) == false`, as it should be. Immutable
wouldn't have its rules affected by any change to shared.


Re: shared - i need it to be useful

2018-10-15 Thread Nicholas Wilson via Digitalmars-d

On Monday, 15 October 2018 at 21:08:38 UTC, Manu wrote:
On Mon, Oct 15, 2018 at 2:05 PM Nicholas Wilson via 
Digitalmars-d  wrote:
I'm saying that while what you propose sounds very reasonable, 
we should make sure that reading immutable variables is still 
good ;)


I don't think what I describe affects immutable in any way;
`is(immutable(int) == shared) == false`, as it should be. 
Immutable

wouldn't have its rules affected by any change to shared.


OK, just making sure you've got this covered.



Re: shared - i need it to be useful

2018-10-15 Thread Stanislav Blinov via Digitalmars-d

On Monday, 15 October 2018 at 20:57:46 UTC, Manu wrote:
On Mon, Oct 15, 2018 at 1:15 PM Stanislav Blinov via 
Digitalmars-d  wrote:


On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:

> Assuming the rules above: "can't read or write to members", 
> and the understanding that `shared` methods are expected to 
> have threadsafe implementations (because that's the whole 
> point), what are the risks from allowing T* -> shared(T)* 
> conversion?

>
> All the risks that I think have been identified previously 
> assume that you can arbitrarily modify the data. That's 
> insanity... assume we fix that... I think the promotion 
> actually becomes safe now...?


You're still talking about implicit promotion?


Absolutely. This is critical to make shared useful, and I think 
there's a path to make it work.



No, it does not
become safe no matter what restrictions you put on `shared`
instances, because the caller of any function that takes 
`shared`

arguments remains blissfully unaware of this promotion.


It doesn't matter... what danger is there of distributing a 
shared pointer? Are you concerned that someone with a shared 
reference might call a threadsafe method? If that's an invalid 
operation, then the method has broken it's basic agreed 
contract.


No, on the contrary. Someone with an unshared pointer may call 
unshared method or read/write data while someone else accesses it 
via `shared` interface precisely because you allow T to escape to 
shared(T). You *need* an explicit cast for this.


And I agree that it's conceivable that you could contrive a bad 
program, but you can contrive a bad program with literally any 
language feature!





Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 2:25 PM Stanislav Blinov via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 20:57:46 UTC, Manu wrote:
> > On Mon, Oct 15, 2018 at 1:15 PM Stanislav Blinov via
> > Digitalmars-d  wrote:
> >>
> >> On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
> >>
> >> > Assuming the rules above: "can't read or write to members",
> >> > and the understanding that `shared` methods are expected to
> >> > have threadsafe implementations (because that's the whole
> >> > point), what are the risks from allowing T* -> shared(T)*
> >> > conversion?
> >> >
> >> > All the risks that I think have been identified previously
> >> > assume that you can arbitrarily modify the data. That's
> >> > insanity... assume we fix that... I think the promotion
> >> > actually becomes safe now...?
> >>
> >> You're still talking about implicit promotion?
> >
> > Absolutely. This is critical to make shared useful, and I think
> > there's a path to make it work.
> >
> >> No, it does not
> >> become safe no matter what restrictions you put on `shared`
> >> instances, because the caller of any function that takes
> >> `shared`
> >> arguments remains blissfully unaware of this promotion.
> >
> > It doesn't matter... what danger is there of distributing a
> > shared pointer? Are you concerned that someone with a shared
> > reference might call a threadsafe method? If that's an invalid
> > operation, then the method has broken it's basic agreed
> > contract.
>
> No, on the contrary. Someone with an unshared pointer may call
> unshared method or read/write data while someone else accesses it
> via `shared` interface precisely because you allow T to escape to
> shared(T). You *need* an explicit cast for this.

If a shared method is incompatible with an unshared method, your class
is broken.
Explicit casting doesn't magically implement thread-safety, it
basically just guarantees failure. What I suggest are rules that lead
to proper behaviour with respect to writing a thread-safe API.
You can write bad code with any feature in any number of ways.

I see it this way:
If your object has shared methods, then it is distinctly and
*deliberately* involved in thread-safety. You have deliberately
opted-in to writing a thread-safe object, and you must deliver on your
promise.
The un-shared API of an object that supports `shared` are not exempt
from the thread-safety commitment, they are simply the subset of the
API that may not be called from a shared context.
If your shared method is incompatible with other methods, your class
is broken, and you violate your promise.

Nobody writes methods of an object such that they don't work with each
other... methods are part of a deliberately crafted and packaged
entity. If you write a shared object, you do so deliberately, and you
buy responsibility of making sure your objects API is thread-safe.
If your object is not thread-safe, don't write shared methods.


Re: shared - i need it to be useful

2018-10-15 Thread Peter Alexander via Digitalmars-d

On Monday, 15 October 2018 at 20:53:32 UTC, Manu wrote:
On Mon, Oct 15, 2018 at 1:05 PM Peter Alexander via 
Digitalmars-d  wrote:


On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
1. A single producer, single consumer (SPSC) queue is 
necessarily shared, but is only safe if there is one writing 
thread and one reading thread. Is it ok if shared also 
requires user discipline and/or runtime checks to ensure 
correct usage?


I think you can model this differently... perhaps rather than a 
single

object, it's a coupled pair.


That's a nice design.


Your swap function is plain broken; it doesn't do what the API 
promises.
You can write all sorts of broken code, and this is a good 
example of

just plain broken code.


If it is broken then why allow it? Why do we need to cast shared 
away if they weren't atomic and why do we allow it if they are 
atomic?


I understand that shared can't magically tell you when code is 
thread safe or not. It does make sense to disallow almost 
everything and require casts. I'm just not seeing the value of 
allowing shared methods to access shared members if it isn't 
thread safe. Make it require casts.


Re: shared - i need it to be useful

2018-10-15 Thread Stanislav Blinov via Digitalmars-d

On Monday, 15 October 2018 at 21:51:43 UTC, Manu wrote:

If a shared method is incompatible with an unshared method, 
your class is broken.


What?!? So... my unshared methods should also perform all that's 
necessary for `shared` methods?



Explicit casting doesn't magically implement thread-safety, it
basically just guarantees failure.


It doesn't indeed. It does, however, at least help prevent silent 
bugs. Via that same guaranteed failure. That failure is about all 
the help we can get from the compiler anyway.


What I suggest are rules that lead to proper behaviour with 
respect to writing a thread-safe API.

You can write bad code with any feature in any number of ways.


Yup. For example, passing an int* to a function expecting shared 
int*.



I see it this way:
If your object has shared methods, then it is distinctly and
*deliberately* involved in thread-safety. You have deliberately
opted-in to writing a thread-safe object, and you must deliver 
on your promise.


The un-shared API of an object that supports `shared` are not 
exempt from the thread-safety commitment, they are simply the 
subset of the API that may not be called from a shared context.


And therefore they lack any synchronization. So I don't see how 
they *can* be "compatible" with `shared` methods.


If your shared method is incompatible with other methods, your 
class is broken, and you violate your promise.


Nope.

class BigCounter {

this() { /* don't even need the mutex if I'm not sharing this 
*/ }


this(Mutex m = null) shared {
this.m = m ? m : new Mutex;
}

void increment() { value += 1; }
void increment() shared { synchronized(m) 
*value.assumeUnshared += 1; }


private:
Mutex m;
BigInt value;
}

They're not "compatible" in any shape or form. Or would you have 
the unshared ctor also create the mutex and unshared increment 
also take the lock? What's the point of having them  then? Better 
disallow mixed implementations altogether (which is actually not 
that bad of an idea).


Nobody writes methods of an object such that they don't work 
with each other... methods are part of a deliberately crafted 
and packaged
entity. If you write a shared object, you do so deliberately, 
and you buy responsibility of making sure your objects API is 
thread-safe.

If your object is not thread-safe, don't write shared methods.


Ahem... Okay...

import std.concurrency;
import core.atomic;

void thread(shared int* x) {
(*x).atomicOp!"+="(1);
}

shared int c;

void main() {
int x;
auto tid = spawn(&thread, &x); // "just" a typo
}

You're saying that's ok, it should "just" compile. It shouldn't. 
It should produce an error and a mild electric discharge into the 
developer's chair.


Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 4:25 PM Peter Alexander via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 20:53:32 UTC, Manu wrote:
> > On Mon, Oct 15, 2018 at 1:05 PM Peter Alexander via
> > Digitalmars-d  wrote:
> >>
> >> On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
> >> 1. A single producer, single consumer (SPSC) queue is
> >> necessarily shared, but is only safe if there is one writing
> >> thread and one reading thread. Is it ok if shared also
> >> requires user discipline and/or runtime checks to ensure
> >> correct usage?
> >
> > I think you can model this differently... perhaps rather than a
> > single
> > object, it's a coupled pair.
>
> That's a nice design.
>
>
> > Your swap function is plain broken; it doesn't do what the API
> > promises.
> > You can write all sorts of broken code, and this is a good
> > example of
> > just plain broken code.
>
> If it is broken then why allow it? Why do we need to cast shared
> away if they weren't atomic and why do we allow it if they are
> atomic?

It's not that it's 'allowed' other than, yes, access to atomic int's
is allowed in a threadsafe way because atomic access to individual
int's is threadsafe, so the primitive operation is perfectly
acceptable.

Your function models a higher-level concept; which is an atomic swap.
You need to make sure that a threadsafe API you're authoring does
actually deliver on the promise it makes.

> I understand that shared can't magically tell you when code is
> thread safe or not. It does make sense to disallow almost
> everything and require casts. I'm just not seeing the value of
> allowing shared methods to access shared members if it isn't
> thread safe. Make it require casts.

Because in an awful lot of cases, it is threadsafe. Implementing
low-level machinery, and consuming such machinery should have a 1:many
relationship. You're talking about adding friction to the 'many' such
that the '1' knows that they need to implement their function right? I
mean, they already know that, because they wrote 'shared' after their
function declaration.

In the common case, perhaps my object might aggregate a threadsafe
queue, and my shared method prepares an item and then adds it to the
queue. I think the vast majority case will be making use of utility
functionality and that shouldn't present undue friction.
It's not like atomic int's that are class members are going to be
accidentally twiddled; you added Atomic!int's to your class, and that
wasn't an accident... and if you're making use of the lowest-level
atomic primitives, it's fair to presume you know how to use them.
You're writing a shared method, which means you already encapsulate
the promise that you are implementing a function that deals with
thread-safety. I don't know what the cast in this case would add.


Re: shared - i need it to be useful

2018-10-15 Thread Nicholas Wilson via Digitalmars-d
On Monday, 15 October 2018 at 23:30:43 UTC, Stanislav Blinov 
wrote:

On Monday, 15 October 2018 at 21:51:43 UTC, Manu wrote:

If a shared method is incompatible with an unshared method, 
your class is broken.


What?!? So... my unshared methods should also perform all 
that's necessary for `shared` methods?


No, its the other way around: a shared method that does extra 
synchronisation should work irrespective of wether or not the 
object needs that synchronisation. e.g. atomic loading a TLS 
variable is fine.



Explicit casting doesn't magically implement thread-safety, it
basically just guarantees failure.


It doesn't indeed. It does, however, at least help prevent 
silent bugs. Via that same guaranteed failure. That failure is 
about all the help we can get from the compiler anyway.


What I suggest are rules that lead to proper behaviour with 
respect to writing a thread-safe API.

You can write bad code with any feature in any number of ways.


Yup. For example, passing an int* to a function expecting 
shared int*.


That is a reasonable thing to do if shared is const + no 
unsynched reads.



I see it this way:
If your object has shared methods, then it is distinctly and
*deliberately* involved in thread-safety. You have deliberately
opted-in to writing a thread-safe object, and you must deliver 
on your promise.


The un-shared API of an object that supports `shared` are not 
exempt from the thread-safety commitment, they are simply the 
subset of the API that may not be called from a shared context.


And therefore they lack any synchronization. So I don't see how 
they *can* be "compatible" with `shared` methods.




I think Manu means you have a shared object with some shared 
methods and some unshared methods. The shared methods deal with 
synchronisation and can therefore be call from anywhere by 
anyone, whereas the unshared methods must be called on a locked 
object.



snip
Nobody writes methods of an object such that they don't work 
with each other... methods are part of a deliberately crafted 
and packaged
entity. If you write a shared object, you do so deliberately, 
and you buy responsibility of making sure your objects API is 
thread-safe.

If your object is not thread-safe, don't write shared methods.


Ahem... Okay...

import std.concurrency;
import core.atomic;

void thread(shared int* x) {
(*x).atomicOp!"+="(1);
}

shared int c;

void main() {
int x;
auto tid = spawn(&thread, &x); // "just" a typo
}

You're saying that's ok, it should "just" compile. It 
shouldn't. It should produce an error and a mild electric 
discharge into the developer's chair.


Indeed that is just a typo, just as that is a contrived example. 
You'd notice that pretty quick in a debugger.




Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 4:35 PM Stanislav Blinov via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 21:51:43 UTC, Manu wrote:
>
> > If a shared method is incompatible with an unshared method,
> > your class is broken.
>
> What?!? So... my unshared methods should also perform all that's
> necessary for `shared` methods?

Of course! You're writing a threadsafe object... how could you expect otherwise?

> > Explicit casting doesn't magically implement thread-safety, it
> > basically just guarantees failure.
>
> It doesn't indeed. It does, however, at least help prevent silent
> bugs. Via that same guaranteed failure. That failure is about all
> the help we can get from the compiler anyway.

Just to be clear, what I'm suggesting is a significant *restriction*
to what shared already does... there will be a whole lot more safety
under my proposal.
The cast gives exactly nothing that attributing a method as shared
doesn't give you, except that attributing a method shared is so much
more sanitary and clearly communicates intent at the API level.

> > What I suggest are rules that lead to proper behaviour with
> > respect to writing a thread-safe API.
> > You can write bad code with any feature in any number of ways.
>
> Yup. For example, passing an int* to a function expecting shared
> int*.

I don't understand your example. What's the problem you're suggesting?

> > I see it this way:
> > If your object has shared methods, then it is distinctly and
> > *deliberately* involved in thread-safety. You have deliberately
> > opted-in to writing a thread-safe object, and you must deliver
> > on your promise.
>
> > The un-shared API of an object that supports `shared` are not
> > exempt from the thread-safety commitment, they are simply the
> > subset of the API that may not be called from a shared context.
>
> And therefore they lack any synchronization. So I don't see how
> they *can* be "compatible" with `shared` methods.

I don't understand this statement either. Who said they lack
synchronisation? If they need it, they will have it.
There's a good chance they don't need it though, they might not
interact with a thread-unsafe portion of the class.

> > If your shared method is incompatible with other methods, your
> > class is broken, and you violate your promise.
>
> Nope.

So certain...

> class BigCounter {
>
>  this() { /* don't even need the mutex if I'm not sharing this
> */ }
>
>  this(Mutex m = null) shared {
>  this.m = m ? m : new Mutex;
>  }
>
>  void increment() { value += 1; }
>  void increment() shared { synchronized(m)
> *value.assumeUnshared += 1; }
>
> private:
>  Mutex m;
>  BigInt value;
> }

You've just conflated 2 classes into one. One is a threadlocal
counter, the other is a threadsafe counter. Which is it?
Like I said before: "you can contrive a bad program with literally any
language feature!"

> They're not "compatible" in any shape or form.

Correct, you wrote 2 different things and mashed them together.

> Or would you have
> the unshared ctor also create the mutex and unshared increment
> also take the lock? What's the point of having them  then? Better
> disallow mixed implementations altogether (which is actually not
> that bad of an idea).

Right. This is key to my whole suggestion. If you write a shared
thing, you accept that it's shared! You don't just accept it, you jam
the stake in the ground.
There's a relatively small number of things that need to be
threadsafe, you won't see `shared` methods appearing at random. If you
use shared, you promise threadsafety OR the members of the thing are
inaccessible without some sort of lock-&-cast-away treatment.

> > Nobody writes methods of an object such that they don't work
> > with each other... methods are part of a deliberately crafted
> > and packaged
> > entity. If you write a shared object, you do so deliberately,
> > and you buy responsibility of making sure your objects API is
> > thread-safe.
> > If your object is not thread-safe, don't write shared methods.
>
> Ahem... Okay...
>
> import std.concurrency;
> import core.atomic;
>
> void thread(shared int* x) {
>  (*x).atomicOp!"+="(1);
> }
>
> shared int c;
>
> void main() {
>  int x;
>  auto tid = spawn(&thread, &x); // "just" a typo
> }
>
> You're saying that's ok, it should "just" compile. It shouldn't.
> It should produce an error and a mild electric discharge into the
> developer's chair.

Yup. It's a typo. You passed a stack pointer to a scope that outlives
the caller.
That class of issue is not on trial here. There's DIP1000, and all
sorts of things to try and improve safety in terms of lifetimes.
You only managed to contrive this by spawning a thread. If it were
just a normal function, this would be perfectly legitimate, and again,
that's my whole point.


Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 5:10 PM Nicholas Wilson via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 23:30:43 UTC, Stanislav Blinov
> wrote:
> > On Monday, 15 October 2018 at 21:51:43 UTC, Manu wrote:
> >
> >> I see it this way:
> >> If your object has shared methods, then it is distinctly and
> >> *deliberately* involved in thread-safety. You have deliberately
> >> opted-in to writing a thread-safe object, and you must deliver
> >> on your promise.
> >>
> >> The un-shared API of an object that supports `shared` are not
> >> exempt from the thread-safety commitment, they are simply the
> >> subset of the API that may not be called from a shared context.
> >
> > And therefore they lack any synchronization. So I don't see how
> > they *can* be "compatible" with `shared` methods.
> >
>
> I think Manu means you have a shared object with some shared
> methods and some unshared methods. The shared methods deal with
> synchronisation and can therefore be call from anywhere by
> anyone, whereas the unshared methods must be called on a locked
> object.

Yes, except maybe I didn't make it clear that I DO expect the
un-shared methods to be aware that a sibling shared method does exist
(you wrote it!), and that it may manipulate some state, so *if* the
un-shared method does interact with the same data that the shared
method may manipulate (in many cases, it won't; it's likely only a
small subset of an object's functionality that may have thread-safe
access), then the un-shared method does need to acknowledge that
functional overlap.
So even though a method is un-shared, it still needs to be aware that
it may have sibling methods that are shared. If they don't access an
overlapping data-set, no special handling is required. If they do
overlap, they may need to coordinate appropriately.


Re: shared - i need it to be useful

2018-10-15 Thread Isaac S. via Digitalmars-d

On Tuesday, 16 October 2018 at 00:36:12 UTC, Manu wrote:

*snip*

Yes, except maybe I didn't make it clear that I DO expect the
un-shared methods to be aware that a sibling shared method does 
exist
(you wrote it!), and that it may manipulate some state, so *if* 
the
un-shared method does interact with the same data that the 
shared
method may manipulate (in many cases, it won't; it's likely 
only a
small subset of an object's functionality that may have 
thread-safe

access), then the un-shared method does need to acknowledge that
functional overlap.
So even though a method is un-shared, it still needs to be 
aware that
it may have sibling methods that are shared. If they don't 
access an
overlapping data-set, no special handling is required. If they 
do

overlap, they may need to coordinate appropriately.


I understand your point but I think the current shared (no 
implicit conversion) has its uses. It can be quite useful to have 
one interface for when an object is shared and one for when it is 
not (one with and without the synchronization cost). Sure, 
something as trivial as a counter can be re-implemented in both 
ways but more complex objects would easily result in extreme code 
duplication.


It's also easy to acknowledge that implicit conversion to shared 
has its uses.


Instead of forcing one way or another, how about we leave the 
decision up to the programmer? Say something like "alias this 
shared;" enables implicit conversion to shared.


Re: shared - i need it to be useful

2018-10-15 Thread Stanislav Blinov via Digitalmars-d

On Tuesday, 16 October 2018 at 00:15:54 UTC, Manu wrote:
On Mon, Oct 15, 2018 at 4:35 PM Stanislav Blinov via 
Digitalmars-d  wrote:


What?!? So... my unshared methods should also perform all 
that's necessary for `shared` methods?


Of course! You're writing a threadsafe object... how could you 
expect otherwise?


See below.

Just to be clear, what I'm suggesting is a significant 
*restriction*
to what shared already does... there will be a whole lot more 
safety under my proposal.


I don't see how an *implicit* cast can be a restriction. At all.

The cast gives exactly nothing that attributing a method as 
shared
doesn't give you, except that attributing a method shared is so 
much

more sanitary and clearly communicates intent at the API level.


It's like we're talking about wholly different things here. 
Casting should be done by the caller, i.e. a programmer that uses 
some API. If that API expects shared arguments, the caller better 
make sure they pass shared values. Implicit conversion destroys 
any obligations between the caller and the API.


> You can write bad code with any feature in any number of 
> ways.


Yup. For example, passing an int* to a function expecting 
shared int*.


I don't understand your example. What's the problem you're 
suggesting?


The problem that I'm suggesting is exactly that: an `int*` is 
not, and can not, be a `shared int*` at the same time. Substitute 
int for any type. But D is not Rust and it can't statically 
prevent that, except for disallowing trivial programming 
mistakes, which, with implicit conversion introduced, would also 
go away.


...And therefore they lack any synchronization. So I don't see 
how they *can* be "compatible" with `shared` methods.


I don't understand this statement either. Who said they lack 
synchronisation? If they need it, they will have it. There's a 
good chance they don't need it though, they might not  interact 
with a thread-unsafe portion of the class.


Or they might.

> If your shared method is incompatible with other methods, 
> your class is broken, and you violate your promise.


Nope.


So certain...


class BigCounter {

 this() { /* don't even need the mutex if I'm not sharing 
this

*/ }

 this(Mutex m = null) shared {
 this.m = m ? m : new Mutex;
 }

 void increment() { value += 1; }
 void increment() shared { synchronized(m)
*value.assumeUnshared += 1; }

private:
 Mutex m;
 BigInt value;
}


You've just conflated 2 classes into one. One is a threadlocal
counter, the other is a threadsafe counter. Which is it?
Like I said before: "you can contrive a bad program with 
literally any language feature!"


Because that is exactly the code that a good amount of 
"developers" will write. Especially those of the "don't think 
about it" variety. Don't be mistaken for a second: if the 
language allows it, they'll write it.



They're not "compatible" in any shape or form.


Correct, you wrote 2 different things and mashed them together.


Can you actually provide an example of a mixed shared/unshared 
class that even makes sense then? As I said, at this point I'd 
rather see such definitions prohibited entirely.



Or would you have
the unshared ctor also create the mutex and unshared increment 
also take the lock? What's the point of having them  then? 
Better disallow mixed implementations altogether (which is 
actually not that bad of an idea).


Right. This is key to my whole suggestion. If you write a 
shared thing, you accept that it's shared! You don't just 
accept it, you jam the stake in the ground.


Then, once more, `shared` should then just be a type qualifier 
exclusively, and mixing shared/unshared methods should just not 
be allowed.



There's a relatively small number of things that need to be
threadsafe, you won't see `shared` methods appearing at random. 
If you use shared, you promise threadsafety OR the members of 
the thing are inaccessible without some sort of 
lock-&-cast-away treatment.


As above.


import std.concurrency;
import core.atomic;

void thread(shared int* x) {
 (*x).atomicOp!"+="(1);
}

shared int c;

void main() {
 int x;
 auto tid = spawn(&thread, &x); // "just" a typo
}

You're saying that's ok, it should "just" compile. It 
shouldn't. It should produce an error and a mild electric 
discharge into the developer's chair.


Yup. It's a typo. You passed a stack pointer to a scope that 
outlives the caller.
That class of issue is not on trial here. There's DIP1000, and 
all sorts of things to try and improve safety in terms of 
lifetimes.


I'm sorry, I'm not very good at writing "real" examples for 
things that don't exist or don't compile. End of sarcasm.


Let's come back to DIP1000 when it's actually implemented in it's 
entirety, ok? Anyway, you're nitpicking while actually missing 
the point altogether. The way `shared` is "implemented" today, 
the API (`thread` function) *requires* the caller to pass a 
`shared int*`. Implicit co

Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 7:15 PM Isaac S. via Digitalmars-d
 wrote:
>
> On Tuesday, 16 October 2018 at 00:36:12 UTC, Manu wrote:
> > *snip*
> >
> > Yes, except maybe I didn't make it clear that I DO expect the
> > un-shared methods to be aware that a sibling shared method does
> > exist
> > (you wrote it!), and that it may manipulate some state, so *if*
> > the
> > un-shared method does interact with the same data that the
> > shared
> > method may manipulate (in many cases, it won't; it's likely
> > only a
> > small subset of an object's functionality that may have
> > thread-safe
> > access), then the un-shared method does need to acknowledge that
> > functional overlap.
> > So even though a method is un-shared, it still needs to be
> > aware that
> > it may have sibling methods that are shared. If they don't
> > access an
> > overlapping data-set, no special handling is required. If they
> > do
> > overlap, they may need to coordinate appropriately.
>
> I understand your point but I think the current shared (no
> implicit conversion) has its uses. It can be quite useful to have
> one interface for when an object is shared and one for when it is
> not (one with and without the synchronization cost). Sure,
> something as trivial as a counter can be re-implemented in both
> ways but more complex objects would easily result in extreme code
> duplication.

If you can give a single 'use', I'm all ears ;)

> It's also easy to acknowledge that implicit conversion to shared
> has its uses.

I actually know of many real uses for this case.

> Instead of forcing one way or another, how about we leave the
> decision up to the programmer? Say something like "alias this
> shared;" enables implicit conversion to shared.

That might be fine.
Like I said in OP, the first point that I think needs to be agreed on,
is that shared can not read or write members. I think that's a
pre-requisite for any interesting development.


Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 7:25 PM Stanislav Blinov via Digitalmars-d
 wrote:
>
> On Tuesday, 16 October 2018 at 00:15:54 UTC, Manu wrote:
> > On Mon, Oct 15, 2018 at 4:35 PM Stanislav Blinov via
> > Digitalmars-d  wrote:
>
> >> What?!? So... my unshared methods should also perform all
> >> that's necessary for `shared` methods?
>
> > Of course! You're writing a threadsafe object... how could you
> > expect otherwise?
>
> See below.
>
> > Just to be clear, what I'm suggesting is a significant
> > *restriction*
> > to what shared already does... there will be a whole lot more
> > safety under my proposal.
>
> I don't see how an *implicit* cast can be a restriction. At all.

Because a shared pointer can't access anything.
You can't do anything with a shared instance, so the can be no harm done.

Only if there are shared methods (that promise thread-safety) is it
that shared gets interesting.
Without that, it's just a market for the existing recommended use of
shared; which is lock and cast away.

> > The cast gives exactly nothing that attributing a method as
> > shared
> > doesn't give you, except that attributing a method shared is so
> > much
> > more sanitary and clearly communicates intent at the API level.
>
> It's like we're talking about wholly different things here.
> Casting should be done by the caller, i.e. a programmer that uses
> some API. If that API expects shared arguments, the caller better
> make sure they pass shared values. Implicit conversion destroys
> any obligations between the caller and the API.

Why? What could a function do with shared arguments?

> >> > You can write bad code with any feature in any number of
> >> > ways.
> >>
> >> Yup. For example, passing an int* to a function expecting
> >> shared int*.
> >
> > I don't understand your example. What's the problem you're
> > suggesting?
>
> The problem that I'm suggesting is exactly that: an `int*` is
> not, and can not, be a `shared int*` at the same time. Substitute
> int for any type. But D is not Rust and it can't statically
> prevent that, except for disallowing trivial programming
> mistakes, which, with implicit conversion introduced, would also
> go away.

Why not? The guy who receives the argument receives an argument that
*may be shared*, and as such, he's restricted access to it
appropriately.
Just like if you receive a const thing, you can't write to it, even if
the caller's thing isn't const.
If you receive a shared thing, you can't read or write to it.

> >> ...And therefore they lack any synchronization. So I don't see
> >> how they *can* be "compatible" with `shared` methods.
> >
> > I don't understand this statement either. Who said they lack
> > synchronisation? If they need it, they will have it. There's a
> > good chance they don't need it though, they might not  interact
> > with a thread-unsafe portion of the class.
>
> Or they might.

Then you will implement synchronisation, or have violated your
thread-safety promise.

> >> > If your shared method is incompatible with other methods,
> >> > your class is broken, and you violate your promise.
> >>
> >> Nope.
> >
> > So certain...
> >
> >> class BigCounter {
> >>
> >>  this() { /* don't even need the mutex if I'm not sharing
> >> this
> >> */ }
> >>
> >>  this(Mutex m = null) shared {
> >>  this.m = m ? m : new Mutex;
> >>  }
> >>
> >>  void increment() { value += 1; }
> >>  void increment() shared { synchronized(m)
> >> *value.assumeUnshared += 1; }
> >>
> >> private:
> >>  Mutex m;
> >>  BigInt value;
> >> }
> >
> > You've just conflated 2 classes into one. One is a threadlocal
> > counter, the other is a threadsafe counter. Which is it?
> > Like I said before: "you can contrive a bad program with
> > literally any language feature!"
>
> Because that is exactly the code that a good amount of
> "developers" will write. Especially those of the "don't think
> about it" variety. Don't be mistaken for a second: if the
> language allows it, they'll write it.

This is not even an argument.
Atomic!int must be used with care. Any threading of ANY KIND must be
handled with care.
Saying we shouldn't make shared useful because someone can do
something wrong is like saying we shouldn't have atomic int's and we
shouldn't have spawn(). They're simply too dangerous to give to
users...

> >> They're not "compatible" in any shape or form.
> >
> > Correct, you wrote 2 different things and mashed them together.
>
> Can you actually provide an example of a mixed shared/unshared
> class that even makes sense then? As I said, at this point I'd
> rather see such definitions prohibited entirely.

I think this is a typical sort of construction:

struct ThreadsafeQueue(T)
{
  void QueueItem(T*) shared;
  T* UnqueueItem() shared;
}

struct SpecialWorkList
{
  struct Job { ... }

  void MakeJob(int x, float y, string z) shared  // <- any thread may
produce a job
  {
Job* job = new Job; // <- this is thread-local
PopulateJob(job, x, y, z); // <

Re: shared - i need it to be useful

2018-10-15 Thread Isaac S. via Digitalmars-d

On Tuesday, 16 October 2018 at 02:26:04 UTC, Manu wrote:
I understand your point but I think the current shared (no 
implicit conversion) has its uses. It can be quite useful to 
have one interface for when an object is shared and one for 
when it is not (one with and without the synchronization 
cost). Sure, something as trivial as a counter can be 
re-implemented in both ways but more complex objects would 
easily result in extreme code duplication.


If you can give a single 'use', I'm all ears ;)


My usages are a custom ref counted template and list types (which 
are built on top of the former). The ref counted template will 
initialize naively when not shared but utilize compare-and-set 
and thread yielding if needed when shared (ensureInitialized can 
occur any time after declaration). The list types (which are not 
shared-compatible *yet*) will utilize a read-write mutex only 
when shared (no cost beyond a little unused memory to non-shared 
objects). As such, casting any of the non-shared versions of 
these types to shared would be unsafe.


(Technically the types can be safely casted to shared so long as 
no non-shared

reference to them exists and vice versa.)

It's also easy to acknowledge that implicit conversion to 
shared has its uses.

I actually know of many real uses for this case.
(There was a time when I wanted it as well. I've forgotten what 
for by now.)



That might be fine.
Like I said in OP, the first point that I think needs to be 
agreed on,

is that shared can not read or write members. I think that's a
pre-requisite for any interesting development.


I will agree that *outsiders* should not be able to read/write 
members of a shared object. I'm not sure whether your design 
means even within a shared function such members cannot be read 
from or written to. If it does, the casting required may get a 
little annoying but isn't unworkable (especially since an 
unshared template can do the cast in an @trusted manner).


Re: shared - i need it to be useful

2018-10-15 Thread Manu via Digitalmars-d
On Mon, Oct 15, 2018 at 8:55 PM Isaac S. via Digitalmars-d
 wrote:
>
> On Tuesday, 16 October 2018 at 02:26:04 UTC, Manu wrote:
> >> I understand your point but I think the current shared (no
> >> implicit conversion) has its uses. It can be quite useful to
> >> have one interface for when an object is shared and one for
> >> when it is not (one with and without the synchronization
> >> cost). Sure, something as trivial as a counter can be
> >> re-implemented in both ways but more complex objects would
> >> easily result in extreme code duplication.
> >
> > If you can give a single 'use', I'm all ears ;)
>
> My usages are a custom ref counted template and list types (which
> are built on top of the former). The ref counted template will
> initialize naively when not shared but utilize compare-and-set
> and thread yielding if needed when shared (ensureInitialized can
> occur any time after declaration). The list types (which are not
> shared-compatible *yet*) will utilize a read-write mutex only
> when shared (no cost beyond a little unused memory to non-shared
> objects). As such, casting any of the non-shared versions of
> these types to shared would be unsafe.
>
> (Technically the types can be safely casted to shared so long as
> no non-shared
> reference to them exists and vice versa.)

Can you link to code? It doesn't sound incompatible with my proposal.
Mutexes are blunt instruments, and easy to model. My whole suggestion
has virtually no effect on the mutex protected case, which is what
most people seem to talk about.

> > That might be fine.
> > Like I said in OP, the first point that I think needs to be
> > agreed on,
> > is that shared can not read or write members. I think that's a
> > pre-requisite for any interesting development.
>
> I will agree that *outsiders* should not be able to read/write
> members of a shared object. I'm not sure whether your design
> means even within a shared function such members cannot be read
> from or written to.

Absolutely. Shared references can not access members, that's the rule.
It's completely unsafe to read or write to members of a shared thing. Period.
Attributing a method shared doesn't change that fact.

You can create a thread-local (ie, accessible) object from a shared
object by acquiring a lock on it for some duration, and it would be
helpful if the mechanism that fetches the lock also cast away shared
as part of the lock operation.

> If it does, the casting required may get a
> little annoying but isn't unworkable (especially since an
> unshared template can do the cast in an @trusted manner).

I agree users shouldn't write casts explicitly, they should use a lock
helper or something that grabs the lock and returns the un-shared
reference.
The existing behaviour is totally unacceptable though; shared objects
can read/write arbitrarily... what's the point of that? It gives the
impression that it's okay, but it's completely broken.
There is no situation where a shared thing can arbitrarily access its members.


Re: shared - i need it to be useful

2018-10-16 Thread Jacob Carlborg via Digitalmars-d

On 2018-10-15 20:46, Manu wrote:


1. traditional; assert that the object become thread-local by
acquiring a lock, cast shared away


Instead of having to explicitly cast away shared we could leverage the 
synchronized statement. It could be enhanced to allow the following:


shared int a;
Mutex mutex;

synchronized(tlsA; a, mutex)
{
// within this scope "tlsA" is the same as "a" but without
// the "shared" qualifier. "tlsA" is not allowed to escape the block
}

This could also be implemented as a library function, but that would 
require casting away shared inside the implementation:


guard(a, mutex, (tlsA){

});

void guard(T)(scope shared T value, Mutex mutex, scope void delegate 
(scope T) block)

{
mutex.lock_nothrow();
scope (exit)
mutex.unlock_nothrow();

block(cast(T) value);
}


--
/Jacob Carlborg


Re: shared - i need it to be useful

2018-10-16 Thread Kagamin via Digitalmars-d

On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
Current situation where you can arbitrarily access shared 
members

undermines any value it has.


The value of shared is existence of thread-local data that's 
guaranteed to be not shared, so you don't need to worry about 
thread-local data being shared, and shared protects that value 
well.



Assuming this world... how do you use shared?


Unique solution for each case.

If you write a lock-free queue for instance, and all the 
methods are
`shared` (ie, threadsafe), then under the current rules, you 
can't

interact with the object when it's not shared, and that's fairly
useless.


Create it as shared.

Assuming the rules above: "can't read or write to members", and 
the understanding that `shared` methods are expected to have 
threadsafe implementations (because that's the whole point), 
what are the risks from allowing T* -> shared(T)* conversion?


All data becomes possibly shared, so you can't assume it's 
unshared, effectively C-style sharing. BTW D supports the latter 
already.


Re: shared - i need it to be useful

2018-10-16 Thread Timon Gehr via Digitalmars-d

On 15.10.2018 20:46, Manu wrote:


Assuming the rules above: "can't read or write to members", and the
understanding that `shared` methods are expected to have threadsafe
implementations (because that's the whole point), what are the risks
from allowing T* -> shared(T)* conversion?



Unshared becomes useless, and in turn, shared becomes useless. You can't 
have unshared/shared aliasing.



All the risks that I think have been identified previously assume that
you can arbitrarily modify the data. That's insanity... assume we fix
that... I think the promotion actually becomes safe now...?


But useless, because there is no way to ensure thread safety of reads 
and writes if only one party to the shared state knows about the sharing.


Re: shared - i need it to be useful

2018-10-16 Thread Dominikus Dittes Scherkl via Digitalmars-d

On Tuesday, 16 October 2018 at 10:15:51 UTC, Timon Gehr wrote:

On 15.10.2018 20:46, Manu wrote:


Assuming the rules above: "can't read or write to members", 
and the
understanding that `shared` methods are expected to have 
threadsafe
implementations (because that's the whole point), what are the 
risks

from allowing T* -> shared(T)* conversion?



Unshared becomes useless, and in turn, shared becomes useless.

why is unshared useless?
Unshared means you can read an write to it.
If you give it to a function that expect something shared,
the function you had given it to can't read or write it, so it
can't do any harm.
Of course it can handle it threadsave, but as it is local,
that is only overhead - reading or changing the value can't do
any harm either. I like the idea.

But useless, because there is no way to ensure thread safety of 
reads and writes if only one party to the shared state knows 
about the sharing.

Of course there is. Giving an unshared value to a function that
even can handle shared values may create some overhead, but is
indeed threadsave.





Re: shared - i need it to be useful

2018-10-16 Thread Stanislav Blinov via Digitalmars-d

On Tuesday, 16 October 2018 at 03:00:21 UTC, Manu wrote:

I don't see how an *implicit* cast can be a restriction. At 
all.



Because a shared pointer can't access anything.
You can't do anything with a shared instance, so the can be no 
harm done.


That just doesn't compute. You obviously *can* do "anything" with 
a shared instance, per your own requirements all you need is 
methods. But at this point, the caller has an unshared reference, 
and your API possibliy stole away a shared one without the caller 
ever knowing of it happening.


It's like we're talking about wholly different things here. 
Casting should be done by the caller, i.e. a programmer that 
uses some API. If that API expects shared arguments, the 
caller better make sure they pass shared values. Implicit 
conversion destroys any obligations between the caller and the 
API.


Why? What could a function do with shared arguments?


How, exactly, do you propose getting shared state from one thread 
to another? Exclusively through globals?


The problem that I'm suggesting is exactly that: an `int*` is 
not, and can not, be a `shared int*` at the same time. 
Substitute int for any type. But D is not Rust and it can't 
statically prevent that, except for disallowing trivial 
programming mistakes, which, with implicit conversion 
introduced, would also go away.


Why not? The guy who receives the argument receives an argument 
that
*may be shared*, and as such, he's restricted access to it 
appropriately.


But the guy that *provides* that argument may have no idea of 
this happening. This is unshared aliasing.


module yourapi;

// My code does not know about this function at all
void giveToThread(shared int* ptr) { /* ... */ }

void yourAPI(int* ptr) {
giveToThread(ptr);
}

module mycode;

int x;

void main() {
yourAPI(&x);
}

Just like if you receive a const thing, you can't write to it, 
even if the caller's thing isn't const.


You do know why those Rust guys disallowed mutable aliasing? That 
is, having both mutable and immutable references at the same 
time. That's a long known problem that in C++ and D is only 
"solved" by programmer discipline and not much else.



If you receive a shared thing, you can't read or write to it.


You can, per your model, through methods. All the while the 
original is being read and written freely.


> a good chance they don't need it though, they might not  
> interact with a thread-unsafe portion of the class.


Or they might.


Then you will implement synchronisation, or have violated your 
thread-safety promise.


Such a good promise it is when it's simply ignored by an implicit 
cast.


Because that is exactly the code that a good amount of 
"developers" will write. Especially those of the "don't think 
about it" variety. Don't be mistaken for a second: if the 
language allows it, they'll write it.


This is not even an argument.
Atomic!int must be used with care. Any threading of ANY KIND 
must be

handled with care.


Implicit casts are in another galaxy as far as handling with care 
is concerned.


Saying we shouldn't make shared useful because someone can do 
something wrong is like saying we shouldn't have atomic int's 
and we shouldn't have spawn(). They're simply too dangerous to 
give to users...


We should make `shared` useful. We shouldn't allow unshared 
aliasing.


Can you actually provide an example of a mixed shared/unshared 
class that even makes sense then? As I said, at this point I'd 
rather see such definitions prohibited entirely.


I think this is a typical sort of construction:

struct ThreadsafeQueue(T)
{
  void QueueItem(T*) shared;
  T* UnqueueItem() shared;
}


That's an interface for a multi-producer multi-consumer, yet 
you're using it as a multi-producer single-consumer. Those would 
have very different implementations.



struct SpecialWorkList
{
  struct Job { ... }

  void MakeJob(int x, float y, string z) shared  // <- any 
thread may produce a job

  {
Job* job = new Job; // <- this is thread-local
PopulateJob(job, x, y, z); // <- preparation of a job might 
be complex, and worthy of the SpecialWorkList implementation


...except you can't call PopulateJob from MakeJob.

The two methods after compiler rewrite are (pseudocode):

void struct_method_SpecialWorkList_MakeJob(shared 
SpecialWorkList* this, int x, float y, string z);
void struct_method_SpecialWorkList_PopulateJob(SpecialWorkList* 
this, Job* job, ...);


Or are you also suggesting to allow implicitly demoting 
shared(T)* to T* ?!? Then you can just throw away `shared`.


jobList.QueueItem(job);  // <- QueueItem encapsulates 
thread-safety, no need for blunt casts


None were needed here regardless.


  void Flush() // <- not shared, thread-local consumer
  {


I.e. this can only be called by the thread that owns this 
instance, because only that thread can have an unshared reference 
to it.


  void GetSpecialSystemState() // <- this has NOTHING to do 
with the threadsafe part of Sp

Re: shared - i need it to be useful

2018-10-16 Thread Timon Gehr via Digitalmars-d

On 16.10.2018 13:04, Dominikus Dittes Scherkl wrote:

On Tuesday, 16 October 2018 at 10:15:51 UTC, Timon Gehr wrote:

On 15.10.2018 20:46, Manu wrote:


Assuming the rules above: "can't read or write to members", and the
understanding that `shared` methods are expected to have threadsafe
implementations (because that's the whole point), what are the risks
from allowing T* -> shared(T)* conversion?



Unshared becomes useless, and in turn, shared becomes useless.

why is unshared useless?
Unshared means you can read an write to it.
If you give it to a function that expect something shared,
the function you had given it to can't read or write it, so it
can't do any harm.


It can do harm to others who hold an unshared alias to the same data and 
are operating on it concurrently.



Of course it can handle it threadsave, but as it is local,
that is only overhead - reading or changing the value can't do
any harm either. I like the idea.

But useless, because there is no way to ensure thread safety of reads 
and writes if only one party to the shared state knows about the sharing.

Of course there is.


Please do enlighten me. You have two processors operating 
(reading/writing) on the same address space on a modern computer 
architecture with a weak memory model, and you are using an optimizing 
compiler. How do you ensure sensible results without cooperation from 
both of them? (Hint: you don't.)



Giving an unshared value to a function that
even can handle shared values may create some overhead, but is
indeed threadsave.



Yes, if you give it to one function only, that is the case. However, as 
you may know, concurrency means that there may be multiple functions 
operating on the data _at the same time_. If one of them operates on the 
data as if it was not shared, you will run into trouble.


You are arguing as if there was either no concurrency or no mutable 
aliasing.


Re: shared - i need it to be useful

2018-10-16 Thread Steven Schveighoffer via Digitalmars-d

On 10/15/18 2:46 PM, Manu wrote:

Okay, so I've been thinking on this for a while... I think I have a
pretty good feel for how shared is meant to be.

1. shared should behave exactly like const, except in addition to
inhibiting write access, it also inhibits read access.

I think this is the foundation for a useful definition for shared, and
it's REALLY easy to understand and explain.

Current situation where you can arbitrarily access shared members
undermines any value it has. Shared must assure you don't access
members unsafely, and the only way to do that with respect to data
members, is to inhibit access completely.
I think shared is just const without read access.

Assuming this world... how do you use shared?

1. traditional; assert that the object become thread-local by
acquiring a lock, cast shared away
2. object may have shared methods; such methods CAN be called on
shared instances. such methods may internally implement
synchronisation to perform their function. perhaps methods of a
lock-free queue structure for instance, or operator overloads on
`Atomic!int`, etc.

In practise, there is no functional change in usage from the current
implementation, except we disallow unsafe accesses (which will make
the thing useful).


From there, it opens up another critical opportunity; T* -> shared(T)*

promotion.
Const would be useless without T* -> const(T)* promotion. Shared
suffers a similar problem.
If you write a lock-free queue for instance, and all the methods are
`shared` (ie, threadsafe), then under the current rules, you can't
interact with the object when it's not shared, and that's fairly
useless.

Assuming the rules above: "can't read or write to members", and the
understanding that `shared` methods are expected to have threadsafe
implementations (because that's the whole point), what are the risks
from allowing T* -> shared(T)* conversion?

All the risks that I think have been identified previously assume that
you can arbitrarily modify the data. That's insanity... assume we fix
that... I think the promotion actually becomes safe now...?

Destroy...



This is a step in the right direction. But there is still one problem -- 
shared is inherently transitive.


So casting away shared is super-dangerous, even if you lock the shared 
data, because any of the subreferences will become unshared and 
read/writable.


For instance:

struct S
{
   int x;
   int *y;
}

shared int z;

auto s1 = shared(S)(1, &z);

auto s2 = shared(S)(2, &z);

S* s1locked = s1.lock;

Now I have access to z via s1locked as an unshared int, and I never 
locked z. Potentially one could do the same thing via s2, and now there 
are 2 mutable references, potentially in 2 threads.


All of this, of course, is manual. So technically we could manually 
implement it properly inside S. But this means shared doesn't help us much.


We really need on top of shared, a way to specify something is 
tail-shared. That is, all the data in S is unshared, but anything it 
points to is still shared. That at least helps the person implementing 
the manual locking from doing stupid things himself.


-Steve


Re: shared - i need it to be useful

2018-10-16 Thread Steven Schveighoffer via Digitalmars-d

On 10/16/18 9:25 AM, Steven Schveighoffer wrote:

On 10/15/18 2:46 PM, Manu wrote:



From there, it opens up another critical opportunity; T* -> shared(T)*

promotion.
Const would be useless without T* -> const(T)* promotion. Shared
suffers a similar problem.
If you write a lock-free queue for instance, and all the methods are
`shared` (ie, threadsafe), then under the current rules, you can't
interact with the object when it's not shared, and that's fairly
useless.



Oh, I didn't see this part. Completely agree with Timon on this, no 
implicit conversions should be allowed.


If you want to have a lock-free implementation of something, you can 
abstract the assignments and reads behind the proper mechanisms anyway, 
and still avoid locking (casting is not locking).


-Steve


Re: shared - i need it to be useful

2018-10-16 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 2:25 AM Kagamin via Digitalmars-d
 wrote:
>
> On Monday, 15 October 2018 at 18:46:45 UTC, Manu wrote:
> > Current situation where you can arbitrarily access shared
> > members
> > undermines any value it has.
>
> The value of shared is existence of thread-local data that's
> guaranteed to be not shared, so you don't need to worry about
> thread-local data being shared, and shared protects that value
> well.

This isn't really an argument in favour of shared being able to
arbitrarily access all its members though. I'm not sure how your
points addresses my claim?

this sounds like that old quote "there can not be peace without war".
It's not shared that makes not-shared thread-local by default, it's
just that's how D is defined.
Shared allows you to break that core assumption. Without shared, it
would still be thread-local by default... you just wouldn't have an
escape hatch ;)

> > Assuming this world... how do you use shared?
>
> Unique solution for each case.
>
> > If you write a lock-free queue for instance, and all the
> > methods are
> > `shared` (ie, threadsafe), then under the current rules, you
> > can't
> > interact with the object when it's not shared, and that's fairly
> > useless.
>
> Create it as shared.

Then you can't use it locally.

> > Assuming the rules above: "can't read or write to members", and
> > the understanding that `shared` methods are expected to have
> > threadsafe implementations (because that's the whole point),
> > what are the risks from allowing T* -> shared(T)* conversion?
>
> All data becomes possibly shared, so you can't assume it's
> unshared, effectively C-style sharing. BTW D supports the latter
> already.

No data becomes 'possibly shared', because it's all inaccessible.
Only if your object specifies a threadsafe API may some controlled
data become shared... and that's the whole point of writing a
threadsafe object.
If the threadsafe object doesn't support sharing its own data, then
it's not really a threadsafe object.


Re: shared - i need it to be useful

2018-10-16 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 3:20 AM Timon Gehr via Digitalmars-d
 wrote:
>
> On 15.10.2018 20:46, Manu wrote:
> >
> > Assuming the rules above: "can't read or write to members", and the
> > understanding that `shared` methods are expected to have threadsafe
> > implementations (because that's the whole point), what are the risks
> > from allowing T* -> shared(T)* conversion?
> >
>
> Unshared becomes useless, and in turn, shared becomes useless. You can't
> have unshared/shared aliasing.

What aliasing? Please show a reasonable and likely construction of the
problem. I've been trying to think of it.

> > All the risks that I think have been identified previously assume that
> > you can arbitrarily modify the data. That's insanity... assume we fix
> > that... I think the promotion actually becomes safe now...?
>
> But useless, because there is no way to ensure thread safety of reads
> and writes if only one party to the shared state knows about the sharing.

What? I don't understand this sentence.

If a shared method is not threadsafe, then it's an implementation
error. A user should expect that a shared method is threadsafe,
otherwise it shouldn't be a shared method! Thread-local (ie, normal)
methods are for not-threadsafe functionality.


Re: shared - i need it to be useful

2018-10-16 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 6:25 AM Timon Gehr via Digitalmars-d
 wrote:
>
> On 16.10.2018 13:04, Dominikus Dittes Scherkl wrote:
> > On Tuesday, 16 October 2018 at 10:15:51 UTC, Timon Gehr wrote:
> >> On 15.10.2018 20:46, Manu wrote:
> >>>
> >>> Assuming the rules above: "can't read or write to members", and the
> >>> understanding that `shared` methods are expected to have threadsafe
> >>> implementations (because that's the whole point), what are the risks
> >>> from allowing T* -> shared(T)* conversion?
> >>>
> >>
> >> Unshared becomes useless, and in turn, shared becomes useless.
> > why is unshared useless?
> > Unshared means you can read an write to it.
> > If you give it to a function that expect something shared,
> > the function you had given it to can't read or write it, so it
> > can't do any harm.
>
> It can do harm to others who hold an unshared alias to the same data and
> are operating on it concurrently.

Nobody else holds an unshared alias.
If you pass a value as const, you don't fear that it will become mutable.

> > Of course it can handle it threadsave, but as it is local,
> > that is only overhead - reading or changing the value can't do
> > any harm either. I like the idea.
> >
> >> But useless, because there is no way to ensure thread safety of reads
> >> and writes if only one party to the shared state knows about the sharing.
> > Of course there is.
>
> Please do enlighten me. You have two processors operating
> (reading/writing) on the same address space on a modern computer
> architecture with a weak memory model, and you are using an optimizing
> compiler. How do you ensure sensible results without cooperation from
> both of them? (Hint: you don't.)

What? This is a weird statement.
So, you're saying that nobody has successfully written any threadsafe
code, ever... we should stop trying, and we should admit that
threadsafe queues and atomics, and mutexes and stuff all don't exist?

> without cooperation from both of them?

Perhaps this is the key to your statement?
Yes. 'cooperation from both of them' in this case means, they are both
interacting with a threadsafe api, and they are blocked from accessing
members, or any non-threadsafe api.

> > Giving an unshared value to a function that
> > even can handle shared values may create some overhead, but is
> > indeed threadsave.
> >
>
> Yes, if you give it to one function only, that is the case. However, as
> you may know, concurrency means that there may be multiple functions
> operating on the data _at the same time_. If one of them operates on the
> data as if it was not shared, you will run into trouble.

Who's doing this, and how?

> You are arguing as if there was either no concurrency or no mutable
> aliasing.

If a class has no shared methods, there's no possibility for mutable aliasing.
If the class has shared methods, then the class was carefully designed
to be threadsafe.


Re: shared - i need it to be useful

2018-10-16 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 6:35 AM Steven Schveighoffer via Digitalmars-d
 wrote:
>
> On 10/16/18 9:25 AM, Steven Schveighoffer wrote:
> > On 10/15/18 2:46 PM, Manu wrote:
>
> >>> From there, it opens up another critical opportunity; T* -> shared(T)*
> >> promotion.
> >> Const would be useless without T* -> const(T)* promotion. Shared
> >> suffers a similar problem.
> >> If you write a lock-free queue for instance, and all the methods are
> >> `shared` (ie, threadsafe), then under the current rules, you can't
> >> interact with the object when it's not shared, and that's fairly
> >> useless.
> >>
>
> Oh, I didn't see this part. Completely agree with Timon on this, no
> implicit conversions should be allowed.

Why?

> If you want to have a lock-free implementation of something, you can
> abstract the assignments and reads behind the proper mechanisms anyway,
> and still avoid locking (casting is not locking).

Sorry, I don't understand what you're saying. Can you clarify?


Re: shared - i need it to be useful

2018-10-16 Thread Steven Schveighoffer via Digitalmars-d

On 10/16/18 2:10 PM, Manu wrote:

On Tue, Oct 16, 2018 at 6:35 AM Steven Schveighoffer via Digitalmars-d
 wrote:


On 10/16/18 9:25 AM, Steven Schveighoffer wrote:

On 10/15/18 2:46 PM, Manu wrote:



 From there, it opens up another critical opportunity; T* -> shared(T)*

promotion.
Const would be useless without T* -> const(T)* promotion. Shared
suffers a similar problem.
If you write a lock-free queue for instance, and all the methods are
`shared` (ie, threadsafe), then under the current rules, you can't
interact with the object when it's not shared, and that's fairly
useless.



Oh, I didn't see this part. Completely agree with Timon on this, no
implicit conversions should be allowed.


Why?


int x;

shared int *p = &x; // allow implicit conversion, currently error

passToOtherThread(p);

useHeavily(&x);

How is this safe? Thread1 is using x without locking, while the other 
thread has to lock. In order for synchronization to work, both sides 
have to agree on a synchronization technique and abide by it.



If you want to have a lock-free implementation of something, you can
abstract the assignments and reads behind the proper mechanisms anyway,
and still avoid locking (casting is not locking).


Sorry, I don't understand what you're saying. Can you clarify?



I'd still mark a lock-free implementation shared, and all its methods 
shared. shared does not mean you have to lock, just cast away shared. A 
lock-free container still has to do some special things to make sure it 
avoids races, and having an "unusable" state aids in enforcing this.


-Steve


Re: shared - i need it to be useful

2018-10-16 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 11:30 AM Steven Schveighoffer via
Digitalmars-d  wrote:
>
> On 10/16/18 2:10 PM, Manu wrote:
> > On Tue, Oct 16, 2018 at 6:35 AM Steven Schveighoffer via Digitalmars-d
> >  wrote:
> >>
> >> On 10/16/18 9:25 AM, Steven Schveighoffer wrote:
> >>> On 10/15/18 2:46 PM, Manu wrote:
> >>
> >  From there, it opens up another critical opportunity; T* -> shared(T)*
>  promotion.
>  Const would be useless without T* -> const(T)* promotion. Shared
>  suffers a similar problem.
>  If you write a lock-free queue for instance, and all the methods are
>  `shared` (ie, threadsafe), then under the current rules, you can't
>  interact with the object when it's not shared, and that's fairly
>  useless.
> 
> >>
> >> Oh, I didn't see this part. Completely agree with Timon on this, no
> >> implicit conversions should be allowed.
> >
> > Why?
>
> int x;
>
> shared int *p = &x; // allow implicit conversion, currently error
>
> passToOtherThread(p);
>
> useHeavily(&x);

What does this mean? It can't do anything... that's the whole point here.
I think I'm struggling here with people bringing presumptions to the
thread. You need to assume the rules I define in the OP for the
experiment to work.

> How is this safe?

Because useHeavily() can't read or write to x.

> Thread1 is using x without locking, while the other
> thread has to lock. In order for synchronization to work, both sides
> have to agree on a synchronization technique and abide by it.

Only the owning thread can access x, the shared instance can't access x at all.

If some code somewhere decides it wants to cast-away shared, then it
needs to determine ownership via some other means. It needs to be
confident that the original owner yielded ownership, and any API that
leads to this behaviour would need to be designed in such a way to
encourage correct behaviour.
That's *exactly* how it is now... I haven't changed anything from this
perspective.

> >> If you want to have a lock-free implementation of something, you can
> >> abstract the assignments and reads behind the proper mechanisms anyway,
> >> and still avoid locking (casting is not locking).
> >
> > Sorry, I don't understand what you're saying. Can you clarify?
> >
>
> I'd still mark a lock-free implementation shared, and all its methods
> shared. shared does not mean you have to lock, just cast away shared.

Shared *should* mean that the function is threadsafe, and you are safe
to call it from a shared instance.
If a function is not shared, then you MUST cast away shared, and that
implies that you need to use external means to create a context where
you have thread-local ownership of the instance (usually with a
mutex).

You shouldn't need to cast-away shared to make a safe function call.
By casting away shared, you also gain access to all the non-threadsafe
methods and members.
Casting shared away to call a shared method makes a safe access into a
potentially unsafe access.

It's unacceptable to case-away shared. It should be as unacceptable as
casting const away. The only situation where it's okay is where you're
externally verifying thread-locality by external means, and that's
subject to your broader systemic design.

> A lock-free container still has to do some special things to make sure it
> avoids races, and having an "unusable" state aids in enforcing this.

Can you explain how my proposal doesn't model this very neatly?


Re: shared - i need it to be useful

2018-10-16 Thread Steven Schveighoffer via Digitalmars-d

On 10/16/18 4:26 PM, Manu wrote:

On Tue, Oct 16, 2018 at 11:30 AM Steven Schveighoffer via
Digitalmars-d  wrote:


On 10/16/18 2:10 PM, Manu wrote:

On Tue, Oct 16, 2018 at 6:35 AM Steven Schveighoffer via Digitalmars-d
 wrote:


On 10/16/18 9:25 AM, Steven Schveighoffer wrote:

On 10/15/18 2:46 PM, Manu wrote:



  From there, it opens up another critical opportunity; T* -> shared(T)*

promotion.
Const would be useless without T* -> const(T)* promotion. Shared
suffers a similar problem.
If you write a lock-free queue for instance, and all the methods are
`shared` (ie, threadsafe), then under the current rules, you can't
interact with the object when it's not shared, and that's fairly
useless.



Oh, I didn't see this part. Completely agree with Timon on this, no
implicit conversions should be allowed.


Why?


int x;

shared int *p = &x; // allow implicit conversion, currently error

passToOtherThread(p);

useHeavily(&x);


What does this mean? It can't do anything... that's the whole point here.
I think I'm struggling here with people bringing presumptions to the
thread. You need to assume the rules I define in the OP for the
experiment to work.


OK, I wrote a whole big response to this, and I went and re-quoted the 
above, and now I think I understand what the point of your statement is.


I'll first say that if you don't want to allow implicit casting of 
shared to mutable, then you can't allow implicit casting from mutable to 
shared. Because it's mutable, races can happen.


There is in fact, no difference between:

int *p;
shared int *p2 = p;
int *p3 = cast(int*)p2;

and this:

int *p;
shared int *p2 = p;
int *p3 = p;

So really, the effort to prevent the reverse cast is defeated by 
allowing the implicit cast.


There is a reason we disallow assigning from mutable to immutable 
without a cast. Yet, it is done in many cases, because you are sometimes 
building an immutable object with mutable pieces, and want to cast the 
final result.


In this case, it's ON YOU to make sure it's correct, and the traditional 
mechanism for the compiler giving you the responsibility is to require a 
cast.


-

OK, so here is where I think I misunderstood your point. When you said a 
lock-free queue would be unusable if it wasn't shared, I thought you 
meant it would be unusable if we didn't allow the implicit cast. But I 
realize now, you meant you should be able to use a lock-free queue 
without it being actually shared anywhere.


What I say to this is that it doesn't need to be usable. I don't care to 
use a lock-free queue in a thread-local capacity. I'll just use a normal 
queue, which is easy to implement, and doesn't have to worry about race 
conditions or using atomics. A lock free queue is a special thing, very 
difficult to get right, and only really necessary if you are going to 
share it. And used for performance reasons!


Why would I want to incur performance penalties when using a lock-free 
queue in an unshared mode? I would actually expect 2 separate 
implementations of the primitives, one for shared one for unshared.


What about primitives that would be implemented the same? In that case, 
the shared method becomes:


auto method() { return (cast(Queue*)&this).method; }

Is this "unusable"? Without a way to say, you can call this on shared or 
unshared instances, then we need to do it this way.


But I would trust the queue to handle this properly depending on whether 
it was typed shared or not.


-Steve


Re: shared - i need it to be useful

2018-10-16 Thread Nicholas Wilson via Digitalmars-d
On Tuesday, 16 October 2018 at 21:19:26 UTC, Steven Schveighoffer 
wrote:
OK, so here is where I think I misunderstood your point. When 
you said a lock-free queue would be unusable if it wasn't 
shared, I thought you meant it would be unusable if we didn't 
allow the implicit cast. But I realize now, you meant you 
should be able to use a lock-free queue without it being 
actually shared anywhere.


What I say to this is that it doesn't need to be usable. I 
don't care to use a lock-free queue in a thread-local capacity. 
I'll just use a normal queue, which is easy to implement, and 
doesn't have to worry about race conditions or using atomics. A 
lock free queue is a special thing, very difficult to get 
right, and only really necessary if you are going to share it. 
And used for performance reasons!


I think this comes up where the queue was originally shared, you 
acquired a lock on the thing it is a member of, and you want to 
continue using it through your exclusive reference.




Re: shared - i need it to be useful

2018-10-16 Thread Nicholas Wilson via Digitalmars-d
On Tuesday, 16 October 2018 at 21:19:26 UTC, Steven Schveighoffer 
wrote:

There is in fact, no difference between:

int *p;
shared int *p2 = p;
int *p3 = cast(int*)p2;

and this:

int *p;
shared int *p2 = p;
int *p3 = p;


If I understand Manu correctly the first should compile, and the 
second should error, just like if you replaces shared with const 
in the above.




Re: shared - i need it to be useful

2018-10-16 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 2:20 PM Steven Schveighoffer via Digitalmars-d
 wrote:
>
> On 10/16/18 4:26 PM, Manu wrote:
> > On Tue, Oct 16, 2018 at 11:30 AM Steven Schveighoffer via
> > Digitalmars-d  wrote:
> >>
> >> On 10/16/18 2:10 PM, Manu wrote:
> >>> On Tue, Oct 16, 2018 at 6:35 AM Steven Schveighoffer via Digitalmars-d
> >>>  wrote:
> 
>  On 10/16/18 9:25 AM, Steven Schveighoffer wrote:
> > On 10/15/18 2:46 PM, Manu wrote:
> 
> >>>   From there, it opens up another critical opportunity; T* -> 
> >>> shared(T)*
> >> promotion.
> >> Const would be useless without T* -> const(T)* promotion. Shared
> >> suffers a similar problem.
> >> If you write a lock-free queue for instance, and all the methods are
> >> `shared` (ie, threadsafe), then under the current rules, you can't
> >> interact with the object when it's not shared, and that's fairly
> >> useless.
> >>
> 
>  Oh, I didn't see this part. Completely agree with Timon on this, no
>  implicit conversions should be allowed.
> >>>
> >>> Why?
> >>
> >> int x;
> >>
> >> shared int *p = &x; // allow implicit conversion, currently error
> >>
> >> passToOtherThread(p);
> >>
> >> useHeavily(&x);
> >
> > What does this mean? It can't do anything... that's the whole point here.
> > I think I'm struggling here with people bringing presumptions to the
> > thread. You need to assume the rules I define in the OP for the
> > experiment to work.
>
> OK, I wrote a whole big response to this, and I went and re-quoted the
> above, and now I think I understand what the point of your statement is.
>
> I'll first say that if you don't want to allow implicit casting of
> shared to mutable,

It's critical that this is not allowed. It's totally unreasonable to
cast from shared to thread-local without synchronisation.
It's as bad as casting away const.

> then you can't allow implicit casting from mutable to
> shared. Because it's mutable, races can happen.

I don't follow...

> There is in fact, no difference between:
>
> int *p;
> shared int *p2 = p;
> int *p3 = cast(int*)p2;

Totally illegal!! You casted away shared. That's as bad as casting away const.

> and this:
>
> int *p;
> shared int *p2 = p;
> int *p3 = p;

There's nothing wrong with this... I don't understand the point?

> So really, the effort to prevent the reverse cast is defeated by
> allowing the implicit cast.

Only the caller has the thread-local instance. You can take a
thread-local pointer to a thread-local within the context of a single
thread.
So, it's perfectly valid for `p` and `p3` to exist in a single scope.
`p2` is fine here too... and if that shared pointer were to escape to
another thread, it wouldn't be a threat, because it's not readable or
writable, and you can't make it back into a thread-local pointer
without carefully/deliberately deployed machinery.

> There is a reason we disallow assigning from mutable to immutable
> without a cast. Yet, it is done in many cases, because you are sometimes
> building an immutable object with mutable pieces, and want to cast the
> final result.

I don't think analogy to immutable has a place in this discussion, or
at least, I don't understand the relevance...
I think the reasonable analogy is const.

> In this case, it's ON YOU to make sure it's correct, and the traditional
> mechanism for the compiler giving you the responsibility is to require a
> cast.

I think what you're talking about are behaviours relating to casting
shared *away*, and that's some next-level shit. Handling in that case
is no different to the way it exists today. You must guarantee that
the pointer you possess becomes thread-local before casting it to a
thread-local pointer.
In my application framework, I will never cast shared away under my
proposed design. We don't have any such global locks.

> -
>
> OK, so here is where I think I misunderstood your point. When you said a
> lock-free queue would be unusable if it wasn't shared, I thought you
> meant it would be unusable if we didn't allow the implicit cast. But I
> realize now, you meant you should be able to use a lock-free queue
> without it being actually shared anywhere.

Right, a lock-free queue is a threadsafe object, and it's methods work
whether the queue is shared or not.
The methods are attributed shared because they can be called on shared
instances... but they can ALSO be called from a thread-local instance,
and under my suggested promotion rules, it's fine for the this-pointer
to promote to shared to make the call.

> What I say to this is that it doesn't need to be usable. I don't care to
> use a lock-free queue in a thread-local capacity. I'll just use a normal
> queue, which is easy to implement, and doesn't have to worry about race
> conditions or using atomics. A lock free queue is a special thing, very
> difficult to get right, and only really necessary if you are going to
> share it. And used for performance reasons!

I'm more interested in the 

Re: shared - i need it to be useful

2018-10-16 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 3:25 PM Nicholas Wilson via Digitalmars-d
 wrote:
>
> On Tuesday, 16 October 2018 at 21:19:26 UTC, Steven Schveighoffer
> wrote:
> > There is in fact, no difference between:
> >
> > int *p;
> > shared int *p2 = p;
> > int *p3 = cast(int*)p2;
> >
> > and this:
> >
> > int *p;
> > shared int *p2 = p;
> > int *p3 = p;
>
> If I understand Manu correctly the first should compile, and the
> second should error, just like if you replaces shared with const
> in the above.

Why is the second an error?

Right, for the sake of the thought experiment, replace shared with const.
The first is an abomination (casting away const)... the second is just a no-op.


Re: shared - i need it to be useful

2018-10-16 Thread Nicholas Wilson via Digitalmars-d

On Wednesday, 17 October 2018 at 00:29:04 UTC, Manu wrote:
On Tue, Oct 16, 2018 at 3:25 PM Nicholas Wilson via 
Digitalmars-d  wrote:


On Tuesday, 16 October 2018 at 21:19:26 UTC, Steven 
Schveighoffer wrote:

> There is in fact, no difference between:
>
> int *p;
> shared int *p2 = p;
> int *p3 = cast(int*)p2;
>
> and this:
>
> int *p;
> shared int *p2 = p;
> int *p3 = p;

If I understand Manu correctly the first should compile, and 
the second should error, just like if you replaces shared with 
const in the above.


Why is the second an error?

Right, for the sake of the thought experiment, replace shared 
with const.
The first is an abomination (casting away const)... the second 
is just a no-op.


I missed that the third example was *p3 = p; not *p3 = p2;


Re: shared - i need it to be useful

2018-10-16 Thread Isaac S. via Digitalmars-d

On Tuesday, 16 October 2018 at 06:21:22 UTC, Manu wrote:
On Mon, Oct 15, 2018 at 8:55 PM Isaac S. via Digitalmars-d 
 wrote:


On Tuesday, 16 October 2018 at 02:26:04 UTC, Manu wrote:
>> I understand your point but I think the current shared (no 
>> implicit conversion) has its uses.

>> *snip*
>
> If you can give a single 'use', I'm all ears ;)

My usages are a custom ref counted template and list types 
(which are built on top of the former). The ref counted 
template will initialize naively when not shared but utilize 
compare-and-set and thread yielding if needed when shared 
(ensureInitialized can occur any time after declaration). The 
list types (which are not shared-compatible *yet*) will 
utilize a read-write mutex only when shared (no cost beyond a 
little unused memory to non-shared objects). As such, casting 
any of the non-shared versions of these types to shared would 
be unsafe.


(Technically the types can be safely casted to shared so long 
as no non-shared reference to them exists and vice versa.)


Can you link to code? It doesn't sound incompatible with my 
proposal. Mutexes are blunt instruments, and easy to model. My 
whole suggestion has virtually no effect on the mutex protected 
case, which is what most people seem to talk about.


On Wednesday, 17 October 2018 at 00:26:58 UTC, Manu wrote:

*snip*

Overloading for shared and unshared is possible, and may be 
desirable in many cases.
There are also many cases where the code duplication and 
tech-debt
does not carry its weight. It should not be required, because 
it's not technically required.


Overloading for shared and unshared is my reason for not allowing 
implicit conversion on my types (I have no problems with implicit 
conversion being optional or disableable). The unshared function 
performs no synchronization of any kind while the shared function 
always does. This means that having an unshared and shared 
reference to the same object is unsafe.


As for an actual link to code, I can really only link to my ref 
counted template
as I haven't gotten around to making the containers 
shared-compatible yet. The main point of interest is at lines 
103-131:


https://github.com/isaacs-dev/Familiar/blob/66f1a94fc099601465e755d40a2c68bf4200cabd/containers/familiar/containers/safe_ref_counted.d#L103

The unshared ensureInitialized() doesn't worry about threads 
simultaneously calling it. The shared version of it uses 
compare-and-set to prevent two threads from initializing it at 
the same time. An unsafe example would require a shared 
reference/pointer to an unshared SafeRefCounted (which would be 
fairly weird to do) and both calling ensureInitialized. While an 
example using the ref counted template is fairly contrived, a 
list-type that only uses a read-write mutex when it's shared 
isn't.



As to the larger part of preventing reading/writing of shared 
objects, I agree with its purpose and have no problems with it.


Re: shared - i need it to be useful

2018-10-16 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 8:20 PM Isaac S. via Digitalmars-d
 wrote:
>
> On Tuesday, 16 October 2018 at 06:21:22 UTC, Manu wrote:
> > On Mon, Oct 15, 2018 at 8:55 PM Isaac S. via Digitalmars-d
> >  wrote:
> >>
> >> On Tuesday, 16 October 2018 at 02:26:04 UTC, Manu wrote:
> >> >> I understand your point but I think the current shared (no
> >> >> implicit conversion) has its uses.
> >> >> *snip*
> >> >
> >> > If you can give a single 'use', I'm all ears ;)
> >>
> >> My usages are a custom ref counted template and list types
> >> (which are built on top of the former). The ref counted
> >> template will initialize naively when not shared but utilize
> >> compare-and-set and thread yielding if needed when shared
> >> (ensureInitialized can occur any time after declaration). The
> >> list types (which are not shared-compatible *yet*) will
> >> utilize a read-write mutex only when shared (no cost beyond a
> >> little unused memory to non-shared objects). As such, casting
> >> any of the non-shared versions of these types to shared would
> >> be unsafe.
> >>
> >> (Technically the types can be safely casted to shared so long
> >> as no non-shared reference to them exists and vice versa.)
> >
> > Can you link to code? It doesn't sound incompatible with my
> > proposal. Mutexes are blunt instruments, and easy to model. My
> > whole suggestion has virtually no effect on the mutex protected
> > case, which is what most people seem to talk about.
>
> On Wednesday, 17 October 2018 at 00:26:58 UTC, Manu wrote:
> > *snip*
> >
> > Overloading for shared and unshared is possible, and may be
> > desirable in many cases.
> > There are also many cases where the code duplication and
> > tech-debt
> > does not carry its weight. It should not be required, because
> > it's not technically required.
>
> Overloading for shared and unshared is my reason for not allowing
> implicit conversion on my types (I have no problems with implicit
> conversion being optional or disableable). The unshared function
> performs no synchronization of any kind while the shared function
> always does. This means that having an unshared and shared
> reference to the same object is unsafe.

Okay, so just to be clear, you're objecting to an immensely useful
behaviour because you exploit the current design as an optimisation
potential?
That said, I'm still not taking anything away from you... you can
still implement your class that way if you like, no change will affect
that decision.

You obviously have mechanisms in place to guarantee the
thread-local-ness of your object (otherwise it wouldn't work), so,
assuming you implement the unshared overload to be unsafe, then
situation hasn't changed for you at all...?
I don't think a robust middleware would make that trade-off, but an
application can do that if it wishes. Trading implicit safety
(enforcing it externally via application context) for perf is not
unusual at all.

> As to the larger part of preventing reading/writing of shared
> objects, I agree with its purpose and have no problems with it.

Right, the implicit cast thing is secondary to this fundamental part.
I think it's worth first focusing on getting that rule right.
shared = no read + no write .. I don't think that's objectionable.


Re: shared - i need it to be useful

2018-10-16 Thread Isaac S. via Digitalmars-d

On Wednesday, 17 October 2018 at 03:50:44 UTC, Manu wrote:
On Tue, Oct 16, 2018 at 8:20 PM Isaac S. via Digitalmars-d 
 wrote:

*snip*

Overloading for shared and unshared is my reason for not 
allowing implicit conversion on my types (I have no problems 
with implicit conversion being optional or disableable). The 
unshared function performs no synchronization of any kind 
while the shared function always does. This means that having 
an unshared and shared reference to the same object is unsafe.


Okay, so just to be clear, you're objecting to an immensely 
useful
behaviour because you exploit the current design as an 
optimisation potential?
That said, I'm still not taking anything away from you... you 
can still implement your class that way if you like, no change 
will affect that decision.


You obviously have mechanisms in place to guarantee the
thread-local-ness of your object (otherwise it wouldn't work), 
so, assuming you implement the unshared overload to be unsafe, 
then

situation hasn't changed for you at all...?
I don't think a robust middleware would make that trade-off, 
but an application can do that if it wishes. Trading implicit 
safety
(enforcing it externally via application context) for perf is 
not unusual at all.


I'm not objecting to the behavior (I actually really want it in 
the language as it allows for some really useful designs) but 
more-so saying it would be nice if it could be enabled or 
disabled. If its enabled-by-default, the @disable attribute could 
be used somehow (maybe "@disable alias this shared;").


Even without the ability to enable/disable shared implicit 
conversion it won't break my code, it'll just allow a potential 
bug. Since it does allow for extremely useful designs, I'll 
support this change (*even if it can't be enabled/disabled*).


Re: shared - i need it to be useful

2018-10-16 Thread Walter Bright via Digitalmars-d

On 10/15/2018 11:46 AM, Manu wrote:

[...]


Shared has one incredibly valuable feature - it allows you, the programmer, to 
identify data that can be accessed by multiple threads. There are so many ways 
that data can be shared, the only way to comprehend what is going on is to build 
a wall around shared data.


(The exception to this is immutable data. Immutable data does not need 
synchronization, so there is no need to distinguish between shared and unshared 
immutable data.)


This falls completely apart if shared and unshared data can be aliased. When 
Andrei and I came up with the rules for:


   mutable
   const
   shared
   const shared
   immutable

and which can be implicitly converted to what, so far nobody has found a fault 
in those rules. Timon Gehr has done a good job showing that they still stand 
unbreached.


So, how can mutable data become shared, and vice versa? I've never found a way 
to do that that is proveably safe. Therefore, it's up to the programmer.


If a programmer does the following:

T* p;
shared(T)* sp;

p = cast(T*)sp;   (1)
sp = cast(shared(T)*) p;  (2)

those casts will be rejected in @safe code. They'll have to be in @trusted or 
@system code. It will be up to the programmer to ensure (via mutexes, locks, 
uniqueness, etc.) that:


(1) sp is a unique pointer and that ownership of the data is thus 
transferred for the lifetime of p


(2) p is a unique pointer and that ownership of the data is thus 
transferred for the lifetime of sp


A sensible program should try to minimize the places where there are these 
"gates" through the wall, as those gates will be where you'll be looking when 
threading bugs appear.


Allowing implicit aliasing between shared and unshared data means the entire 
program needs to reviewed for threading bugs, rather than just the shared parts. 
One might as well just not have shared as a language feature at all. D being a 
systems programming language, one can certainly write code that way, just like 
one does with C, and with all of the threading bugs one gets doing that.


--

Currently, the compiler allows you to read/write shared data without any locks 
or atomics. I.e. the compiler doesn't attempt to insert any synchronization on 
your behalf. Given all the ways synchronization can be done, it just seems 
presumptive and restrictive to impose a particular scheme. It's pretty much left 
up to the user.


Which is why I recommend minimizing the amount of code that actually has to deal 
with shared data.


Re: shared - i need it to be useful

2018-10-17 Thread Manu via Digitalmars-d
On Tue, Oct 16, 2018 at 10:45 PM Walter Bright via Digitalmars-d
 wrote:
>
> On 10/15/2018 11:46 AM, Manu wrote:
> > [...]
>
> Shared has one incredibly valuable feature - it allows you, the programmer, to
> identify data that can be accessed by multiple threads. There are so many ways
> that data can be shared, the only way to comprehend what is going on is to 
> build
> a wall around shared data.
>
> (The exception to this is immutable data. Immutable data does not need
> synchronization, so there is no need to distinguish between shared and 
> unshared
> immutable data.)
>
> This falls completely apart if shared and unshared data can be aliased.

What does it mean 'aliased' precisely? I'm trying to prevent that in
practical terms.
If you can't read/write to a shared object... is it actually aliased?
Or do you just have a fancy int that happens to look like a pointer?

> When Andrei and I came up with the rules for:
>
> mutable
> const
> shared
> const shared
> immutable
>
> and which can be implicitly converted to what, so far nobody has found a fault
> in those rules.

Oh bollocks... everyone has been complaining about this for at least
the 10 years I've been here!
Shared is uninteresting and mostly useless as spec-ed, everyone knows this.
Interaction with shared via barely-controlled blunt casting in
@trusted blocks is feeble and boring. It doesn't really give us
anything in practice that we don't have in C++. It's an uninspired
design.

I am working against a design where *everything* may be shared, and
it's a very interesting design space. I have no doubt it represents
the future of my field.
We are limited by what the type system can express on this front. I'm
trying to explore opportunities to make shared interesting and
useful... and I'm mostly being met with prejudice here.
Eject what you think you know about shared from your brain, and try
and take a fresh look from the perspective I'm proposing.
I think you'll find that the current wisdom and interactions with
shared are actually preserved in practice, but new opportunities
become available. If not, I want to understand how the design is
broken, so I can continue to iterate.

If D offered a real advantage here over C++, this would really
represent a strong attracting force.

> Timon Gehr has done a good job showing that they still stand
> unbreached.

His last comment was applied to a different proposal.
His only comment on this thread wasn't in response to the proposal in
this thread.
If you nominate Timon as your proxy, then he needs to destroy my
proposal, or at least comment on it, rather than make some prejudiced
comment generally.

> So, how can mutable data become shared, and vice versa? I've never found a way
> to do that that is proveably safe. Therefore, it's up to the programmer.

I'm proposing a way, and that is:
1. the rule must be applied that shared object can not be read or written
2. attributing a method shared is a statement and a promise that the
method is threadsafe

The rest just follows naturally.

> If a programmer does the following:
>
>  T* p;
>  shared(T)* sp;
>
>  p = cast(T*)sp;   (1)
>  sp = cast(shared(T)*) p;  (2)
>
> those casts will be rejected in @safe code. They'll have to be in @trusted or
> @system code. It will be up to the programmer to ensure (via mutexes, locks,
> uniqueness, etc.) that:
>
>  (1) sp is a unique pointer and that ownership of the data is thus
> transferred for the lifetime of p
>
>  (2) p is a unique pointer and that ownership of the data is thus
> transferred for the lifetime of sp

'Transfer' is a nice idea, but it's not what's happening here. That's
not what these operations do.
Transferring ownership is a job for move semantics, and that's nowhere in sight.

Also, there's no need to 'transfer' a this pointer to shared to call a
threadsafe method. You can always just call it safely.

> A sensible program should try to minimize the places where there are these
> "gates" through the wall, as those gates will be where you'll be looking when
> threading bugs appear.

I agree, that's my goal here. Shared right now is an unsafe mess; I
want to apply aggressive restriction so that you can't do unsafe
operations without explicit casts.

> Allowing implicit aliasing between shared and unshared data means the entire
> program needs to reviewed for threading bugs, rather than just the shared 
> parts.

That's backwards, I'm suggesting conversion TO shared, such that you
are able to call threadsafe methods on thread-local things. There's no
possible harm in that.
I'm trying to eliminate aliasing by removing read/write access.
The result is, the only thing you are able to do with a shared
reference are explicitly threadsafe things. Any other operation is
inaccessible without deliberate blunt casting, and the rules
surrounding that are the same as always; those that you listed above.

> One might as well just not have shared as a language feature a

Re: shared - i need it to be useful

2018-10-17 Thread Stanislav Blinov via Digitalmars-d
On Wednesday, 17 October 2018 at 05:40:41 UTC, Walter Bright 
wrote:



When Andrei and I came up with the rules for:

   mutable
   const
   shared
   const shared
   immutable

and which can be implicitly converted to what, so far nobody 
has found a fault in those rules...


Here's one: shared -> const shared shouldn't be allowed. Mutable 
aliasing in single-threaded code is bad enough as it is.


Re: shared - i need it to be useful

2018-10-17 Thread jmh530 via Digitalmars-d
On Wednesday, 17 October 2018 at 05:40:41 UTC, Walter Bright 
wrote:

On 10/15/2018 11:46 AM, Manu wrote:

[...]


Shared has one incredibly valuable feature - it allows you, the 
programmer, to identify data that can be accessed by multiple 
threads. There are so many ways that data can be shared, the 
only way to comprehend what is going on is to build a wall 
around shared data.


(The exception to this is immutable data. Immutable data does 
not need synchronization, so there is no need to distinguish 
between shared and unshared immutable data.)

[snip]



Isn't that also true for isolated data (data that only allows one 
alias)?


Re: shared - i need it to be useful

2018-10-17 Thread jmh530 via Digitalmars-d

On Wednesday, 17 October 2018 at 07:20:20 UTC, Manu wrote:

[snip]

Oh bollocks... everyone has been complaining about this for at 
least

the 10 years I've been here!
[snip]


As far as I had known from reading the forums, shared was not 
feature complete.


Also, are you familiar with Atila's fearless library for safe 
sharing?

https://github.com/atilaneves/fearless


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 16.10.2018 20:07, Manu wrote:

On Tue, Oct 16, 2018 at 6:25 AM Timon Gehr via Digitalmars-d
 wrote:


On 16.10.2018 13:04, Dominikus Dittes Scherkl wrote:

On Tuesday, 16 October 2018 at 10:15:51 UTC, Timon Gehr wrote:

On 15.10.2018 20:46, Manu wrote:


Assuming the rules above: "can't read or write to members", and the
understanding that `shared` methods are expected to have threadsafe
implementations (because that's the whole point), what are the risks
from allowing T* -> shared(T)* conversion?



Unshared becomes useless, and in turn, shared becomes useless.

why is unshared useless?
Unshared means you can read an write to it.
If you give it to a function that expect something shared,
the function you had given it to can't read or write it, so it
can't do any harm.


It can do harm to others who hold an unshared alias to the same data and
are operating on it concurrently.


Nobody else holds an unshared alias.


How so? If you allow implicit conversions from unshared to shared, then 
you immediately get this situation.



If you pass a value as const, you don't fear that it will become mutable.
...


No, but as I already explained last time, mutable -> const is not at all 
like unshared -> shared.


const only takes away capabilities, shared adds new capabilities, such 
as sending a reference to another thread. If you have two threads that 
share data, you need cooperation from both to properly synchronize accesses.



Of course it can handle it threadsave, but as it is local,
that is only overhead - reading or changing the value can't do
any harm either. I like the idea.


But useless, because there is no way to ensure thread safety of reads
and writes if only one party to the shared state knows about the sharing.

Of course there is.


Please do enlighten me. You have two processors operating
(reading/writing) on the same address space on a modern computer
architecture with a weak memory model, and you are using an optimizing
compiler. How do you ensure sensible results without cooperation from
both of them? (Hint: you don't.)


What? This is a weird statement.
So, you're saying that nobody has successfully written any threadsafe
code, ever... we should stop trying, and we should admit that
threadsafe queues and atomics, and mutexes and stuff all don't exist?



Obviously I am not saying that.


without cooperation from both of them?


Perhaps this is the key to your statement?


Yes.


Yes. 'cooperation from both of them' in this case means, they are both
interacting with a threadsafe api, and they are blocked from accessing
members, or any non-threadsafe api.
...


Yes. Your proposal only enforces this for the shared alias.


Giving an unshared value to a function that
even can handle shared values may create some overhead, but is
indeed threadsave.



Yes, if you give it to one function only, that is the case. However, as
you may know, concurrency means that there may be multiple functions
operating on the data _at the same time_. If one of them operates on the
data as if it was not shared, you will run into trouble.


Who's doing this,


Anyone, it really does not matter. One major point of the type system is 
to ensure that _all_ @safe code has defined behavior. You can convert 
between shared and unshared, just not in @safe code.



and how?
...


They create a mutable instance of a class, they create a shared alias 
using one of your proposed holes, then send the shared alias to another 
thread, call some methods on it in both threads and get race conditions.



You are arguing as if there was either no concurrency or no mutable
aliasing.


If a class has no shared methods, there's no possibility for mutable aliasing.
If the class has shared methods, then the class was carefully designed
to be threadsafe.



Not necessarily. Counterexample:

@safe:
class C{
int x;
void foo(){
x+=1; // this can still race with atomicIncrement
}
void bar()shared{
atomicIncrement(x); // presumably you want to allow this
}
}

void main(){
auto c=new C();
shared s=c; // depending on your exact proposed rules, this step 
may be more cumbersome

spawn!(()=>s.bar());
s.foo(); // race
}

Now, if a class has only shared members, that is another story. In this 
case, all references should implicitly convert to shared. There's a DIP 
I meant to write about this. (For all qualifiers, not just shared).


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 16.10.2018 19:25, Manu wrote:

On Tue, Oct 16, 2018 at 3:20 AM Timon Gehr via Digitalmars-d
 wrote:


On 15.10.2018 20:46, Manu wrote:


Assuming the rules above: "can't read or write to members", and the
understanding that `shared` methods are expected to have threadsafe
implementations (because that's the whole point), what are the risks
from allowing T* -> shared(T)* conversion?



Unshared becomes useless, and in turn, shared becomes useless. You can't
have unshared/shared aliasing.


What aliasing?


Aliasing means you have two references to the same data. The two 
references are then said to alias. An implicit conversion from unshared 
to shared by definition introduces aliasing, where one of the two 
references is unshared and the other is shared.



Please show a reasonable and likely construction of the
problem. I've been trying to think of it.
...


I have given you an example. I don't care whether it is "reasonable" or 
"likely". The point of @safe is to have a subset of the language with a 
sound type system.



All the risks that I think have been identified previously assume that
you can arbitrarily modify the data. That's insanity... assume we fix
that... I think the promotion actually becomes safe now...?


But useless, because there is no way to ensure thread safety of reads
and writes if only one party to the shared state knows about the sharing.


What? I don't understand this sentence.

If a shared method is not threadsafe, then it's an implementation
error. A user should expect that a shared method is threadsafe,
otherwise it shouldn't be a shared method! Thread-local (ie, normal)
methods are for not-threadsafe functionality.



Your function can be thread safe all you want. If you have a thread 
unsafe function also operating on the same state, you will still get 
race conditions. E.g. mutual exclusion is based on cooperation from all 
threads. If one of them forgets to lock, it does not matter how 
threadsafe all the others are.


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 15.10.2018 23:51, Manu wrote:

If a shared method is incompatible with an unshared method, your class
is broken.


Then what you want is not implicit unshared->shared conversion. What you 
want is a different way to type shared member access. You want a setup 
where shared methods are only allowed to access shared members and 
unshared methods are only allowed to access unshared members.


I.e., what you want is that shared is not transitive. You want that if 
you have a shared(C) c, then it is an error to access c.m iff m is not 
shared. This way you can have partially shared classes, where part of 
the class is thread-local, and other parts are shared with other threads.


Is this it?


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 17.10.2018 14:24, Timon Gehr wrote:

On 15.10.2018 23:51, Manu wrote:

If a shared method is incompatible with an unshared method, your class
is broken.


Then what you want is not implicit unshared->shared conversion. What you 
want is a different way to type shared member access. You want a setup 
where shared methods are only allowed to access shared members and 
unshared methods are only allowed to access unshared members.


I.e., what you want is that shared is not transitive. You want that if 
you have a shared(C) c, then it is an error to access c.m iff m is not 
shared. This way you can have partially shared classes, where part of 
the class is thread-local, and other parts are shared with other threads.


Is this it?


(Also, with this new definition of 'shared', unshared -> shared 
conversion would of course become sound.)


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 17.10.2018 09:20, Manu wrote:

Timon Gehr has done a good job showing that they still stand
unbreached.

His last comment was applied to a different proposal.
His only comment on this thread wasn't in response to the proposal in
this thread.
If you nominate Timon as your proxy, then he needs to destroy my
proposal, or at least comment on it, rather than make some prejudiced
comment generally.



There is no "prejudice", just reasoning. Your proposal was "disallow 
member access on shared aggregates, allow implicit conversion from 
unshared to shared and keep everything else the same". This is a bad 
proposal. There may be a good proposal that allows the things you want, 
but you have not stated what they are, your OP was just: "look at this 
bad proposal, it might work, no?" I said no, then was met with some 
hostility.


You should focus on finding a good proposal that achieves what you want 
without breaking the type system instead of attacking me.


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 17.10.2018 14:29, Timon Gehr wrote:

to access c.m iff m is not shared


Unfortunate typo. This should be if, not iff (if and only if).


Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/16/18 6:24 PM, Nicholas Wilson wrote:

On Tuesday, 16 October 2018 at 21:19:26 UTC, Steven Schveighoffer wrote:
OK, so here is where I think I misunderstood your point. When you said 
a lock-free queue would be unusable if it wasn't shared, I thought you 
meant it would be unusable if we didn't allow the implicit cast. But I 
realize now, you meant you should be able to use a lock-free queue 
without it being actually shared anywhere.


What I say to this is that it doesn't need to be usable. I don't care 
to use a lock-free queue in a thread-local capacity. I'll just use a 
normal queue, which is easy to implement, and doesn't have to worry 
about race conditions or using atomics. A lock free queue is a special 
thing, very difficult to get right, and only really necessary if you 
are going to share it. And used for performance reasons!


I think this comes up where the queue was originally shared, you 
acquired a lock on the thing it is a member of, and you want to continue 
using it through your exclusive reference.




Isn't that a locking queue? I thought we were talking lock-free?

-Steve


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 17.10.2018 14:24, Timon Gehr wrote:

and unshared methods are only allowed to access unshared members.


This is actually not necessary, let me reformulate:

You want:

- if you have a C c and a shared(C) s, typeof(s.x) == typeof(c.x).
- shared methods are not allowed to access unshared members.
- shared is not transitive, and therefore unshared class references 
implicitly convert to shared class references


Applied to pointers, this would mean that you can implicitly convert 
int* -> shared(int*), but not shared(int*)->int*, int* -> shared(int)* 
or shared(int)* -> int*. shared(int*) and shared(shared(int)*) would be 
different types, such that shared(int*) cannot be dereferenced but 
shared(shared(int)*) can.


Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/16/18 8:26 PM, Manu wrote:

On Tue, Oct 16, 2018 at 2:20 PM Steven Schveighoffer via Digitalmars-d
 wrote:


On 10/16/18 4:26 PM, Manu wrote:

On Tue, Oct 16, 2018 at 11:30 AM Steven Schveighoffer via
Digitalmars-d  wrote:

int x;

shared int *p = &x; // allow implicit conversion, currently error

passToOtherThread(p);

useHeavily(&x);


What does this mean? It can't do anything... that's the whole point here.
I think I'm struggling here with people bringing presumptions to the
thread. You need to assume the rules I define in the OP for the
experiment to work.


OK, I wrote a whole big response to this, and I went and re-quoted the
above, and now I think I understand what the point of your statement is.

I'll first say that if you don't want to allow implicit casting of
shared to mutable,


It's critical that this is not allowed. It's totally unreasonable to
cast from shared to thread-local without synchronisation.


OK, so even with synchronization in the second thread when you cast, you 
still have a thread-local pointer in the originating thread WITHOUT 
synchronization.



It's as bad as casting away const.


Of course! But shared has a different problem from const. Const allows 
the data to change through another reference, shared cannot allow 
changes without synchronization.


Changes without synchronization are *easy* with an unshared reference. 
Data can't be shared and unshared at the same time.



then you can't allow implicit casting from mutable to
shared. Because it's mutable, races can happen.


I don't follow...


You seem to be saying that shared data is unusable. But why the hell 
have it then? At some point it has to be usable. And the agreed-upon use 
is totally defeated if you also have some stray non-shared reference to it.





There is in fact, no difference between:

int *p;
shared int *p2 = p;
int *p3 = cast(int*)p2;


Totally illegal!! You casted away shared. That's as bad as casting away const.


But if you can't do anything with shared data, how do you use it?




and this:

int *p;
shared int *p2 = p;
int *p3 = p;


There's nothing wrong with this... I don't understand the point?


It's identical to the top one. You now have a new unshared reference to 
shared data. This is done WITHOUT any agreed-upon synchronization.



So really, the effort to prevent the reverse cast is defeated by
allowing the implicit cast.


Only the caller has the thread-local instance. You can take a
thread-local pointer to a thread-local within the context of a single
thread.
So, it's perfectly valid for `p` and `p3` to exist in a single scope.
`p2` is fine here too... and if that shared pointer were to escape to
another thread, it wouldn't be a threat, because it's not readable or
writable, and you can't make it back into a thread-local pointer
without carefully/deliberately deployed machinery.


Huh? If shared data can never be used, why have it?

Pretend that p is not a pointer to an int, but a pointer to an UNSHARED 
type that has shared methods on it and unshared methods (for when you 
don't need any sync).


Now the shared methods will obey the sync, but the unshared ones won't. 
The result is races. I can't understand how you don't see that.



There is a reason we disallow assigning from mutable to immutable
without a cast. Yet, it is done in many cases, because you are sometimes
building an immutable object with mutable pieces, and want to cast the
final result.


I don't think analogy to immutable has a place in this discussion, or
at least, I don't understand the relevance...
I think the reasonable analogy is const.


No, immutable is more akin to shared because immutable and mutable are 
completely different. const can point at mutable or immutable data. 
shared can't be both shared and unshared. There's no comparison. Data is 
either shared or not shared, there is no middle ground. There is no 
equivalent of const to say "this data could be shared, or could be 
unshared".



In this case, it's ON YOU to make sure it's correct, and the traditional
mechanism for the compiler giving you the responsibility is to require a
cast.


I think what you're talking about are behaviours relating to casting
shared *away*, and that's some next-level shit. Handling in that case
is no different to the way it exists today. You must guarantee that
the pointer you possess becomes thread-local before casting it to a
thread-local pointer.
In my application framework, I will never cast shared away under my
proposed design. We don't have any such global locks.


OK, so how does shared data actually operate? Somewhere, the magic has 
to turn into real code. If not casting away shared, what do you suggest?



-

OK, so here is where I think I misunderstood your point. When you said a
lock-free queue would be unusable if it wasn't shared, I thought you
meant it would be unusable if we didn't allow the implicit cast. But I
realize now, you meant you should be able to use a lock-free queue
without it bein

Re: shared - i need it to be useful

2018-10-17 Thread Stanislav Blinov via Digitalmars-d

Jesus Manu, it's soon 8 pages of dancing around a trivial issue.

Implicit casting from mutable to shared is unsafe, case closed.
Explicit cast from mutable to unsafe, on the other hand:

- is an assertion (on programmer's behalf) that this instance is 
indeed unique

- is self-documenting
- is greppable (especially if implemented as assumeShared or 
other descriptive name).


I don't understand why are you so fixated on this. The other 
parts of your proposal are much more important.


Re: shared - i need it to be useful

2018-10-17 Thread Stanislav Blinov via Digitalmars-d
On Wednesday, 17 October 2018 at 13:36:53 UTC, Stanislav Blinov 
wrote:



Explicit cast from mutable to unsafe, on the other hand:


Blargh, to shared of course.


Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/17/18 8:02 AM, Timon Gehr wrote:
Now, if a class has only shared members, that is another story. In this 
case, all references should implicitly convert to shared. There's a DIP 
I meant to write about this. (For all qualifiers, not just shared).


When you say "shared members", you mean all the data is shared too or 
just the methods are shared?


If not the data, D has a problem with encapsulation. Not only all the 
methods on the class must be shared, but ALL code in the entire module 
must be marked as using a shared class instance. Otherwise, other 
functions could modify the private data without using the proper synch 
mechanisms.


We are better off requiring the cast, or enforcing that one must use a 
shared object to begin with.


I think any sometimes-shared object is in any case going to benefit from 
parallel implementations for when the thing is unshared.


-Steve


Re: shared - i need it to be useful

2018-10-17 Thread Nicholas Wilson via Digitalmars-d
On Wednesday, 17 October 2018 at 13:25:28 UTC, Steven 
Schveighoffer wrote:

On 10/16/18 8:26 PM, Manu wrote:
On Tue, Oct 16, 2018 at 2:20 PM Steven Schveighoffer via 
Digitalmars-d

 wrote:

There is in fact, no difference between:

int *p;
shared int *p2 = p;
int *p3 = cast(int*)p2;


Totally illegal!! You casted away shared. That's as bad as 
casting away const.


But if you can't do anything with shared data, how do you use 
it?





and this:

int *p;
shared int *p2 = p;
int *p3 = p;


There's nothing wrong with this... I don't understand the 
point?


It's identical to the top one. You now have a new unshared 
reference to shared data. This is done WITHOUT any agreed-upon 
synchronization.


It isn't, you typo'd it (I originally missed it too).

int *p3 = cast(int*)p2;


vs


int *p3 = p;


Re: shared - i need it to be useful

2018-10-17 Thread Nicholas Wilson via Digitalmars-d
On Wednesday, 17 October 2018 at 07:24:13 UTC, Stanislav Blinov 
wrote:
On Wednesday, 17 October 2018 at 05:40:41 UTC, Walter Bright 
wrote:



When Andrei and I came up with the rules for:

   mutable
   const
   shared
   const shared
   immutable

and which can be implicitly converted to what, so far nobody 
has found a fault in those rules...


Here's one: shared -> const shared shouldn't be allowed. 
Mutable aliasing in single-threaded code is bad enough as it is.


Could you explain that a bit more? I don't understand it, mutable 
-> const is half the reason const exists.


I was thinking that mutable -> shared const as apposed to mutable 
-> shared would get around the issues that Timon posted.


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 17.10.2018 15:40, Steven Schveighoffer wrote:

On 10/17/18 8:02 AM, Timon Gehr wrote:
Now, if a class has only shared members, that is another story. In 
this case, all references should implicitly convert to shared. There's 
a DIP I meant to write about this. (For all qualifiers, not just shared).


When you say "shared members", you mean all the data is shared too or 
just the methods are shared?


If not the data, D has a problem with encapsulation. Not only all the 
methods on the class must be shared, but ALL code in the entire module 
must be marked as using a shared class instance. Otherwise, other 
functions could modify the private data without using the proper synch 
mechanisms.


We are better off requiring the cast, or enforcing that one must use a 
shared object to begin with.


I think any sometimes-shared object is in any case going to benefit from 
parallel implementations for when the thing is unshared.


-Steve


The specific proposal was that, for example, if a class is defined like 
this:


shared class C{
// ...
}

then shared(C) and C are implicitly convertible to each other. The 
change is not fully backwards-compatible, because right now, this 
annotation just makes all members (data and methods) shared, but child 
classes may introduce unshared members.


Re: shared - i need it to be useful

2018-10-17 Thread Timon Gehr via Digitalmars-d

On 17.10.2018 16:14, Nicholas Wilson wrote:


I was thinking that mutable -> shared const as apposed to mutable -> 
shared would get around the issues that Timon posted.


Unfortunately not. For example, the thread with the mutable reference is 
not obliged to actually make the changes that are performed on that 
reference visible to other threads.


Re: shared - i need it to be useful

2018-10-17 Thread Nicholas Wilson via Digitalmars-d

On Wednesday, 17 October 2018 at 14:26:43 UTC, Timon Gehr wrote:

On 17.10.2018 16:14, Nicholas Wilson wrote:


I was thinking that mutable -> shared const as apposed to 
mutable -> shared would get around the issues that Timon 
posted.


Unfortunately not. For example, the thread with the mutable 
reference is not obliged to actually make the changes that are 
performed on that reference visible to other threads.


Yes, but that is covered by not being able to read non-atomically 
from a shared reference.


Re: shared - i need it to be useful

2018-10-17 Thread Guillaume Piolat via Digitalmars-d

On Wednesday, 17 October 2018 at 07:20:20 UTC, Manu wrote:
Shared is uninteresting and mostly useless as spec-ed, everyone 
knows this.

Interaction with shared via barely-controlled blunt casting in
@trusted blocks is feeble and boring. It doesn't really give us
anything in practice that we don't have in C++. It's an 
uninspired

design.


+1

I'm pretty sure it's great in theory, in practice it's another 
invasive type constructor, and not a particular well-understood 
or easy to deal with one.


The fact that this _type constructor_ finds its way into 
_identifiers_ create some concern: 
https://github.com/dlang/phobos/blob/656798f2b385437c239246b59e0433148190938c/std/experimental/allocator/package.d#L642


Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/17/18 10:18 AM, Timon Gehr wrote:

On 17.10.2018 15:40, Steven Schveighoffer wrote:

On 10/17/18 8:02 AM, Timon Gehr wrote:
Now, if a class has only shared members, that is another story. In 
this case, all references should implicitly convert to shared. 
There's a DIP I meant to write about this. (For all qualifiers, not 
just shared).


When you say "shared members", you mean all the data is shared too or 
just the methods are shared?


If not the data, D has a problem with encapsulation. Not only all the 
methods on the class must be shared, but ALL code in the entire module 
must be marked as using a shared class instance. Otherwise, other 
functions could modify the private data without using the proper synch 
mechanisms.


We are better off requiring the cast, or enforcing that one must use a 
shared object to begin with.


I think any sometimes-shared object is in any case going to benefit 
from parallel implementations for when the thing is unshared.


-Steve


The specific proposal was that, for example, if a class is defined like 
this:


shared class C{
     // ...
}

then shared(C) and C are implicitly convertible to each other. The 
change is not fully backwards-compatible, because right now, this 
annotation just makes all members (data and methods) shared, but child 
classes may introduce unshared members.


OK, so the proposal is that all data and function members are shared. 
That makes sense.


In one sense, because the class reference is conflated with the type 
modifier, having a C that isn't shared, actually have it's class data be 
shared, would be useful.


-Steve



Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/17/18 10:33 AM, Nicholas Wilson wrote:

On Wednesday, 17 October 2018 at 14:26:43 UTC, Timon Gehr wrote:

On 17.10.2018 16:14, Nicholas Wilson wrote:


I was thinking that mutable -> shared const as apposed to mutable -> 
shared would get around the issues that Timon posted.


Unfortunately not. For example, the thread with the mutable reference 
is not obliged to actually make the changes that are performed on that 
reference visible to other threads.


Yes, but that is covered by not being able to read non-atomically from a 
shared reference.


All sides must participate in synchronization for it to make sense. The 
mutable side has no obligation to use atomics. It can use ++data, and 
race conditions will happen.


-Steve


Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/17/18 9:58 AM, Nicholas Wilson wrote:

On Wednesday, 17 October 2018 at 13:25:28 UTC, Steven Schveighoffer wrote:
It's identical to the top one. You now have a new unshared reference 
to shared data. This is done WITHOUT any agreed-upon synchronization.


It isn't, you typo'd it (I originally missed it too).

int *p3 = cast(int*)p2;


vs


int *p3 = p;


It wasn't a typo.

It's identical in that both result in a thread-local pointer equivalent 
to p. Effectively, you can "cast" away shared without having to write a 
cast.


I was trying to demonstrate the ineffectiveness of preventing implicit 
casting from shared to mutable if you allow unshared data to implicitly 
cast to shared.


It's the same problem with mutable and immutable. It's why we can't 
allow the implicit casting. Explicit casting is OK as long as you don't 
later modify the data.


In the same vein, explicit casting of local to shared is OK as long as 
you don't ever treat the data as local again. Which should requires a 
cast to say "I know what I'm doing, compiler".


-Steve


Re: shared - i need it to be useful

2018-10-17 Thread Nicholas Wilson via Digitalmars-d
On Wednesday, 17 October 2018 at 15:51:04 UTC, Steven 
Schveighoffer wrote:

On 10/17/18 9:58 AM, Nicholas Wilson wrote:
On Wednesday, 17 October 2018 at 13:25:28 UTC, Steven 
Schveighoffer wrote:
It's identical to the top one. You now have a new unshared 
reference to shared data. This is done WITHOUT any 
agreed-upon synchronization.


It isn't, you typo'd it (I originally missed it too).

int *p3 = cast(int*)p2;


vs


int *p3 = p;


It wasn't a typo.


The first example assigns p2, the second assigns p (which is 
thread local) _not_ p2 (which is shared), I'm confused.




Re: shared - i need it to be useful

2018-10-17 Thread Stanislav Blinov via Digitalmars-d
On Wednesday, 17 October 2018 at 14:44:19 UTC, Guillaume Piolat 
wrote:


The fact that this _type constructor_ finds its way into 
_identifiers_ create some concern: 
https://github.com/dlang/phobos/blob/656798f2b385437c239246b59e0433148190938c/std/experimental/allocator/package.d#L642


Well, ISharedAllocator is indeed overboard, but at least 
`allocateShared` would've been a very useful identifier indeed.


Re: shared - i need it to be useful

2018-10-17 Thread Stanislav Blinov via Digitalmars-d
On Wednesday, 17 October 2018 at 14:14:56 UTC, Nicholas Wilson 
wrote:
On Wednesday, 17 October 2018 at 07:24:13 UTC, Stanislav Blinov 
wrote:
On Wednesday, 17 October 2018 at 05:40:41 UTC, Walter Bright 
wrote:



When Andrei and I came up with the rules for:

   mutable
   const
   shared
   const shared
   immutable

and which can be implicitly converted to what, so far nobody 
has found a fault in those rules...


Here's one: shared -> const shared shouldn't be allowed. 
Mutable aliasing in single-threaded code is bad enough as it 
is.


Could you explain that a bit more? I don't understand it, 
mutable -> const is half the reason const exists.


Yes, but `shared` is not `const`. This 'might change' rule is 
poisonous for shared data: you essentially create extra work for 
the CPU for very little gain. There's absolutely no reason to 
share a read-only half-constant anyway. Either give immutable, or 
just flat out copy.


I was thinking that mutable -> shared const as apposed to 
mutable -> shared would get around the issues that Timon posted.


It wouldn't, as Timon already explained. Writes to not-`shared` 
mutables might just not be propagated beyond the core. Wouldn't 
happen on amd64, of course, but still. It's not about atomicity 
of reads, it just depends on architecture. On some systems you 
have to explicitly synchronize memory.


Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/17/18 12:27 PM, Nicholas Wilson wrote:

On Wednesday, 17 October 2018 at 15:51:04 UTC, Steven Schveighoffer wrote:

On 10/17/18 9:58 AM, Nicholas Wilson wrote:
On Wednesday, 17 October 2018 at 13:25:28 UTC, Steven Schveighoffer 
wrote:
It's identical to the top one. You now have a new unshared reference 
to shared data. This is done WITHOUT any agreed-upon synchronization.


It isn't, you typo'd it (I originally missed it too).

int *p3 = cast(int*)p2;


vs


int *p3 = p;


It wasn't a typo.


The first example assigns p2, the second assigns p (which is thread 
local) _not_ p2 (which is shared), I'm confused.




Here they are again:

int *p;
shared int *p2 = p;
int *p3 = cast(int*)p2;

int *p;
shared int *p2 = p;
int *p3 = p;


I'll put some asserts in that show they accomplish the same thing:

assert(p3 is p2);
assert(p3 is p);
assert(p2 is p);

What the example demonstrates is that while you are trying to disallow 
implicit casting of a shared pointer to an unshared pointer, you have 
inadvertently allowed it by leaving behind an unshared pointer that is 
the same thing.


While we do implicitly allow mutable to cast to const, it's because 
const is a weak guarantee. It's a guarantee that the data may not change 
via *this* reference, but could change via other references.


Shared doesn't have the same characteristics. In order for a datum to be 
safely shared, it must be accessed with synchronization or atomics by 
ALL parties. If you have one party that can simply change it without 
those, you will get races.


That's why shared/unshared is more akin to mutable/immutable than 
mutable/const.


It's true that only one thread will have thread-local access. It's not 
valid any more than having one mutable alias to immutable data.


-Steve


Re: shared - i need it to be useful

2018-10-17 Thread Manu via Digitalmars-d
On Wed, Oct 17, 2018 at 10:30 AM Steven Schveighoffer via
Digitalmars-d  wrote:
>
> On 10/17/18 12:27 PM, Nicholas Wilson wrote:
> > On Wednesday, 17 October 2018 at 15:51:04 UTC, Steven Schveighoffer wrote:
> >> On 10/17/18 9:58 AM, Nicholas Wilson wrote:
> >>> On Wednesday, 17 October 2018 at 13:25:28 UTC, Steven Schveighoffer
> >>> wrote:
>  It's identical to the top one. You now have a new unshared reference
>  to shared data. This is done WITHOUT any agreed-upon synchronization.
> >>>
> >>> It isn't, you typo'd it (I originally missed it too).
>  int *p3 = cast(int*)p2;
> >>>
> >>> vs
> >>>
>  int *p3 = p;
> >>
> >> It wasn't a typo.
> >
> > The first example assigns p2, the second assigns p (which is thread
> > local) _not_ p2 (which is shared), I'm confused.
> >
>
> Here they are again:
>
> int *p;
> shared int *p2 = p;
> int *p3 = cast(int*)p2;
>
> int *p;
> shared int *p2 = p;
> int *p3 = p;
>
>
> I'll put some asserts in that show they accomplish the same thing:
>
> assert(p3 is p2);
> assert(p3 is p);
> assert(p2 is p);
>
> What the example demonstrates is that while you are trying to disallow
> implicit casting of a shared pointer to an unshared pointer, you have
> inadvertently allowed it by leaving behind an unshared pointer that is
> the same thing.

This doesn't make sense... you're showing a thread-local program.
The thread owning the unshared pointer is entitled to the unshared
pointer. It can make as many copies at it likes. They are all
thread-local.
There's only one owning thread, and you can't violate that without unsafe casts.

> In order for a datum to be
> safely shared, it must be accessed with synchronization or atomics by
> ALL parties.

** Absolutely **

> If you have one party that can simply change it without
> those, you will get races.

*** THIS IS NOT WHAT I'M PROPOSING ***

I've explained it a few times now, but people aren't reading what I
actually write, and just assume based on what shared already does that
they know what I'm suggesting.
You need to eject all presumptions from your mind, take the rules I
offer as verbatim, and do thought experiments from there.

> That's why shared/unshared is more akin to mutable/immutable than
> mutable/const.

Only if you misrepresent my suggestion.

> It's true that only one thread will have thread-local access. It's not
> valid any more than having one mutable alias to immutable data.

And this is why the immutable analogy is invalid. It's like const.
shared offers restricted access (like const), not a different class of
thing.
There is one thread with thread-local access, and many threads with
shared access.

If a shared (threadsafe) method can be defeated by threadlocal access,
then it's **not threadsafe**, and the program is invalid.

struct NotThreadsafe
{
  int x;
  void local()
  {
++x; // <- invalidates the method below, you violate the other
function's `shared` promise
  }
  void notThreadsafe() shared
  {
atomicIncrement(&x);
  }
}

struct Atomic(T)
{
  void opUnary(string op : "++")() shared { atomicIncrement(&val); }
  private T val;
}
struct Threadsafe
{
  Atomic!int x;
  void local()
  {
++x;
  }
  void threadsafe() shared
  {
++x;
  }
}

Naturally, local() is redundant, and it's perfectly fine for a
thread-local to call threadsafe() via implicit conversion.

Here's another one, where only a subset of the object is modeled to be
threadsafe (this is particularly interesting to me):

struct Threadsafe
{
  int x;
  Atomic!int y;

  void notThreadsafe()
  {
++x;
++y;
  }
  void threadsafe() shared
  {
++y;
  }
}

In these examples, the thread-local function *does not* undermine the
threadsafety of threadsafe(), it MUST NOT undermine the threadsafety
of threadsafe(), or else threadsafe() **IS NOT THREADSAFE**.
In the second example, you can see how it's possible and useful to do
thread-local work without invalidating the objects threadsafety
commitments.


I've said this a bunch of times, there are 2 rules:
1. shared inhibits read and write access to members
2. `shared` methods must be threadsafe

>From there, shared becomes interesting and useful.


Re: shared - i need it to be useful

2018-10-17 Thread Stanislav Blinov via Digitalmars-d

On Wednesday, 17 October 2018 at 18:46:18 UTC, Manu wrote:


I've said this a bunch of times, there are 2 rules:
1. shared inhibits read and write access to members
2. `shared` methods must be threadsafe


From there, shared becomes interesting and useful.


Oh God...

void atomicInc(shared int* i) { /* ... */ }

Now what? There are no "methods" for ints, only UFCS. Those 
functions can be as safe as you like, but if you allow implicit 
promotion of int* to shared int*, you *allow implicit races*.


Re: shared - i need it to be useful

2018-10-17 Thread Manu via Digitalmars-d
On Wed, Oct 17, 2018 at 12:05 PM Stanislav Blinov via Digitalmars-d
 wrote:
>
> On Wednesday, 17 October 2018 at 18:46:18 UTC, Manu wrote:
>
> > I've said this a bunch of times, there are 2 rules:
> > 1. shared inhibits read and write access to members
> > 2. `shared` methods must be threadsafe
> >
> >>From there, shared becomes interesting and useful.
>
> Oh God...
>
> void atomicInc(shared int* i) { /* ... */ }
>
> Now what? There are no "methods" for ints, only UFCS. Those
> functions can be as safe as you like, but if you allow implicit
> promotion of int* to shared int*, you *allow implicit races*.

This function is effectively an intrinsic. It's unsafe by definition.
It's a tool for implementing threadsafe machinery.
No user can just start doing atomic operations on random ints and say
"it's threadsafe", you must encapsulate the threadsafe functionality
into some sort of object that aggregates all concerns and presents an
intellectually sound api.


Let me try one:

void free(void*) { ... }

Now what? I might have dangling pointers... it's a catastrophe!
It's essentially the same argument.
This isn't a function that professes to do something that people might
misunderstand and try to use in an unsafe way, it's a low-level
implementation device, which is used to build larger *useful*
constructs.


Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/17/18 2:46 PM, Manu wrote:

On Wed, Oct 17, 2018 at 10:30 AM Steven Schveighoffer via



What the example demonstrates is that while you are trying to disallow
implicit casting of a shared pointer to an unshared pointer, you have
inadvertently allowed it by leaving behind an unshared pointer that is
the same thing.


This doesn't make sense... you're showing a thread-local program.
The thread owning the unshared pointer is entitled to the unshared
pointer. It can make as many copies at it likes. They are all
thread-local.


It's assumed that shared int pointer can be passed to another thread, 
right? Do I have to write a full program to demonstrate?



There's only one owning thread, and you can't violate that without unsafe casts.


The what is the point of shared? Like why would you share data that 
NOBODY CAN USE?


At SOME POINT, shared data needs to be readable and writable. Any 
correct system is going to dictate how that works. It's a good start to 
make shared data unusable unless you cast. But then to make it 
implicitly castable from unshared defeats the whole purpose.



In order for a datum to be
safely shared, it must be accessed with synchronization or atomics by
ALL parties.


** Absolutely **


If you have one party that can simply change it without
those, you will get races.


*** THIS IS NOT WHAT I'M PROPOSING ***

I've explained it a few times now, but people aren't reading what I
actually write, and just assume based on what shared already does that
they know what I'm suggesting.
You need to eject all presumptions from your mind, take the rules I
offer as verbatim, and do thought experiments from there.


What seems to be a mystery here is how one is to actually manipulate 
shared data. If it's not usable as shared data, how does one use it?





That's why shared/unshared is more akin to mutable/immutable than
mutable/const.


Only if you misrepresent my suggestion.


It's not misrepresentation, I'm trying to fill in the holes with the 
only logical possibilities I can think of.





It's true that only one thread will have thread-local access. It's not
valid any more than having one mutable alias to immutable data.


And this is why the immutable analogy is invalid. It's like const.
shared offers restricted access (like const), not a different class of
thing.


No, not at all. Somehow one must manipulate shared data. If shared data 
cannot be read or written, there is no reason to share it.


So LOGICALLY, we have to assume, yes there actually IS a way to 
manipulate shared data through these very carefully constructed and 
guarded things.



There is one thread with thread-local access, and many threads with
shared access.

If a shared (threadsafe) method can be defeated by threadlocal access,
then it's **not threadsafe**, and the program is invalid.

struct NotThreadsafe
{
   int x;
   void local()
   {
 ++x; // <- invalidates the method below, you violate the other
function's `shared` promise
   }
   void notThreadsafe() shared
   {
 atomicIncrement(&x);
   }
}


So the above program is invalid. Is it compilable with your added 
allowance of implicit casting to shared? If it's not compilable, why 
not? If it is compilable, how in the hell does your proposal help 
anything? I get the exact behavior today without any changes (except 
today, I need to explicitly cast, which puts the onus on me).




struct Atomic(T)
{
   void opUnary(string op : "++")() shared { atomicIncrement(&val); }
   private T val;
}
struct Threadsafe
{
   Atomic!int x;
   void local()
   {
 ++x;
   }
   void threadsafe() shared
   {
 ++x;
   }
}

Naturally, local() is redundant, and it's perfectly fine for a
thread-local to call threadsafe() via implicit conversion.


In this case, yes. But that's not because of anything the compiler can 
prove.


How does Atomic work? I thought shared data was not usable? I'm being 
pedantic because every time I say "well at some point you must be able 
to modify things", you explode.


Complete the sentence: "In order to read or write shared data, you have 
to ..."




Here's another one, where only a subset of the object is modeled to be
threadsafe (this is particularly interesting to me):

struct Threadsafe
{
   int x;
   Atomic!int y;

   void notThreadsafe()
   {
 ++x;
 ++y;
   }
   void threadsafe() shared
   {
 ++y;
   }
}

In these examples, the thread-local function *does not* undermine the
threadsafety of threadsafe(), it MUST NOT undermine the threadsafety
of threadsafe(), or else threadsafe() **IS NOT THREADSAFE**.
In the second example, you can see how it's possible and useful to do
thread-local work without invalidating the objects threadsafety
commitments.


I've said this a bunch of times, there are 2 rules:
1. shared inhibits read and write access to members
2. `shared` methods must be threadsafe


From there, shared becomes interesting and useful.




Given rule 1, how does Atomic!int actually work, if it can't read or 
writ

Re: shared - i need it to be useful

2018-10-17 Thread NX via Digitalmars-d
I don't see any problem with this proposal as long as these 
points hold:


- Shared <-> Unshared is never implicit, either requiring an 
explicit cast (both ways) or having a language support which 
allows the conversion gracefully.
- Shared methods are called by compiler if the type is shared or 
if there is no unshared equivalent.
- Programmer needs to guarantee that shared -> unshared 
cast/conversion is thread-safe by hand; such as acquiring a lock, 
atomic operations...
- Programmer needs to guarantee that when unshared -> shared 
cast/conversion happens, data is not accessed through unshared 
reference during the lifetime of shared reference(s). Effectively 
this means a data needs to be treated as shared everywhere at the 
same time otherwise all things fall apart.


Re: shared - i need it to be useful

2018-10-17 Thread ag0aep6g via Digitalmars-d

On 17.10.18 20:46, Manu wrote:

struct NotThreadsafe
{
   int x;
   void local()
   {
 ++x; // <- invalidates the method below, you violate the other
function's `shared` promise
   }
   void notThreadsafe() shared
   {
 atomicIncrement(&x);
   }
}


In the `shared` method you'd get a nice error when attempting `++x;`, 
because it's not thread-safe. With your proposal, it's just as unsafe in 
`local`, but you don't get any help from the compiler in spotting it. 
`local` is effectively `@trusted` without being marked as such.


Re: shared - i need it to be useful

2018-10-17 Thread Stanislav Blinov via Digitalmars-d

On Wednesday, 17 October 2018 at 19:25:33 UTC, Manu wrote:
On Wed, Oct 17, 2018 at 12:05 PM Stanislav Blinov via 
Digitalmars-d  wrote:


On Wednesday, 17 October 2018 at 18:46:18 UTC, Manu wrote:

> I've said this a bunch of times, there are 2 rules:
> 1. shared inhibits read and write access to members
> 2. `shared` methods must be threadsafe
>
>>From there, shared becomes interesting and useful.

Oh God...

void atomicInc(shared int* i) { /* ... */ }

Now what? There are no "methods" for ints, only UFCS. Those 
functions can be as safe as you like, but if you allow 
implicit promotion of int* to shared int*, you *allow implicit 
races*.


This function is effectively an intrinsic. It's unsafe by 
definition.


Only if implicit conversion is allowed. If it isn't, that's 
likely @trusted, and this:


void atomicInc(ref shared int);

can even be @safe.


It's a tool for implementing threadsafe machinery.
No user can just start doing atomic operations on random ints 
and say
"it's threadsafe", you must encapsulate the threadsafe 
functionality
into some sort of object that aggregates all concerns and 
presents an

intellectually sound api.


Threadsafety starts and ends with the programmer. By your logic 
*all* functions operating on `shared` are unsafe then. As far as 
compiler is concerned, there would be no difference between these 
two:


struct S {}
void atomicInc(ref shared S);

and

struct S { void atomicInc() shared { /* ... */ } }

The signatures of those two functions are exactly the same. How 
is that different from a function taking a shared int pointer or 
reference?




Let me try one:

void free(void*) { ... }

Now what? I might have dangling pointers... it's a catastrophe!


One could argue that it should be void free(ref void* p) { /* ... 
*/ p = null; }
As a matter of fact, in my own allocators memory blocks allocated 
by them are passed by value and are non-copyable, they're not 
just void[] as in std.experimental.allocator. One must 'move' 
them to pass ownership, and that includes deallocation. But 
that's another story altogether.



It's essentially the same argument.
This isn't a function that professes to do something that 
people might

misunderstand and try to use in an unsafe way, it's a low-level
implementation device, which is used to build larger *useful*
constructs.


You're missing the point, again. You have an int. You pass a 
pointer to it to some API that takes an int*. You continue to use 
your int as just an int. The API changes, and now the function 
you called previously takes a shared int*. Implicit conversion 
works, everything compiles, you have a race. Now, that's of 
course an extremely stupid scenario. The point is: the caller of 
some API *must* assert that they indeed pass shared data. It's 
insufficient for the API alone to "promise" taking shared data. 
That's the difference with promotion to `const`.


Re: shared - i need it to be useful

2018-10-17 Thread Manu via Digitalmars-d
On Wed, Oct 17, 2018 at 12:35 PM Steven Schveighoffer via
Digitalmars-d  wrote:
>
> On 10/17/18 2:46 PM, Manu wrote:
> > On Wed, Oct 17, 2018 at 10:30 AM Steven Schveighoffer via
>
> >> What the example demonstrates is that while you are trying to disallow
> >> implicit casting of a shared pointer to an unshared pointer, you have
> >> inadvertently allowed it by leaving behind an unshared pointer that is
> >> the same thing.
> >
> > This doesn't make sense... you're showing a thread-local program.
> > The thread owning the unshared pointer is entitled to the unshared
> > pointer. It can make as many copies at it likes. They are all
> > thread-local.
>
> It's assumed that shared int pointer can be passed to another thread,
> right? Do I have to write a full program to demonstrate?

And that shared(int)* provides no access. No other thread with that
pointer can do anything with it.

> > There's only one owning thread, and you can't violate that without unsafe 
> > casts.
>
> The what is the point of shared? Like why would you share data that
> NOBODY CAN USE?

You can call shared methods. They promise threadsafety.
That's a small subset of the program, but that's natural; only a very
small subset of the program is safe to be called from a shared
context.

In addition, traditional unsafe interactions which may involve
acquiring locks and doing casts remain exactly the same, and the exact
same design patterns must apply which assure that the object is
handled correctly.
I'm not suggesting any changes that affect that workflow.

> At SOME POINT, shared data needs to be readable and writable. Any
> correct system is going to dictate how that works. It's a good start to
> make shared data unusable unless you cast. But then to make it
> implicitly castable from unshared defeats the whole purpose.

No. No casting! This is antiquated workflow.. I'm not trying to take
it away from you, but it's not an interesting model for the future.
`shared` can model more than just that.
You can call threadsafe methods. Shared methods explicitly dictate how
the system works, and in a very clear and obvious/intuitive way.

The implicit cast makes using threadsafe objects more convenient when
you only have one, which is extremely common.

> >> In order for a datum to be
> >> safely shared, it must be accessed with synchronization or atomics by
> >> ALL parties.
> >
> > ** Absolutely **
> >
> >> If you have one party that can simply change it without
> >> those, you will get races.
> >
> > *** THIS IS NOT WHAT I'M PROPOSING ***
> >
> > I've explained it a few times now, but people aren't reading what I
> > actually write, and just assume based on what shared already does that
> > they know what I'm suggesting.
> > You need to eject all presumptions from your mind, take the rules I
> > offer as verbatim, and do thought experiments from there.
>
> What seems to be a mystery here is how one is to actually manipulate
> shared data. If it's not usable as shared data, how does one use it?

Call shared methods. It's not like I haven't been saying this in every
post since my OP.
The only possible thing that you can safely do with a shared object is
call a method that has been carefully designed for thread-safe
calling. Any other access is invalid under any circumstance.

> >> It's true that only one thread will have thread-local access. It's not
> >> valid any more than having one mutable alias to immutable data.
> >
> > And this is why the immutable analogy is invalid. It's like const.
> > shared offers restricted access (like const), not a different class of
> > thing.
>
> No, not at all. Somehow one must manipulate shared data. If shared data
> cannot be read or written, there is no reason to share it.

Call shared methods.

> So LOGICALLY, we have to assume, yes there actually IS a way to
> manipulate shared data through these very carefully constructed and
> guarded things.

Yes, call the methods, they were carefully constructed to be threadsafe.
Only functions that have made a promise to be threadsafe and
implemented that complexity are valid interactions.

> > There is one thread with thread-local access, and many threads with
> > shared access.
> >
> > If a shared (threadsafe) method can be defeated by threadlocal access,
> > then it's **not threadsafe**, and the program is invalid.
> >
> > struct NotThreadsafe
> > {
> >int x;
> >void local()
> >{
> >  ++x; // <- invalidates the method below, you violate the other
> > function's `shared` promise
> >}
> >void notThreadsafe() shared
> >{
> >  atomicIncrement(&x);
> >}
> > }
>
> So the above program is invalid. Is it compilable with your added
> allowance of implicit casting to shared? If it's not compilable, why
> not?

All my examples assume my implicit conversion rule. But regardless,
under my proposal, the above program is invalid. This violates the
threadsafe promise.

> If it is compilable, how in the hell does your proposal help
> anything? I g

Re: shared - i need it to be useful

2018-10-17 Thread Manu via Digitalmars-d
On Wed, Oct 17, 2018 at 2:15 PM Stanislav Blinov via Digitalmars-d
 wrote:
>
> On Wednesday, 17 October 2018 at 19:25:33 UTC, Manu wrote:
> > On Wed, Oct 17, 2018 at 12:05 PM Stanislav Blinov via
> > Digitalmars-d  wrote:
> >>
> >> On Wednesday, 17 October 2018 at 18:46:18 UTC, Manu wrote:
> >>
> >> > I've said this a bunch of times, there are 2 rules:
> >> > 1. shared inhibits read and write access to members
> >> > 2. `shared` methods must be threadsafe
> >> >
> >> >>From there, shared becomes interesting and useful.
> >>
> >> Oh God...
> >>
> >> void atomicInc(shared int* i) { /* ... */ }
> >>
> >> Now what? There are no "methods" for ints, only UFCS. Those
> >> functions can be as safe as you like, but if you allow
> >> implicit promotion of int* to shared int*, you *allow implicit
> >> races*.
> >
> > This function is effectively an intrinsic. It's unsafe by
> > definition.
>
> Only if implicit conversion is allowed. If it isn't, that's
> likely @trusted, and this:
>
> void atomicInc(ref shared int);
>
> can even be @safe.

In this case, with respect to the context (a single int) atomicInc()
is ALWAYS safe, even with implicit conversion. You can atomicInc() a
thread-local int perfectly safely.

> > It's a tool for implementing threadsafe machinery.
> > No user can just start doing atomic operations on random ints
> > and say
> > "it's threadsafe", you must encapsulate the threadsafe
> > functionality
> > into some sort of object that aggregates all concerns and
> > presents an
> > intellectually sound api.
>
> Threadsafety starts and ends with the programmer. By your logic
> *all* functions operating on `shared` are unsafe then. As far as
> compiler is concerned, there would be no difference between these
> two:
>
> struct S {}
> void atomicInc(ref shared S);
>
> and
>
> struct S { void atomicInc() shared { /* ... */ } }
>
> The signatures of those two functions are exactly the same. How
> is that different from a function taking a shared int pointer or
> reference?

It's not, atomicInc() of an int is always safe with respect to the int itself.
You can call atomicInc() on an unshared int and it's perfectly fine,
but now you need to consider context, and that's a problem for the
design of the higher-level scope.

To maintain thread-safety, the int in question must be appropriately contained.

The problem is that the same as the example I presented before, which
I'll repeat:

struct InvalidProgram
{
  int x;
  void fun() { ++x; }
  void gun() shared { atomicInc(&x); }
}

The method gun() (and therefore the whole object) is NOT threadsafe by
my definition, because fun() violates the threadsafety of gun().
The situation applies equally here that:
int x;
atomicInc(&x);
++x; // <- by my definition, this 'API' (increment an int) violates
the threadsafety of atomicInc(), and atomicInc() is therefore not
threadsafe.

`int` doesn't present a threadsafe API, so int is by definition, NOT
threadsafe. atomicInc() should be @system, and not @trusted.

If you intend to share an int, use Atomic!int, because it has a threadsafe API.
atomicInc(shared int*) is effectively just an unsafe intrinsic, and
its only use is at ground-level implementation of threadsafe
machinery, like malloc() and free().

> > Let me try one:
> >
> > void free(void*) { ... }
> >
> > Now what? I might have dangling pointers... it's a catastrophe!
>
> One could argue that it should be void free(ref void* p) { /* ...
> */ p = null; }

void *p2 = p;
free(p);
p2.crash();

> As a matter of fact, in my own allocators memory blocks allocated
> by them are passed by value and are non-copyable, they're not
> just void[] as in std.experimental.allocator. One must 'move'
> them to pass ownership, and that includes deallocation. But
> that's another story altogether.

Right, now you're talking about move semantics to implement transfer
of ownership... you might recall I was arguing this exact case to
express transferring ownership of objects between threads earlier.
This talk of blunt casts and "making sure everything is good" is all
just great, but it doesn't mean anything interesting with respect to
`shared`. It should be interesting even without unsafe casts.

> > It's essentially the same argument.
> > This isn't a function that professes to do something that
> > people might
> > misunderstand and try to use in an unsafe way, it's a low-level
> > implementation device, which is used to build larger *useful*
> > constructs.
>
> You're missing the point, again. You have an int. You pass a
> pointer to it to some API that takes an int*. You continue to use
> your int as just an int.

You have written an invalid program. I can think of an infinite number
of ways to write an invalid program.
In this case, don't have an `int`, instead, have an Atomic!int; you
now guarantee appropriate access, problem solved!
If you do have an int, don't pass it to other threads at random when
you don't have any idea what they intend to do with it! That's basic
common sense. Yo

Re: shared - i need it to be useful

2018-10-17 Thread Stanislav Blinov via Digitalmars-d

On Wednesday, 17 October 2018 at 23:12:48 UTC, Manu wrote:
On Wed, Oct 17, 2018 at 2:15 PM Stanislav Blinov via 
Digitalmars-d  wrote:


On Wednesday, 17 October 2018 at 19:25:33 UTC, Manu wrote:
> On Wed, Oct 17, 2018 at 12:05 PM Stanislav Blinov via 
> Digitalmars-d  wrote:

>>
>> On Wednesday, 17 October 2018 at 18:46:18 UTC, Manu wrote:
>>
>> > I've said this a bunch of times, there are 2 rules:
>> > 1. shared inhibits read and write access to members
>> > 2. `shared` methods must be threadsafe
>> >
>> >>From there, shared becomes interesting and useful.
>>
>> Oh God...
>>
>> void atomicInc(shared int* i) { /* ... */ }
>>
>> Now what? There are no "methods" for ints, only UFCS. Those 
>> functions can be as safe as you like, but if you allow 
>> implicit promotion of int* to shared int*, you *allow 
>> implicit races*.

>
> This function is effectively an intrinsic. It's unsafe by 
> definition.


Only if implicit conversion is allowed. If it isn't, that's 
likely @trusted, and this:


void atomicInc(ref shared int);

can even be @safe.


In this case, with respect to the context (a single int) 
atomicInc()
is ALWAYS safe, even with implicit conversion. You can 
atomicInc() a

thread-local int perfectly safely.


Yes, *you* can. *Another* function can't unless *you* allow for 
it to be safe. You can't do that if that function silently 
assumes you gave it shared data, when in fact you did not.


The signatures of those two functions are exactly the same. 
How is that different from a function taking a shared int 
pointer or reference?


It's not, atomicInc() of an int is always safe with respect to 
the int itself.
You can call atomicInc() on an unshared int and it's perfectly 
fine,
but now you need to consider context, and that's a problem for  
the

design of the higher-level scope.

To maintain thread-safety, the int in question must be 
appropriately contained.


Exactly. And that means it can't convert to shared without my say 
so :)


The problem is that the same as the example I presented before, 
which I'll repeat:


struct InvalidProgram
{
  int x;
  void fun() { ++x; }
  void gun() shared { atomicInc(&x); }
}

The method gun() (and therefore the whole object) is NOT 
threadsafe by

my definition, because fun() violates the threadsafety of gun().
The situation applies equally here that:
int x;
atomicInc(&x);
++x; // <- by my definition, this 'API' (increment an int) 
violates the threadsafety of atomicInc(), and atomicInc() is 
therefore not threadsafe.


No. The 'API' is just the atomicInc function. You, the user of 
that API, own the int. If the API wants a shared int from you, 
you have to be in agreement. You can't have any agreement if the 
API is only making promises and assumptions.


`int` doesn't present a threadsafe API, so int is by 
definition, NOT threadsafe. atomicInc() should be @system, and 
not @trusted.


Exactly. `int` isn't threadsafe and therefore cannot 
automatically convert to `shared int`.


If you intend to share an int, use Atomic!int, because it has a 
threadsafe API.


No. With current implementation of `shared`, which disallows your 
automatic promotion,
your intent is enforced. You cannot share a local `int` unless 
*you know* it's safe to do so and therefore can cast that int to 
shared.


atomicInc(shared int*) is effectively just an unsafe intrinsic, 
and


It is only unsafe if you allow int* to silently convert to shared 
int*. If you can't do that, you can't call `atomicInc` on an int*.


One could argue that it should be void free(ref void* p) { /* 
... */ p = null; }



void *p2 = p;
free(p);
p2.crash();


That's exactly analogous to what you're proposing: leaking 
`shared` references while keeping unshared data.


As a matter of fact, in my own allocators memory blocks 
allocated by them are passed by value and are non-copyable, 
they're not just void[] as in std.experimental.allocator. One 
must 'move' them to pass ownership, and that includes 
deallocation. But that's another story altogether.


Right, now you're talking about move semantics to implement 
transfer of ownership... you might recall I was arguing this 
exact case to express transferring ownership of objects between 
threads earlier.


You went off on an irrelevant tangent there, and I feel like you 
didn't even see my reply. You don't pass any ownership when you 
share. You just share. As an owner, you get to access the 
un-`shared` interface freely. Others do not.


This talk of blunt casts and "making sure everything is good" 
is all just great, but it doesn't mean  anything interesting 
with respect to `shared`. It should be interesting even without 
unsafe casts.


Again, I feel like you didn't see my reply. It's not talk about 
just blunt casts to make sure everything is good. You either have 
shared data to begin with, and so can share it freely, or you 
*know* that you can share this particular piece of data, even if 
it itself isn't marked `shared`, and you *assert* that by 
deli

Re: shared - i need it to be useful

2018-10-17 Thread Erik van Velzen via Digitalmars-d
I don't have anything to add that hasn't been said yet but it's 
good to see some thinking on this subject. It feels like progress.


Re: shared - i need it to be useful

2018-10-17 Thread Steven Schveighoffer via Digitalmars-d

On 10/17/18 6:37 PM, Manu wrote:

On Wed, Oct 17, 2018 at 12:35 PM Steven Schveighoffer via
Digitalmars-d  wrote:


On 10/17/18 2:46 PM, Manu wrote:

On Wed, Oct 17, 2018 at 10:30 AM Steven Schveighoffer via



What the example demonstrates is that while you are trying to disallow
implicit casting of a shared pointer to an unshared pointer, you have
inadvertently allowed it by leaving behind an unshared pointer that is
the same thing.


This doesn't make sense... you're showing a thread-local program.
The thread owning the unshared pointer is entitled to the unshared
pointer. It can make as many copies at it likes. They are all
thread-local.


It's assumed that shared int pointer can be passed to another thread,
right? Do I have to write a full program to demonstrate?


And that shared(int)* provides no access. No other thread with that
pointer can do anything with it.


So then it's a misnomer -- it's not really shared, because I can't do 
anything with it.





There's only one owning thread, and you can't violate that without unsafe casts.


The what is the point of shared? Like why would you share data that
NOBODY CAN USE?


You can call shared methods. They promise threadsafety.
That's a small subset of the program, but that's natural; only a very
small subset of the program is safe to be called from a shared
context.


All I can see is that a shared method promises to be callable on shared 
or unshared data. In essence, it promises nothing.


It's the programmer who must implement the thread safety, and there 
really is no help at all from the compiler for this. At some level, 
there will be either casts, or intrinsics, both of which are unsafe 
without knowing all the context of the object. In any case, it's simply 
a false guarantee of thread safety, which might as well be a convention 
of "any function which starts with TS_ is supposed to be thread safe".


shared in the current form promises one thing and one thing only -- data 
marked as shared is actually sharable between threads, and data not 
marked as shared is actually not shared between threads. This new regime 
you are proposing does nothing extra or new, except break that guarantee.



At SOME POINT, shared data needs to be readable and writable. Any
correct system is going to dictate how that works. It's a good start to
make shared data unusable unless you cast. But then to make it
implicitly castable from unshared defeats the whole purpose.


No. No casting! This is antiquated workflow.. I'm not trying to take
it away from you, but it's not an interesting model for the future.
`shared` can model more than just that.
You can call threadsafe methods. Shared methods explicitly dictate how
the system works, and in a very clear and obvious/intuitive way.

The implicit cast makes using threadsafe objects more convenient when
you only have one, which is extremely common.


The implicit cast means that you have to look at more than just your 
method. You have to look at the entire module, and figure out all the 
interactions, to see if the thread safe method actually is thread safe. 
That's programming by convention, and fully trusting the programmer.


I don't think this thread is going anywhere, so I'll just have to wait 
and see if someone else can explain it better. I'm a firm no on implicit 
casting from mutable to shared.


-Steve


Re: shared - i need it to be useful

2018-10-17 Thread Manu via Digitalmars-d
On Wed, Oct 17, 2018 at 5:35 PM Stanislav Blinov via Digitalmars-d
 wrote:
>
> On Wednesday, 17 October 2018 at 23:12:48 UTC, Manu wrote:
> > On Wed, Oct 17, 2018 at 2:15 PM Stanislav Blinov via
> > Digitalmars-d  wrote:
> >>
> >> On Wednesday, 17 October 2018 at 19:25:33 UTC, Manu wrote:
> >> > On Wed, Oct 17, 2018 at 12:05 PM Stanislav Blinov via
> >> > Digitalmars-d  wrote:
> >> >>
> >> >> On Wednesday, 17 October 2018 at 18:46:18 UTC, Manu wrote:
> >> >>
> >> >> > I've said this a bunch of times, there are 2 rules:
> >> >> > 1. shared inhibits read and write access to members
> >> >> > 2. `shared` methods must be threadsafe
> >> >> >
> >> >> >>From there, shared becomes interesting and useful.
> >> >>
> >> >> Oh God...
> >> >>
> >> >> void atomicInc(shared int* i) { /* ... */ }
> >> >>
> >> >> Now what? There are no "methods" for ints, only UFCS. Those
> >> >> functions can be as safe as you like, but if you allow
> >> >> implicit promotion of int* to shared int*, you *allow
> >> >> implicit races*.
> >> >
> >> > This function is effectively an intrinsic. It's unsafe by
> >> > definition.
> >>
> >> Only if implicit conversion is allowed. If it isn't, that's
> >> likely @trusted, and this:
> >>
> >> void atomicInc(ref shared int);
> >>
> >> can even be @safe.
> >
> > In this case, with respect to the context (a single int)
> > atomicInc()
> > is ALWAYS safe, even with implicit conversion. You can
> > atomicInc() a
> > thread-local int perfectly safely.
>
> Yes, *you* can. *Another* function can't unless *you* allow for
> it to be safe. You can't do that if that function silently
> assumes you gave it shared data, when in fact you did not.
>
> >> The signatures of those two functions are exactly the same.
> >> How is that different from a function taking a shared int
> >> pointer or reference?
> >
> > It's not, atomicInc() of an int is always safe with respect to
> > the int itself.
> > You can call atomicInc() on an unshared int and it's perfectly
> > fine,
> > but now you need to consider context, and that's a problem for
> > the
> > design of the higher-level scope.
> >
> > To maintain thread-safety, the int in question must be
> > appropriately contained.
>
> Exactly. And that means it can't convert to shared without my say
> so :)

It can though, because `int` doesn't have any shared methods; it's
inaccessible. You can't do anything with a shared int, so it's safe to
distribute regardless.

> > The problem is that the same as the example I presented before,
> > which I'll repeat:
> >
> > struct InvalidProgram
> > {
> >   int x;
> >   void fun() { ++x; }
> >   void gun() shared { atomicInc(&x); }
> > }
> >
> > The method gun() (and therefore the whole object) is NOT
> > threadsafe by
> > my definition, because fun() violates the threadsafety of gun().
> > The situation applies equally here that:
> > int x;
> > atomicInc(&x);
> > ++x; // <- by my definition, this 'API' (increment an int)
> > violates the threadsafety of atomicInc(), and atomicInc() is
> > therefore not threadsafe.
>
> No. The 'API' is just the atomicInc function. You, the user of
> that API, own the int. If the API wants a shared int from you,
> you have to be in agreement. You can't have any agreement if the
> API is only making promises and assumptions.

Okay, here's an idea... atomicInc() should NOT take `shared int*`,
change atomicInt() to receive int*.
We agree that `int` is not a threadsafe type, atomicInc() is not a
threadsafe method, and therefore shouldn't be shared.

If you have a shared(int)*, and you want to do un-threadsafe
atomicInc(), you need to cast to int*.
I think this is actually correct by my definition, because we
recognise int is not a threadsafe type, and atomicInc() is not a
threadsafe method, so hard-cast is required, and programmer must
appropriately validate the context when doing an unsafe operation.

> > `int` doesn't present a threadsafe API, so int is by
> > definition, NOT threadsafe. atomicInc() should be @system, and
> > not @trusted.
>
> Exactly. `int` isn't threadsafe and therefore cannot
> automatically convert to `shared int`.

It can convert to shared int, because it's inaccessible... you can't
do anything with a `shared int` except unsafe cast shared away. Once
you're in unsafe land, you are responsible for assuring correct
conditions.

> > If you intend to share an int, use Atomic!int, because it has a
> > threadsafe API.
>
> No. With current implementation of `shared`, which disallows your
> automatic promotion,
> your intent is enforced. You cannot share a local `int` unless
> *you know* it's safe to do so and therefore can cast that int to
> shared.

Yes, but we're not talking about current definition of shared. Stop
confusing the conversation.
This thread is about the rules I define.

> > atomicInc(shared int*) is effectively just an unsafe intrinsic,
> > and
>
> It is only unsafe if you allow int* to silently convert to shared
> int*. If you can't do that, you can't call `atomicInc` on an

Re: shared - i need it to be useful

2018-10-17 Thread Joakim via Digitalmars-d

On Wednesday, 17 October 2018 at 23:12:48 UTC, Manu wrote:
On Wed, Oct 17, 2018 at 2:15 PM Stanislav Blinov via 
Digitalmars-d  wrote:


On Wednesday, 17 October 2018 at 19:25:33 UTC, Manu wrote:
> On Wed, Oct 17, 2018 at 12:05 PM Stanislav Blinov via 
> Digitalmars-d  wrote:

>>
>> On Wednesday, 17 October 2018 at 18:46:18 UTC, Manu wrote:
>>
>> > I've said this a bunch of times, there are 2 rules:
>> > 1. shared inhibits read and write access to members
>> > 2. `shared` methods must be threadsafe
>> >
>> >>From there, shared becomes interesting and useful.
>>
>> Oh God...
>>
>> void atomicInc(shared int* i) { /* ... */ }
>>
>> Now what? There are no "methods" for ints, only UFCS. Those 
>> functions can be as safe as you like, but if you allow 
>> implicit promotion of int* to shared int*, you *allow 
>> implicit races*.

>
> This function is effectively an intrinsic. It's unsafe by 
> definition.


Only if implicit conversion is allowed. If it isn't, that's 
likely @trusted, and this:


void atomicInc(ref shared int);

can even be @safe.


In this case, with respect to the context (a single int) 
atomicInc()
is ALWAYS safe, even with implicit conversion. You can 
atomicInc() a

thread-local int perfectly safely.


> It's a tool for implementing threadsafe machinery.
> No user can just start doing atomic operations on random ints
> and say
> "it's threadsafe", you must encapsulate the threadsafe
> functionality
> into some sort of object that aggregates all concerns and
> presents an
> intellectually sound api.

Threadsafety starts and ends with the programmer. By your 
logic *all* functions operating on `shared` are unsafe then. 
As far as compiler is concerned, there would be no difference 
between these two:


struct S {}
void atomicInc(ref shared S);

and

struct S { void atomicInc() shared { /* ... */ } }

The signatures of those two functions are exactly the same. 
How is that different from a function taking a shared int 
pointer or reference?


It's not, atomicInc() of an int is always safe with respect to 
the int itself.
You can call atomicInc() on an unshared int and it's perfectly 
fine,
but now you need to consider context, and that's a problem for 
the

design of the higher-level scope.

To maintain thread-safety, the int in question must be 
appropriately contained.


The problem is that the same as the example I presented before, 
which I'll repeat:


struct InvalidProgram
{
  int x;
  void fun() { ++x; }
  void gun() shared { atomicInc(&x); }
}

The method gun() (and therefore the whole object) is NOT 
threadsafe by

my definition, because fun() violates the threadsafety of gun().
The situation applies equally here that:
int x;
atomicInc(&x);
++x; // <- by my definition, this 'API' (increment an int) 
violates
the threadsafety of atomicInc(), and atomicInc() is therefore 
not

threadsafe.

`int` doesn't present a threadsafe API, so int is by 
definition, NOT threadsafe. atomicInc() should be @system, and 
not @trusted.


If you intend to share an int, use Atomic!int, because it has a 
threadsafe API.
atomicInc(shared int*) is effectively just an unsafe intrinsic, 
and

its only use is at ground-level implementation of threadsafe
machinery, like malloc() and free().


> Let me try one:
>
> void free(void*) { ... }
>
> Now what? I might have dangling pointers... it's a 
> catastrophe!


One could argue that it should be void free(ref void* p) { /* 
...

*/ p = null; }


void *p2 = p;
free(p);
p2.crash();

As a matter of fact, in my own allocators memory blocks 
allocated by them are passed by value and are non-copyable, 
they're not just void[] as in std.experimental.allocator. One 
must 'move' them to pass ownership, and that includes 
deallocation. But that's another story altogether.


Right, now you're talking about move semantics to implement 
transfer of ownership... you might recall I was arguing this 
exact case to express transferring ownership of objects between 
threads earlier. This talk of blunt casts and "making sure 
everything is good" is all just great, but it doesn't mean 
anything interesting with respect to `shared`. It should be 
interesting even without unsafe casts.



> It's essentially the same argument.
> This isn't a function that professes to do something that
> people might
> misunderstand and try to use in an unsafe way, it's a 
> low-level

> implementation device, which is used to build larger *useful*
> constructs.

You're missing the point, again. You have an int. You pass a 
pointer to it to some API that takes an int*. You continue to 
use your int as just an int.


You have written an invalid program. I can think of an infinite 
number

of ways to write an invalid program.
In this case, don't have an `int`, instead, have an Atomic!int; 
you

now guarantee appropriate access, problem solved!
If you do have an int, don't pass it to other threads at random 
when
you don't have any idea what they intend to do with it! That's 
basic
common sense. You don't 

Re: shared - i need it to be useful

2018-10-17 Thread Manu via Digitalmars-d
On Wed, Oct 17, 2018 at 6:50 PM Steven Schveighoffer via Digitalmars-d
 wrote:
>
> On 10/17/18 6:37 PM, Manu wrote:
> > On Wed, Oct 17, 2018 at 12:35 PM Steven Schveighoffer via
> > Digitalmars-d  wrote:
> >>
> >> On 10/17/18 2:46 PM, Manu wrote:
> >>> On Wed, Oct 17, 2018 at 10:30 AM Steven Schveighoffer via
> >>
>  What the example demonstrates is that while you are trying to disallow
>  implicit casting of a shared pointer to an unshared pointer, you have
>  inadvertently allowed it by leaving behind an unshared pointer that is
>  the same thing.
> >>>
> >>> This doesn't make sense... you're showing a thread-local program.
> >>> The thread owning the unshared pointer is entitled to the unshared
> >>> pointer. It can make as many copies at it likes. They are all
> >>> thread-local.
> >>
> >> It's assumed that shared int pointer can be passed to another thread,
> >> right? Do I have to write a full program to demonstrate?
> >
> > And that shared(int)* provides no access. No other thread with that
> > pointer can do anything with it.
>
> So then it's a misnomer -- it's not really shared, because I can't do
> anything with it.

**EXACTLY**.. this is the key to the entire design that allows for the
implicit promotion.

> >>> There's only one owning thread, and you can't violate that without unsafe 
> >>> casts.
> >>
> >> The what is the point of shared? Like why would you share data that
> >> NOBODY CAN USE?
> >
> > You can call shared methods. They promise threadsafety.
> > That's a small subset of the program, but that's natural; only a very
> > small subset of the program is safe to be called from a shared
> > context.
>
> All I can see is that a shared method promises to be callable on shared
> or unshared data. In essence, it promises nothing.

How is that nothing? It promises threadsafety, such that it can be
called on shared data. Normally, you can do NOTHING with shared data
short of unsafe operations.
It is always safe to call a threadsafe function on an unshared thing,
so it should be possible.
`shared` opts into a strong commitment to threadsafety that allows for
a shared thing to interact with it, otherwise, no interaction is
possible.

I don't understand how you can claim it promises nothing; this is an
extremely strong promise!

> It's the programmer who must implement the thread safety, and there
> really is no help at all from the compiler for this.

I hope we can work on this moving forwards. There are probably some
things we can do... but even without compiler support, in reality,
there are probably only a small number of core pieces of tooling, but
most authors of a shared thing will just aggregate core tools and call
through to their API's.
Most shared code will use tools written by experts, and implement
nothing fancy or magical themselves.

> At some level,
> there will be either casts, or intrinsics, both of which are unsafe
> without knowing all the context of the object.

This will tend to be in the core tooling, written by an expert.
The whole point of my design is to remove friction from the USERS of
those tools, such that they can create aggregate functionality safely
without doing anything unsafe.

> In any case, it's simply
> a false guarantee of thread safety, which might as well be a convention
> of "any function which starts with TS_ is supposed to be thread safe".

What makes it a false guarantee? It allows you to have confidence in
the stack built on top of some core tools.
The whole point here is to minimise the number of people writing code
like that. I'm trying to make shared generally useful so that people
don't have to engage with it at a low-level.

If you see an unsafe case in a shared function (beyond the core
tooling), then you should immediately be suspicious, and this is a
deliberate part of my goal here. The point here is to produce safer
and more reliable code by defining access rules that lead to confident
threadsafety.

What you describe is what we have now.

> shared in the current form promises one thing and one thing only -- data
> marked as shared is actually sharable between threads, and data not
> marked as shared is actually not shared between threads.

Ummm. The current form might *say* that, but you still have full
unregulated access to all members, and it's completely unsafe, with no
attempt to mitigate that.
This is a new definition. Take it for what it's worth. The old
definition is mostly worthless.
I'm trying to make something that's interesting and useful.

> This new regime you are proposing does nothing extra or new, except break 
> that guarantee.

There are heaps of advantages:
1. If I have a shared thing, I know I can't ruin its state by
manipulating it arbitrarily like I can now
2. I am now able to describe threadsafe objects
3. I relieve users from mental strain of trying to understand how to
correctly and safely interact with shared API's, because they can have
confidence that a shared thing can only perform threadsafe a

  1   2   3   >