Thread overview
spurious gc allocation
Nov 08, 2013
Ellery Newcomer
Nov 08, 2013
Benjamin Thaut
Nov 08, 2013
Timon Gehr
Nov 09, 2013
Ellery Newcomer
Nov 09, 2013
lomereiter
Nov 09, 2013
Ellery Newcomer
Nov 09, 2013
bearophile
November 08, 2013
hello all.

I have a class member function that essentially looks like this:

ThisNode* _InsertAllBut(int value) {
    ThisNode* node = MallocAllocator.allocate!(ThisNode)(1);
    node.value = value;
    node_count++;
    return node;
}


I compile it on x86_64 and the compiler inserts a gc allocation.

I decompiled it, and it looks like it does this:

  ThisNode* _InsertAllBut(int value) {
      struct Thing {
          typeof(this) thing1;
          ThisNode* thing2;
          int thing3;
      }
      Thing* rbp28 = _d_allocmemory(0x14);
      rbp28.thing1 = this;
      rbp28.thing3 = value;
      if (this == 0) {
          // not wasting my time figuring out _d_assert_msg's calling conventions
          r8d = 0x137c;
          rcx = something pointing to "src/multi_index.d";
          rdi = {length associated with rsi};
          rsi = something pointing to "null this";
          rdx = {length associated with rcx};
          _d_assert_msg();
      }
      invariant._d_invariant(this);
      rbp28.thing2 = MallocAllocator.allocate(1);
      rbp28.thing2.value = rbp28.thing3;
      this.nodecount ++;
      return rbp28.thing2;
  }


So. Why the heck is it using heap space for stack space? How the heck am I supposed to call this from within a destructor?
November 08, 2013
Am 08.11.2013 06:19, schrieb Ellery Newcomer:
> hello all.
>
> I have a class member function that essentially looks like this:
>
> ThisNode* _InsertAllBut(int value) {
>      ThisNode* node = MallocAllocator.allocate!(ThisNode)(1);
>      node.value = value;
>      node_count++;
>      return node;
> }
>
>
> I compile it on x86_64 and the compiler inserts a gc allocation.
>
> I decompiled it, and it looks like it does this:
>
>    ThisNode* _InsertAllBut(int value) {
>        struct Thing {
>            typeof(this) thing1;
>            ThisNode* thing2;
>            int thing3;
>        }
>        Thing* rbp28 = _d_allocmemory(0x14);
>        rbp28.thing1 = this;
>        rbp28.thing3 = value;
>        if (this == 0) {
>            // not wasting my time figuring out _d_assert_msg's calling
> conventions
>            r8d = 0x137c;
>            rcx = something pointing to "src/multi_index.d";
>            rdi = {length associated with rsi};
>            rsi = something pointing to "null this";
>            rdx = {length associated with rcx};
>            _d_assert_msg();
>        }
>        invariant._d_invariant(this);
>        rbp28.thing2 = MallocAllocator.allocate(1);
>        rbp28.thing2.value = rbp28.thing3;
>        this.nodecount ++;
>        return rbp28.thing2;
>    }
>
>
> So. Why the heck is it using heap space for stack space? How the heck am
> I supposed to call this from within a destructor?


The problem is that you define the struct Thing as a inner struct. Inner structs have references to the outer class instance they have been created from. If you want a plain old struct you have to write
static struct Thing { ... }

Kind Regards
Benjamin Thaut
November 08, 2013
On 11/08/2013 07:12 AM, Benjamin Thaut wrote:
>
>
> The problem is that you define the struct Thing as a inner struct.

struct Thing only exists in the decompiled version, not in the original source. So far it looks like a bug to me.
November 09, 2013
On 11/08/2013 06:19 AM, Timon Gehr wrote:
> On 11/08/2013 07:12 AM, Benjamin Thaut wrote:
>>
>>
>> The problem is that you define the struct Thing as a inner struct.
>
> struct Thing only exists in the decompiled version, not in the original
> source. So far it looks like a bug to me.

I've reduced it to the following:

a.d:
class C
{
    void _InsertAllBut(int v) {
        int* node = null;
        enum mutable = __traits(compiles, {node.value ;});
    }

}

test.d:
import a;

void main () {
    C c = new C();
    c._InsertAllBut(1);
}


compile:

dmd test.d a.d

order doesn't seem to matter, works with -m32 and -m64, apparently I am running dmd v2.063-devel-e23c785

objdump -d --disassembler-options=intel test | ddemangle

shows me

...
0000000000417888 <void a.C._InsertAllBut(int)>:
  417888:       55                      push   rbp
  417889:       48 8b ec                mov    rbp,rsp
  41788c:       48 83 ec 38             sub    rsp,0x38
  417890:       53                      push   rbx
  417891:       48 89 7d f0             mov    QWORD PTR [rbp-0x10],rdi
  417895:       48 bf 10 00 00 00 00    movabs rdi,0x10
  41789c:       00 00 00
  41789f:       e8 10 22 00 00          call   419ab4 <_d_allocmemory>
  4178a4:       48 89 45 e0             mov    QWORD PTR [rbp-0x20],rax
  4178a8:       48 8b 4d f0             mov    rcx,QWORD PTR [rbp-0x10]
  4178ac:       48 89 08                mov    QWORD PTR [rax],rcx
  4178af:       48 85 c9                test   rcx,rcx
...


can anyone confirm?
November 09, 2013
Indeed, disassembly reveals an allocation (with all three compilers => it's the front-end which generates this crap).

I guess the compiler incorrectly treats { node.value; } as a delegate and copies the node to GC heap.

void foo() {
    int* node = null;
    enum mutable = __traits(compiles, {node.value ;});
}

void main() {
    foo();
}
November 09, 2013
Ellery Newcomer:

> can anyone confirm?

I only see the class instance allocation in the main(). I use two modules, with your code. I compile on Windows 32, using no compilation flags, and I see this asm, with obj2asm:


_D1a1C13_InsertAllButMFiZv:
		push	EAX
		push	EAX
		mov	[ESP],0
		add	ESP,8
		ret	4

__Dmain:
L0:     mov EAX,offset FLAT:_D1a1C7__ClassZ
        push    EAX
        call    near ptr __d_newclass
        push    1
        mov ECX,[EAX]
        call    dword ptr 014h[ECX]
        xor EAX,EAX
        add ESP,4
        ret

_main:
L0:     mov EAX,offset FLAT:__Dmain
        push    EAX
        push    dword ptr 0Ch[ESP]
        push    dword ptr 0Ch[ESP]
        call    near ptr __d_run_main
        add ESP,0Ch
        ret

Bye,
bearophile
November 09, 2013
On 11/09/2013 12:35 AM, lomereiter wrote:
> Indeed, disassembly reveals an allocation (with all three compilers =>
> it's the front-end which generates this crap).

ouch.

>
> I guess the compiler incorrectly treats { node.value; } as a delegate
> and copies the node to GC heap.
>
> void foo() {
>      int* node = null;
>      enum mutable = __traits(compiles, {node.value ;});
> }
>
> void main() {
>      foo();
> }

oh, I see, it's the delegate doing that. that's helpful.


https://d.puremagic.com/issues/show_bug.cgi?id=11483