July 13, 2016
On Wednesday, 13 July 2016 at 00:03:04 UTC, Walter Bright wrote:
> On 7/12/2016 6:13 AM, John Colvin wrote:
>> On Tuesday, 12 July 2016 at 10:19:04 UTC, Walter Bright wrote:
>>> On 7/12/2016 2:40 AM, John Colvin wrote:
>>>> For the previous statement to be false, you must define cases where
>>>> casting away immutability *is* defined.
>>>
>>> @system programming is, by definition, operating outside of the
>>> language guarantees of what happens. It's up to you, the systems
>>> programmer, to know what you're doing there.
>>
>> This is so, so wrong. There's a world of difference between "you have to
>> get this right or you're in trouble" and "the compiler (and especially
>> the optimiser) is free to assume that what you're doing never happens".
>> Undefined behaviour, as used in practice by modern optimising compilers,
>> is in the second camp. You might have a different definition, but it's
>> not the one everyone else is using and not the one that our two fastest
>> backends understand.
>>
>> Given the definition of undefined behaviour that everyone else
>> understands, do you actually mean "modifying immutable data by any means
>> is undefined behaviour" instead of "casting away immutable is undefined
>> behaviour"?
>
> What's the difference?

"Casting away immutable is undefined behaviour": the following code has undefined results (note, not implementation defined, not if-you-know-what-you're-doing defined, undefined), despite not doing anything much:

void foo()
{
    immutable a = new int;
    auto b = cast(int*)a;
}

"modifying immutable data is undefined": The above code is fine, but the following is still undefined:

void foo()
{
    immutable a = new int;
    auto b = cast(int*)a;
    b = 3;
}

> Anyhow, if you cast away immutability, and the data exists in rom, you still can't write to it (you'll get a seg fault). If it is in mutable memory, you can change it, but other threads may be caching or reading the value while you do that, i.e. synchronization issues. The optimizer may be taking advantage of immutability in its semantic transformations.
>
> As a systems programmer, you'd have to account for that.

Something like "it might be in ROM" is an implementation detail. It's the optimiser part that forces a formal decision about what is undefined behaviour or not.


Implementation defined behaviour is in the realm of the "systems programming, be careful you know what you're doing". Undefined behaviour is a different beast.
July 13, 2016
On Wednesday, 13 July 2016 at 09:19:29 UTC, John Colvin wrote:
> Something like "it might be in ROM" is an implementation detail. It's the optimiser part that forces a formal decision about what is undefined behaviour or not.

«Undefined» simply means that such code is not part of the specified language, as in, it is no longer the language covered. The optimizer is an implementation detail, the optimizer is not allowed to change the semantics of the language.

If casting away immutable is claimed to be undefined behaviour it simply means that code that does this is not in the language and the compiler could refuse to compile such code if it was capable of detecting it. Or it _could_ specify it to have a specific type of semantics, but that would be a new language.

Andrei seems to argue that casting away immutable is not undefined behaviour in general.

> Implementation defined behaviour is in the realm of the "systems programming, be careful you know what you're doing". Undefined behaviour is a different beast.

When something is «implementation specific» it means that the concrete compiler/hardware _must_ specify it. For instance, the max available memory is usually implementation specific.

July 13, 2016
On Wednesday, 13 July 2016 at 10:02:58 UTC, Ola Fosheim Grøstad wrote:
> «Undefined» simply means that such code is not part of the specified language, as in, it is no longer the language covered. The optimizer is an implementation detail, the optimizer is not allowed to change the semantics of the language.
>
> If casting away immutable is claimed to be undefined behaviour it simply means that code that does this is not in the language and the compiler could refuse to compile such code if it was capable of detecting it. Or it _could_ specify it to have a specific type of semantics, but that would be a new language.

You're confusing "undefined" with "implementation defined".

Implementation-defined stuff is something that's not specified, but can be presumed to do *something*.  Undefined stuff is something that's officially considered to not even make sense, so a compiler can assume it never happens (even though a programmer can make the mistake of letting it happen).  This is sometimes controversial, but does let optimisers do some extra tricks with sane code.
July 13, 2016
On Wednesday, 13 July 2016 at 10:25:55 UTC, sarn wrote:
> On Wednesday, 13 July 2016 at 10:02:58 UTC, Ola Fosheim Grøstad wrote:
>> «Undefined» simply means that such code is not part of the specified language, as in, it is no longer the language covered. The optimizer is an implementation detail, the optimizer is not allowed to change the semantics of the language.
>>
>> If casting away immutable is claimed to be undefined behaviour it simply means that code that does this is not in the language and the compiler could refuse to compile such code if it was capable of detecting it. Or it _could_ specify it to have a specific type of semantics, but that would be a new language.
>
> You're confusing "undefined" with "implementation defined".

I am not confusing anything. A superset of a language is still covering the language, but it is also a new language. I think you are confusing "language" with "parsing".

> Implementation-defined stuff is something that's not specified, but can be presumed to do *something*.  Undefined stuff is something that's officially considered to not even make sense, so a compiler can assume it never happens (even though a programmer can make the mistake of letting it happen).  This is sometimes controversial, but does let optimisers do some extra tricks with sane code.

No. «Undefined» means exactly that, not defined by the language specification, not part of the language.  It does not say anything about what should or should not happen. It is simply not covered by the spec and a compiler could be compliant even if it turned out rubbish for such code if the spec does not require the compiler to detect all valid programs in the language. It has nothing to do with optimisers, that's just an implementation detail.

«Implementation defined» means that the implemented compiler/interpreter _must_ define it in a sensible manner, depending on the context, in order to comply with the spec.

July 13, 2016
On 7/13/16 5:19 AM, John Colvin wrote:
> "Casting away immutable is undefined behaviour": the following code has
> undefined results (note, not implementation defined, not
> if-you-know-what-you're-doing defined, undefined), despite not doing
> anything much:
>
> void foo()
> {
>     immutable a = new int;
>     auto b = cast(int*)a;
> }
>
> "modifying immutable data is undefined": The above code is fine, but the
> following is still undefined:
>
> void foo()
> {
>     immutable a = new int;
>     auto b = cast(int*)a;
>     b = 3;
> }

Interesting distinction. We must render the latter undefined but not the former. Consider:

struct S { immutable int a; int b; }
S s;
immutable int* p = &s.a;

It may be the case that you need to get to s.b (and change it) when all you have is p, which is a pointer to s.a. This is essentially what makes AffixAllocator work.


Andrei

July 13, 2016
On Wednesday, 13 July 2016 at 11:28:11 UTC, Andrei Alexandrescu wrote:
> On 7/13/16 5:19 AM, John Colvin wrote:
>> "Casting away immutable is undefined behaviour": the following code has
>> undefined results (note, not implementation defined, not
>> if-you-know-what-you're-doing defined, undefined), despite not doing
>> anything much:
>>
>> void foo()
>> {
>>     immutable a = new int;
>>     auto b = cast(int*)a;
>> }
>>
>> "modifying immutable data is undefined": The above code is fine, but the
>> following is still undefined:
>>
>> void foo()
>> {
>>     immutable a = new int;
>>     auto b = cast(int*)a;
>>     b = 3;
>> }
>
> Interesting distinction. We must render the latter undefined but not the former. Consider:
>
> struct S { immutable int a; int b; }
> S s;
> immutable int* p = &s.a;
>
> It may be the case that you need to get to s.b (and change it) when all you have is p, which is a pointer to s.a. This is essentially what makes AffixAllocator work.
>
>
> Andrei

Hmm. You have to create a mutable reference to immutable data to do that (although you are casting away immutable). Let's consider this:

*(p + 1) = 3;

it either has to be written like this:

*((cast(int*)p) + 1) = 3;

or like this:

*(cast(int*)(p + 1)) = 3;

The first is creating a mutable pointer to immutable data, the second creates an immutable pointer to mutable data. I'm not sure which is worse, considering that those reference could sit around for ages and be passed around etc., e.g.

auto tmp = p + 1;
// ... do loads of stuff, possibly reading from tmp
*(cast(int*)tmp) = 3;

seems like we would end up in trouble (either of our own creation or via the optimiser) from thinking tmp actually pointed to immutable data. Probably worse than a mutable reference to immutable data, as long as you didn't write to it.

Pointer arithmetic in objects is really quite dangerous w.r.t. immutability/const.
July 13, 2016
On Wednesday, 13 July 2016 at 11:48:15 UTC, John Colvin wrote:
> Hmm. You have to create a mutable reference to immutable data to do that (although you are casting away immutable). Let's consider this:
>
> *(p + 1) = 3;
>
> it either has to be written like this:
>
> *((cast(int*)p) + 1) = 3;
>
> or like this:
>
> *(cast(int*)(p + 1)) = 3;
>
> The first is creating a mutable pointer to immutable data, the second creates an immutable pointer to mutable data. I'm not sure which is worse, considering that those reference could sit around for ages and be passed around etc., e.g.
>
> auto tmp = p + 1;
> // ... do loads of stuff, possibly reading from tmp
> *(cast(int*)tmp) = 3;
>
> seems like we would end up in trouble (either of our own creation or via the optimiser) from thinking tmp actually pointed to immutable data. Probably worse than a mutable reference to immutable data, as long as you didn't write to it.
>
> Pointer arithmetic in objects is really quite dangerous w.r.t. immutability/const.

immutable int* p = ...
auto cp = cast(const int*)p; // cast immutable* to const*
auto cq = p + 1              // shift const* from immutable data to mutable data
auto q = cast(int*) cq;      // cast const* to mutable*
*q = 3;

This way both temporaries are const, and can point to both mutable and immutable.
So you never use immutable pointers to mutable data nor mutable pointers to immutable data.
July 13, 2016
On 07/13/2016 01:28 PM, Andrei Alexandrescu wrote:
> struct S { immutable int a; int b; }
> S s;
> immutable int* p = &s.a;
>
> It may be the case that you need to get to s.b (and change it) when all
> you have is p, which is a pointer to s.a. This is essentially what makes
> AffixAllocator work.

The obvious way doesn't seem so bad in isolation:

----
void main()
{
    S s;
    immutable int* p = &s.a;
    S* ps = cast(S*) p;
    ps.b = 42;
}
----

But when p comes from a pure function things get iffy:

----
struct S { immutable int a; int b; }

immutable(int*) f() pure
{
    S* s = new S;
    return &s.a;
}

void main()
{
    immutable int* p1 = f();
    S* ps1 = cast(S*) p1;
    ps1.b = 42;

    immutable int* p2 = f();
    S* ps2 = cast(S*) p2;
    ps2.b = 43;
}
----

f is marked pure, has no parameters and no mutable indirections in the return type. So f is strongly pure. That means, the compiler is free to reuse p1 for p2. Or it may allocate two distinct structures, of course.

So, ps1 may or may not be the same as ps2, if those casts are allowed. That can't be right.
July 13, 2016
On 07/13/2016 08:15 AM, ag0aep6g wrote:
>
> ----
> struct S { immutable int a; int b; }
>
> immutable(int*) f() pure
> {
>      S* s = new S;
>      return &s.a;
> }
>
> void main()
> {
>      immutable int* p1 = f();
>      S* ps1 = cast(S*) p1;
>      ps1.b = 42;
>
>      immutable int* p2 = f();
>      S* ps2 = cast(S*) p2;
>      ps2.b = 43;
> }
> ----
>
> f is marked pure, has no parameters and no mutable indirections in the
> return type. So f is strongly pure. That means, the compiler is free to
> reuse p1 for p2. Or it may allocate two distinct structures, of course.
>
> So, ps1 may or may not be the same as ps2, if those casts are allowed.
> That can't be right.

Good example. I think the two pointers should be allowed to be equal. -- Andrei

July 13, 2016
On Wednesday, 13 July 2016 at 11:48:15 UTC, John Colvin wrote:
> On Wednesday, 13 July 2016 at 11:28:11 UTC, Andrei Alexandrescu wrote:
>> On 7/13/16 5:19 AM, John Colvin wrote:
>>> "Casting away immutable is undefined behaviour": the following code has
>>> undefined results (note, not implementation defined, not
>>> if-you-know-what-you're-doing defined, undefined), despite not doing
>>> anything much:
>>>
>>> void foo()
>>> {
>>>     immutable a = new int;
>>>     auto b = cast(int*)a;
>>> }
>>>
>>> "modifying immutable data is undefined": The above code is fine, but the
>>> following is still undefined:
>>>
>>> void foo()
>>> {
>>>     immutable a = new int;
>>>     auto b = cast(int*)a;
>>>     b = 3;
>>> }
>>
>> Interesting distinction. We must render the latter undefined but not the former. Consider:
>>
>> struct S { immutable int a; int b; }
>> S s;
>> immutable int* p = &s.a;
>>
>> It may be the case that you need to get to s.b (and change it) when all you have is p, which is a pointer to s.a. This is essentially what makes AffixAllocator work.
>>
>>
>> Andrei
>
> Hmm. You have to create a mutable reference to immutable data to do that (although you are casting away immutable).

Woops, I meant "You don't have to create".