August 12, 2016
On 08/12/2016 04:06 PM, Shachar Shemesh wrote:
> On 12/08/16 22:55, Walter Bright wrote:
>>
>> I'm surprised that I've never seen anyone else use such a technique.
>> It's so unusual I've seen it be unrepresentable in some 'make'
>> replacements.
>>
>> I suppose it's like unittest and ddoc. Sure, you can do it with some
>> contortions and/or some external tooling, but having it conveniently
>> built in to the language changes everything.
>>
>
> Actually, even with it being easilly accessible in the compiler, there
> are sometimes reasons to still do it with an external tool.
>
> My next task at work requires precomputing a table. This might prove to
> be a quite intensive task, and the result will change quite rarely.
> Under those circumstances, I believe I will not wish to compute it each
> time the system is compiled, but to compute it when it changes and use a
> cached version.

I wonder if you can arrange things such that a compile-time table is computed only upon a change in the source file. I mean:

// table.di
extern double[] table;

// table.d
double[] table = makeComplicatedTable();

double[1024] makeComplicatedTable() {
   ... compile-time computation ...
}

People use the interface file table.di. The complicated computation goes in table.o which is generated only when table.d is changed.


Andrei

August 12, 2016
On 08/12/2016 09:41 PM, ag0aep6g wrote:
> So, `shared int x; x = x + 1;` is ok, as far as I see now. But with
> other types, unsafe reads/writes are generated.

Missing alignment can also throw the compiler off:

----
alias T = int;

enum T a = 0x01_01_01_01;
enum T b = 0x02_02_02_02;

struct S
{
    byte[13] padding;
    align(1) T x = a;
}

shared S s;

import core.thread: Thread;

void write()
{
    bool flip = false;
    foreach (i; 0 .. 1_000_000)
    {
        if (flip) s.x = a; else s.x = b;
        flip = !flip;
    }
}

void main()
{
    auto t = new Thread(&write);
    t.start();
    foreach (i; 0 .. 1_000_000)
    {
        T r = s.x;
        assert(r == a || r == b); // fails
    }
    t.join();
}
----
August 12, 2016
On 8/12/16 2:04 PM, Andrei Alexandrescu wrote:
> On 08/12/2016 01:21 PM, Steven Schveighoffer wrote:
>> On 8/12/16 1:04 PM, Jonathan M Davis via Digitalmars-d wrote:
>>>
>>> Honestly, I don't think that shared is broken.
>>
>> Yes. It is broken.
>>
>> shared int x;
>> ++x; // error, must use atomicOp.
>> x = x + 1; // OK(!)
>
> How is this broken and how should it behave? -- Andrei
>

It's broken because it's inconsistent. If the compiler is going to complain about races in one case, but not in other equivalent cases, then the feature is useless.

If all I have to do to avoid compiler complaints is rewrite my expression in an equivalent way, then what is the point of the complaint? At that point, it's just a style guide.

What should it do? If I knew that, then I'd have proposed a DIP by now. It's not an easy problem to solve. I don't think you can "solve" races because the compiler can't see all interactions with data.

At first glance, it seems that shared data shouldn't be usable without some kind of explicit usage syntax. x = x + 1 is too innocuous looking. It's not going to "solve" the issue, but it makes the code easier to pick out.

-Steve
August 12, 2016
On 8/12/16 6:58 PM, Steven Schveighoffer wrote:
> On 8/12/16 2:04 PM, Andrei Alexandrescu wrote:
>> On 08/12/2016 01:21 PM, Steven Schveighoffer wrote:
>>> On 8/12/16 1:04 PM, Jonathan M Davis via Digitalmars-d wrote:
>>>>
>>>> Honestly, I don't think that shared is broken.
>>>
>>> Yes. It is broken.
>>>
>>> shared int x;
>>> ++x; // error, must use atomicOp.
>>> x = x + 1; // OK(!)
>>
>> How is this broken and how should it behave? -- Andrei
>>
>
> It's broken because it's inconsistent. If the compiler is going to
> complain about races in one case, but not in other equivalent cases,
> then the feature is useless.

But ++expr and expr1 = expr1 + expr2 are fundamentally different.

> If all I have to do to avoid compiler complaints is rewrite my
> expression in an equivalent way, then what is the point of the
> complaint? At that point, it's just a style guide.

A bunch of rewrites to seemingly identical behavior to avoid type errors are de rigoeur in so many situations. This is no different.

> What should it do? If I knew that, then I'd have proposed a DIP by now.
> It's not an easy problem to solve. I don't think you can "solve" races
> because the compiler can't see all interactions with data.

++expr contains a low-level race that is worth removing. Extending that to a variety of sub-patterns of expr1 = expr2 + expr3 seems a terrible idea to me.

> At first glance, it seems that shared data shouldn't be usable without
> some kind of explicit usage syntax. x = x + 1 is too innocuous looking.
> It's not going to "solve" the issue, but it makes the code easier to
> pick out.

We discussed this at a point (some 10 years ago). It was:

atomicReadRef(x) = atomicRead(x) + 1;

But it's painfully obvious that yes the intent is to read the address and read the thing... so why need these anyway? So we left things as they are.

I agree shared needs work, but this is not it. We're wasting our time here.


Andrei

August 13, 2016
On Friday, 12 August 2016 at 08:59:14 UTC, Patrick Schluter wrote:
> I never understood how people can get their feelings hurt when criticising the language they program in. Either the criticised point is true and one accepts it or not, in that case a factual refutation can be done. Feeling offended is ridiculous.
>

http://www.independent.co.uk/arts-entertainment/tv/news/millennials-outraged-over-tv-show-portraying-millennials-as-outraged-a7184771.html

August 13, 2016
On 08/12/2016 08:21 PM, Andrei Alexandrescu wrote:
> Currently the compiler must ensure that an atomic read and an atomic
> write are generated for x.

Would be nice if that could be added to the spec. It's awfully quiet about shared right now.
August 13, 2016
On Friday, 12 August 2016 at 22:58:45 UTC, Steven Schveighoffer wrote:
> It's broken because it's inconsistent. If the compiler is going to complain about races in one case, but not in other equivalent cases, then the feature is useless.
>

If anything, it guarantee you that this kind of things will NEVER happen if not shared, which is in itself a huge benefit.

> If all I have to do to avoid compiler complaints is rewrite my expression in an equivalent way, then what is the point of the complaint? At that point, it's just a style guide.
>

The ways are equivalent only in the absence of race condition on a. This is not guaranteed for shared.

August 13, 2016
On 8/12/16 7:22 PM, Andrei Alexandrescu wrote:
> On 8/12/16 6:58 PM, Steven Schveighoffer wrote:
>> On 8/12/16 2:04 PM, Andrei Alexandrescu wrote:
>>> On 08/12/2016 01:21 PM, Steven Schveighoffer wrote:
>>>> On 8/12/16 1:04 PM, Jonathan M Davis via Digitalmars-d wrote:
>>>>>
>>>>> Honestly, I don't think that shared is broken.
>>>>
>>>> Yes. It is broken.
>>>>
>>>> shared int x;
>>>> ++x; // error, must use atomicOp.
>>>> x = x + 1; // OK(!)
>>>
>>> How is this broken and how should it behave? -- Andrei
>>>
>>
>> It's broken because it's inconsistent. If the compiler is going to
>> complain about races in one case, but not in other equivalent cases,
>> then the feature is useless.
>
> But ++expr and expr1 = expr1 + expr2 are fundamentally different.

It's not expr2, it's 1.

And no they aren't fundamentally different. Don't think like a compiler.

>> If all I have to do to avoid compiler complaints is rewrite my
>> expression in an equivalent way, then what is the point of the
>> complaint? At that point, it's just a style guide.
>
> A bunch of rewrites to seemingly identical behavior to avoid type errors
> are de rigoeur in so many situations. This is no different.

It's not a type error. We have const, and pure, and @safe, which require you to write code in a way that enforces a guarantee.

shared does this in a half-assed way. It's not effective. It's like C++ const is not effective at guaranteeing anything stays constant.

>> What should it do? If I knew that, then I'd have proposed a DIP by now.
>> It's not an easy problem to solve. I don't think you can "solve" races
>> because the compiler can't see all interactions with data.
>
> ++expr contains a low-level race that is worth removing. Extending that
> to a variety of sub-patterns of expr1 = expr2 + expr3 seems a terrible
> idea to me.

I agree that in general races cannot be detected and prevented. ++expr1 is an obvious race, and it is detected and prevented. But it gives the impression that the compiler isn't going to let you make races. It gives the impression that the language has some level of guarantee about avoiding multi-threaded mistakes. It doesn't. It doesn't guarantee anything. It's just a helpful trick in one specific case.

I don't know what the right way to handle shared is. My opinion is simply that shared needs special handling from the user. The compiler isn't going to help you make thread-safe code, but at least allows you to mark where the unsafe code could be (by using shared type modifier).

>> At first glance, it seems that shared data shouldn't be usable without
>> some kind of explicit usage syntax. x = x + 1 is too innocuous looking.
>> It's not going to "solve" the issue, but it makes the code easier to
>> pick out.
>
> We discussed this at a point (some 10 years ago). It was:
>
> atomicReadRef(x) = atomicRead(x) + 1;
>
> But it's painfully obvious that yes the intent is to read the address
> and read the thing... so why need these anyway? So we left things as
> they are.
>
> I agree shared needs work, but this is not it. We're wasting our time here.

Hey, that's fine. I'd rather work on other things too. Shared just seems like an unfulfilled promise, with half-implemented protections.

I'd like to just see shared be unusable, and make people cast away shared to do anything. That at least is an attempt at preventing races without intention. The allowance of certain things that are obvious races, whereas other things that are obvious races are prevented makes one assume the worst.

-Steve
August 13, 2016
On Saturday, August 13, 2016 16:45:16 Steven Schveighoffer via Digitalmars-d wrote:
> Hey, that's fine. I'd rather work on other things too. Shared just seems like an unfulfilled promise, with half-implemented protections.
>
> I'd like to just see shared be unusable, and make people cast away shared to do anything. That at least is an attempt at preventing races without intention. The allowance of certain things that are obvious races, whereas other things that are obvious races are prevented makes one assume the worst.

I'm also tempted to argue that making shared virtually unusable without casting it away would be a good idea - particularly when you consider that most code that uses shared is going to need to cast it away to do anything with it (even if we had synchronized classes, that would still be the case - it would just be that under some circumstances, the compiler would do it for you). Maybe we don't want to go quite to the point that you have to cast away shared to do anything other than pass it around (we probably have to have assignment still not involve casting even though that would normally need to be protected by a lock), but I do think that that's the direction that we shoudld be leaning in.

However, it sounds like Walter wants to postpone discussing this seriously until after scope is sorted out. I think that it's clear that Walter and Andrei agree that some work needs to be done on formalizing shared, and maybe it should be pushed soon, but it sounds like we should wait until after this DIP is sorted out to really get into it. It _is_ one area that we need to make sure that we have done though. I think that the basics of how shared has been done are right, but we do need to make sure that the details are sorted out correctly and that it's clear how shared should be used, since as it is, way too many folks just slap __gshared on stuff if they want it to be shared (which is almost always the wrong thing to do).

- Jonathan M Davis

August 13, 2016
On 8/13/2016 2:57 PM, Jonathan M Davis via Digitalmars-d wrote:
> I'm also tempted to argue that making shared virtually unusable without
> casting it away would be a good idea - particularly when you consider that
> most code that uses shared is going to need to cast it away to do anything
> with it (even if we had synchronized classes, that would still be the case -
> it would just be that under some circumstances, the compiler would do it for
> you).

I suspect that's what the end result will be with shared. Anything else just seems like an illusion of correctness.


> However, it sounds like Walter wants to postpone discussing this seriously
> until after scope is sorted out.

Yes.


> way too many folks just slap __gshared on stuff if they want
> it to be shared (which is almost always the wrong thing to do).

That's why it's not allowed in @safe code.