Thread overview
Troubles with immutable arrays
Jul 23, 2012
bearophile
Jul 23, 2012
kenji hara
Jul 23, 2012
bearophile
Jul 25, 2012
Don Clugston
July 23, 2012
After a discussion in D.learn started by someone else, after a suggestion of mine Timon Gehr has added a bug report:

http://d.puremagic.com/issues/show_bug.cgi?id=8400

But the bug was fixed in the opposite way of what I was thinking.

The problem was that the length of global immutable arrays arrays is seen as a compile-time constant.
Instead of fixing that, Issue 8400 has done the opposite, now even the lenght of local immutable arrays is seen sometimes as a compile-time constant, and example:


int[] foo(in int n) pure nothrow {
    int[] a;
    foreach (i; 0 .. n)
        a ~= i * 10;
    return a;
}
void main() {
    import core.stdc.stdio: printf;
    immutable int[] A = foo(5);
    int[A.length] B;
    printf("%zd\n", B.length);
}



The asm, compiled with -release:

_D4temp3fooFNaNbxiZAi   comdat
L0:     enter   018h,0
        push    EBX
        push    ESI
        mov dword ptr -018h[EBP],0
        mov dword ptr -014h[EBP],0
        mov dword ptr -010h[EBP],0
        mov -0Ch[EBP],EAX
L1E:        mov EAX,-010h[EBP]
        cmp EAX,-0Ch[EBP]
        jge L48
        lea ECX,-010h[EBP]
        mov EDX,[ECX]
        lea EBX,[EDX*4][EDX]
        add EBX,EBX
        push    EBX
        lea ESI,-018h[EBP]
        push    ESI
        mov EAX,offset FLAT:_D11TypeInfo_Ai6__initZ
        push    EAX
        call    near ptr __d_arrayappendcT
        add ESP,0Ch
        inc dword ptr -010h[EBP]
        jmp short   L1E
L48:        mov EDX,-014h[EBP]
        mov EAX,-018h[EBP]
        pop ESI
        pop EBX
        leave
        ret

__Dmain comdat
L0:     enter   018h,0
        push    EBX
        push    ESI
        push    5
        mov EAX,offset FLAT:_D11TypeInfo_Ai6__initZ
        push    EAX
        call    near ptr __d_arrayliteralTX
        mov dword ptr [EAX],0
        mov dword ptr 4[EAX],0Ah
        mov dword ptr 8[EAX],014h
        mov dword ptr 0Ch[EAX],01Eh
        mov dword ptr 010h[EAX],028h
        mov ECX,EAX
        mov EBX,5
        lea EDX,-018h[EBP]
        xor EAX,EAX
        mov [EDX],EAX
        mov 4[EDX],EAX
        mov 8[EDX],EAX
        mov 0Ch[EDX],EAX
        mov 010h[EDX],EAX
        push    5
        mov ESI,offset FLAT:_DATA
        push    ESI
        call    near ptr _printf
        xor EAX,EAX
        add ESP,010h
        pop ESI
        pop EBX
        leave
        ret



This code too compiles, so A is sometimes computed at run-time and sometimes at compile-time:

int[] foo(in int n) pure nothrow {
    int[] a;
    foreach (i; 0 .. n)
        a ~= i * 10;
    return a;
}
void main() {
    import core.stdc.stdio: printf;
    int n = 5;
    immutable int[] A = foo(n);
}



Now immutable arrays are sometimes seen as enums. I think this is a problem. I think in D compile-time is run only if it's evaluated in a context where compile-time values are required. But now the situation is more muddy, because knowing n at compile-time is not a way to ask A to be computed at compile-time.

Another problem is that compile-time arrays in many situations are not efficient, they gets copied every time you use them, and I think that __d_arrayliteralTX performs a heap allocation. So now both enum and immutable arrays perform heap allocations every time you use them, but only in some situations.

I think this is a messy situation, I think the fix for bug 8400 should be undone, and I think Issue 8400 should be fixed the other way, turning global immutable arrays too into run-time entities.

The bug was fixed by Hara and accepted by Walter, both of them are very intelligent, so maybe I am just very wrong :-)

Bye,
bearophile
July 23, 2012
Oh, no. I've accidentally done the *fix*, even though that is contrary to my own claim.

I don't like the cross-talk of compile-time and run-time variables. An related issue is bug 3449, but it has been opposed by Walter.

http://d.puremagic.com/issues/show_bug.cgi?id=3449

A small example of bug 3449:
----
void main()
{
    struct Foo1 { const int bar; }
    pragma(msg, Foo1.sizeof);       // Prints "4"

    Foo1 foo1;
    auto p1 = &foo1.bar;            // Succeeds to compile, as excepced.

    struct Foo2 { const int bar = 123; }
    pragma(msg, Foo2.sizeof);       // Prints "1", not "4"

    Foo2 foo2;
    auto p2 = &foo2.bar;    // Error: constant 123 is not an lvalue
}

Why cannot get address of foo2.bar?
The answer is: compiler makes Foo2.bar a manifest constant, because
its type is not mutable and has an initializer.

---

With current dmd, *all* of variable declarations, that has non mutable type and initializer, are _speculatively_ interpreted in compile time (== CTFE-ed). If it is succeeds, the declaration will be treated as same as manifest constant. That is the reason of you explained *bug* and bug 3449.

I think the *implicit interpretation* is inherited from D1, and if go
back further, will reach to C++ constant variable.
BUT, in D2, we have the 'enum' declaration, which express the
declaration is really manifest constant.

So, the muddy interpretation just confuses many D user's, and less benefit.
I think we should separate run-time variable declarations and compile
time ones, to moderate the leaning curve.

Regards.

Kenji Hara

2012/7/23 bearophile <bearophileHUGS@lycos.com>:
> After a discussion in D.learn started by someone else, after a suggestion of mine Timon Gehr has added a bug report:
>
> http://d.puremagic.com/issues/show_bug.cgi?id=8400
>
> But the bug was fixed in the opposite way of what I was thinking.
>
> The problem was that the length of global immutable arrays arrays is seen as
> a compile-time constant.
> Instead of fixing that, Issue 8400 has done the opposite, now even the
> lenght of local immutable arrays is seen sometimes as a compile-time
> constant, and example:
>
>
> int[] foo(in int n) pure nothrow {
>     int[] a;
>     foreach (i; 0 .. n)
>         a ~= i * 10;
>     return a;
> }
> void main() {
>     import core.stdc.stdio: printf;
>     immutable int[] A = foo(5);
>     int[A.length] B;
>     printf("%zd\n", B.length);
> }
>
>
>
> The asm, compiled with -release:
>
> _D4temp3fooFNaNbxiZAi   comdat
> L0:     enter   018h,0
>         push    EBX
>         push    ESI
>         mov dword ptr -018h[EBP],0
>         mov dword ptr -014h[EBP],0
>         mov dword ptr -010h[EBP],0
>         mov -0Ch[EBP],EAX
> L1E:        mov EAX,-010h[EBP]
>         cmp EAX,-0Ch[EBP]
>         jge L48
>         lea ECX,-010h[EBP]
>         mov EDX,[ECX]
>         lea EBX,[EDX*4][EDX]
>         add EBX,EBX
>         push    EBX
>         lea ESI,-018h[EBP]
>         push    ESI
>         mov EAX,offset FLAT:_D11TypeInfo_Ai6__initZ
>         push    EAX
>         call    near ptr __d_arrayappendcT
>         add ESP,0Ch
>         inc dword ptr -010h[EBP]
>         jmp short   L1E
> L48:        mov EDX,-014h[EBP]
>         mov EAX,-018h[EBP]
>         pop ESI
>         pop EBX
>         leave
>         ret
>
> __Dmain comdat
> L0:     enter   018h,0
>         push    EBX
>         push    ESI
>         push    5
>         mov EAX,offset FLAT:_D11TypeInfo_Ai6__initZ
>         push    EAX
>         call    near ptr __d_arrayliteralTX
>         mov dword ptr [EAX],0
>         mov dword ptr 4[EAX],0Ah
>         mov dword ptr 8[EAX],014h
>         mov dword ptr 0Ch[EAX],01Eh
>         mov dword ptr 010h[EAX],028h
>         mov ECX,EAX
>         mov EBX,5
>         lea EDX,-018h[EBP]
>         xor EAX,EAX
>         mov [EDX],EAX
>         mov 4[EDX],EAX
>         mov 8[EDX],EAX
>         mov 0Ch[EDX],EAX
>         mov 010h[EDX],EAX
>         push    5
>         mov ESI,offset FLAT:_DATA
>         push    ESI
>         call    near ptr _printf
>         xor EAX,EAX
>         add ESP,010h
>         pop ESI
>         pop EBX
>         leave
>         ret
>
>
>
> This code too compiles, so A is sometimes computed at run-time and sometimes at compile-time:
>
> int[] foo(in int n) pure nothrow {
>     int[] a;
>     foreach (i; 0 .. n)
>         a ~= i * 10;
>     return a;
> }
> void main() {
>     import core.stdc.stdio: printf;
>     int n = 5;
>     immutable int[] A = foo(n);
> }
>
>
>
> Now immutable arrays are sometimes seen as enums. I think this is a problem. I think in D compile-time is run only if it's evaluated in a context where compile-time values are required. But now the situation is more muddy, because knowing n at compile-time is not a way to ask A to be computed at compile-time.
>
> Another problem is that compile-time arrays in many situations are not efficient, they gets copied every time you use them, and I think that __d_arrayliteralTX performs a heap allocation. So now both enum and immutable arrays perform heap allocations every time you use them, but only in some situations.
>
> I think this is a messy situation, I think the fix for bug 8400 should be undone, and I think Issue 8400 should be fixed the other way, turning global immutable arrays too into run-time entities.
>
> The bug was fixed by Hara and accepted by Walter, both of them are very intelligent, so maybe I am just very wrong :-)
>
> Bye,
> bearophile
July 23, 2012
Thank you for your answer and your understanding, Hara. I hope you will improve the situation.

Regarding this issue: http://d.puremagic.com/issues/show_bug.cgi?id=3449
Maybe Walter is wrong about it, so I suggest people to express their opinion.

Bye,
bearophile
July 25, 2012
On 23/07/12 15:29, bearophile wrote:
> After a discussion in D.learn started by someone else, after a
> suggestion of mine Timon Gehr has added a bug report:
>
> http://d.puremagic.com/issues/show_bug.cgi?id=8400
>
> But the bug was fixed in the opposite way of what I was thinking.
>
> The problem was that the length of global immutable arrays arrays is
> seen as a compile-time constant.
> Instead of fixing that, Issue 8400 has done the opposite, now even the
> lenght of local immutable arrays is seen sometimes as a compile-time
> constant, and example:

Sorry bearophile, I completely disagree with this post.

Currently, when a compile time value is required, CTFE is attempted. If it fails, an error message is generated.

You are asking for a corner case to be introduced. Under certain circumstances (which aren't clearly defined), you want CTFE to *not* be attempted.

>      immutable int[] A = foo(5);
>      int[A.length] B;

> This code too compiles, so A is sometimes computed at run-time and
> sometimes at compile-time:

      immutable int[] A = foo(n);

> Now immutable arrays are sometimes seen as enums.

That is not correct, an immutable array is always different to an enum. For example, an enum is simply a manifest constant, and does not have an address. An immutable array always has an address.

> I think this is a
> problem. I think in D compile-time is run only if it's evaluated in a
> context where compile-time values are required. But now the situation is
> more muddy, because knowing n at compile-time is not a way to ask A to
> be computed at compile-time.

The only consequence is that if you don't require it at compile time, a particular optimization might not happen. There is no change to semantics.

> Another problem is that compile-time arrays in many situations are not
> efficient, they gets copied every time you use them, and I think that
> __d_arrayliteralTX performs a heap allocation. So now both enum and
> immutable arrays perform heap allocations every time you use them, but
> only in some situations.

That's the famous bug 2356, which is completely irrelevant to this situation.

> I think this is a messy situation, I think the fix for bug 8400 should
> be undone, and I think Issue 8400 should be fixed the other way, turning
> global immutable arrays too into run-time entities.

Did you even know that initializers of global variables, including arrays, including even mutable arrays, are ALWAYS CTFE'd?