Alien primFFICall returning struct with 64bit vm

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Alien primFFICall returning struct with 64bit vm

Balázs Kósi
Hi all,

I'm having trouble with calling struct returning functions with alien on a 64bit linux vm using the hidden first argument mechanism [1]. The function gets called, but the result is not copied into the struct pointed by the hidden argument.

I tried to debug what happens with gdb, but everything looks fine, except not getting the result back. The address is copied into %rdi (regs[0]) in dax64business.h:92 [2], which is exactly what the spec says [3].

Here is a minimal example calling a C function returning a struct with two doubles:

at34 := Alien lookup: 'at34' inLibrary: 'libalientest.so'.
at34 primFFICallResult: nil with: (r := Alien newC: 16) pointer.
vec := (r doubleAt: 1) @ (r doubleAt: 9). r free. vec " 0.0@0.0 "

returns 0.0@0.0 instead of 3.0@4.0

the C code goes like this:

typedef struct atvect{double x, y;} atvect;

atvect atv(double x, double y) {
  atvect v = {x, y};
  return v;
}

atvect at34() {
  return atv(3.0, 4.0);
}

The vm is sqcogspur64linuxht 5.0-201910291408. The image is a recent trunk image (update: #19142), image format 68021 (64 bit). Alien is Alien-Core-TorstenBergmann.101.mcz.

What am I missing?

Thanks, Balázs

[1] "The rules for structure results vary slightly by platform. Most functions returning structures expect a “hidden” first parameter holding the result struct’s address. Because the FFI provides no abstraction one must pass this parameter explicitly. ... On linux all struct results are re-turned through the hidden first argument mechanism ... ".



[3] "2. If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first
argument to the function. In effect, this address becomes a “hidden” first ar-
gument. This storage must not overlap any data visible to the callee through
other names than this argument.
On return %rax will contain the address that has been passed in by the
caller in %rdi."



Reply | Threaded
Open this post in threaded view
|

Re: Alien primFFICall returning struct with 64bit vm

Nicolas Cellier
 Hi Balazs,
I confirm what you observe.
If I compile the source with:

    clang --shared -o libalientest.so -Os alientest.c

then disassemble with:

    objdump -D libalientest.so  | less

I get this:

    000000000000057c <atv>:
     57c:   c3                      retq  

    000000000000057d <at34>:
     57d:   f2 0f 10 05 1b 00 00    movsd  0x1b(%rip),%xmm0        # 5a0 <_fini+0x10>
     584:   00
     585:   f2 0f 10 0d 1b 00 00    movsd  0x1b(%rip),%xmm1        # 5a8 <_fini+0x18>
     58c:   00
     58d:   c3                      retq 

Code is highly suspicious for atv...
at34 just load the constants in dregs, then do nothing (inline atv).
So this does not work as advertized...

The non optimized version is very convoluted:

0000000000000580 <atv>:
 580:   55                      push   %rbp
 581:   48 89 e5                mov    %rsp,%rbp
 584:   f2 0f 11 45 e8          movsd  %xmm0,-0x18(%rbp)
 589:   f2 0f 11 4d e0          movsd  %xmm1,-0x20(%rbp)
 58e:   f2 0f 10 45 e8          movsd  -0x18(%rbp),%xmm0
 593:   f2 0f 11 45 d0          movsd  %xmm0,-0x30(%rbp)
 598:   f2 0f 10 45 e0          movsd  -0x20(%rbp),%xmm0
 59d:   f2 0f 11 45 d8          movsd  %xmm0,-0x28(%rbp)
 5a2:   0f 10 45 d0             movups -0x30(%rbp),%xmm0
 5a6:   0f 29 45 f0             movaps %xmm0,-0x10(%rbp)
 5aa:   f2 0f 10 45 f0          movsd  -0x10(%rbp),%xmm0
 5af:   f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
 5b4:   5d                      pop    %rbp
 5b5:   c3                      retq  
 5b6:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 5bd:   00 00 00

00000000000005c0 <at34>:
 5c0:   55                      push   %rbp
 5c1:   48 89 e5                mov    %rsp,%rbp
 5c4:   48 83 ec 10             sub    $0x10,%rsp
 5c8:   f2 0f 10 05 38 00 00    movsd  0x38(%rip),%xmm0        # 608 <_fini+0x10>
 5cf:   00
 5d0:   f2 0f 10 0d 38 00 00    movsd  0x38(%rip),%xmm1        # 610 <_fini+0x18>
 5d7:   00
 5d8:   e8 a3 ff ff ff          callq  580 <atv>
 5dd:   f2 0f 11 45 f0          movsd  %xmm0,-0x10(%rbp)
 5e2:   f2 0f 11 4d f8          movsd  %xmm1,-0x8(%rbp)
 5e7:   f2 0f 10 45 f0          movsd  -0x10(%rbp),%xmm0
 5ec:   f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
 5f1:   48 83 c4 10             add    $0x10,%rsp
 5f5:   5d                      pop    %rbp
 5f6:   c3                      retq  

It's clear that the resulting structure is passed via first 2 dregs (xmm0 and xmm1).
Doesn't it qualify as a clang bug? Or is our interpretation of ABI erroneous?
I would opt for the later...

Le mar. 5 nov. 2019 à 23:54, Balázs Kósi <[hidden email]> a écrit :
Hi all,

I'm having trouble with calling struct returning functions with alien on a 64bit linux vm using the hidden first argument mechanism [1]. The function gets called, but the result is not copied into the struct pointed by the hidden argument.

I tried to debug what happens with gdb, but everything looks fine, except not getting the result back. The address is copied into %rdi (regs[0]) in dax64business.h:92 [2], which is exactly what the spec says [3].

Here is a minimal example calling a C function returning a struct with two doubles:

at34 := Alien lookup: 'at34' inLibrary: 'libalientest.so'.
at34 primFFICallResult: nil with: (r := Alien newC: 16) pointer.
vec := (r doubleAt: 1) @ (r doubleAt: 9). r free. vec " 0.0@0.0 "

returns 0.0@0.0 instead of 3.0@4.0

the C code goes like this:

typedef struct atvect{double x, y;} atvect;

atvect atv(double x, double y) {
  atvect v = {x, y};
  return v;
}

atvect at34() {
  return atv(3.0, 4.0);
}

The vm is sqcogspur64linuxht 5.0-201910291408. The image is a recent trunk image (update: #19142), image format 68021 (64 bit). Alien is Alien-Core-TorstenBergmann.101.mcz.

What am I missing?

Thanks, Balázs

[1] "The rules for structure results vary slightly by platform. Most functions returning structures expect a “hidden” first parameter holding the result struct’s address. Because the FFI provides no abstraction one must pass this parameter explicitly. ... On linux all struct results are re-turned through the hidden first argument mechanism ... ".



[3] "2. If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first
argument to the function. In effect, this address becomes a “hidden” first ar-
gument. This storage must not overlap any data visible to the callee through
other names than this argument.
On return %rax will contain the address that has been passed in by the
caller in %rdi."




Reply | Threaded
Open this post in threaded view
|

Re: Alien primFFICall returning struct with 64bit vm

Nicolas Cellier
Wikipedia tells that ou assumptions are wrong:


or if you prefer going to the reference:


You'll see that the rules are quite complex!
Notably, page 21, the structure has class MEMORY if longer than eight eightbyte (that's 64 bytes !).
So you should open bug report on Smalltalk side...
And we lack many tests...

Le mer. 6 nov. 2019 à 13:56, Nicolas Cellier <[hidden email]> a écrit :
 Hi Balazs,
I confirm what you observe.
If I compile the source with:

    clang --shared -o libalientest.so -Os alientest.c

then disassemble with:

    objdump -D libalientest.so  | less

I get this:

    000000000000057c <atv>:
     57c:   c3                      retq  

    000000000000057d <at34>:
     57d:   f2 0f 10 05 1b 00 00    movsd  0x1b(%rip),%xmm0        # 5a0 <_fini+0x10>
     584:   00
     585:   f2 0f 10 0d 1b 00 00    movsd  0x1b(%rip),%xmm1        # 5a8 <_fini+0x18>
     58c:   00
     58d:   c3                      retq 

Code is highly suspicious for atv...
at34 just load the constants in dregs, then do nothing (inline atv).
So this does not work as advertized...

The non optimized version is very convoluted:

0000000000000580 <atv>:
 580:   55                      push   %rbp
 581:   48 89 e5                mov    %rsp,%rbp
 584:   f2 0f 11 45 e8          movsd  %xmm0,-0x18(%rbp)
 589:   f2 0f 11 4d e0          movsd  %xmm1,-0x20(%rbp)
 58e:   f2 0f 10 45 e8          movsd  -0x18(%rbp),%xmm0
 593:   f2 0f 11 45 d0          movsd  %xmm0,-0x30(%rbp)
 598:   f2 0f 10 45 e0          movsd  -0x20(%rbp),%xmm0
 59d:   f2 0f 11 45 d8          movsd  %xmm0,-0x28(%rbp)
 5a2:   0f 10 45 d0             movups -0x30(%rbp),%xmm0
 5a6:   0f 29 45 f0             movaps %xmm0,-0x10(%rbp)
 5aa:   f2 0f 10 45 f0          movsd  -0x10(%rbp),%xmm0
 5af:   f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
 5b4:   5d                      pop    %rbp
 5b5:   c3                      retq  
 5b6:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 5bd:   00 00 00

00000000000005c0 <at34>:
 5c0:   55                      push   %rbp
 5c1:   48 89 e5                mov    %rsp,%rbp
 5c4:   48 83 ec 10             sub    $0x10,%rsp
 5c8:   f2 0f 10 05 38 00 00    movsd  0x38(%rip),%xmm0        # 608 <_fini+0x10>
 5cf:   00
 5d0:   f2 0f 10 0d 38 00 00    movsd  0x38(%rip),%xmm1        # 610 <_fini+0x18>
 5d7:   00
 5d8:   e8 a3 ff ff ff          callq  580 <atv>
 5dd:   f2 0f 11 45 f0          movsd  %xmm0,-0x10(%rbp)
 5e2:   f2 0f 11 4d f8          movsd  %xmm1,-0x8(%rbp)
 5e7:   f2 0f 10 45 f0          movsd  -0x10(%rbp),%xmm0
 5ec:   f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
 5f1:   48 83 c4 10             add    $0x10,%rsp
 5f5:   5d                      pop    %rbp
 5f6:   c3                      retq  

It's clear that the resulting structure is passed via first 2 dregs (xmm0 and xmm1).
Doesn't it qualify as a clang bug? Or is our interpretation of ABI erroneous?
I would opt for the later...

Le mar. 5 nov. 2019 à 23:54, Balázs Kósi <[hidden email]> a écrit :
Hi all,

I'm having trouble with calling struct returning functions with alien on a 64bit linux vm using the hidden first argument mechanism [1]. The function gets called, but the result is not copied into the struct pointed by the hidden argument.

I tried to debug what happens with gdb, but everything looks fine, except not getting the result back. The address is copied into %rdi (regs[0]) in dax64business.h:92 [2], which is exactly what the spec says [3].

Here is a minimal example calling a C function returning a struct with two doubles:

at34 := Alien lookup: 'at34' inLibrary: 'libalientest.so'.
at34 primFFICallResult: nil with: (r := Alien newC: 16) pointer.
vec := (r doubleAt: 1) @ (r doubleAt: 9). r free. vec " 0.0@0.0 "

returns 0.0@0.0 instead of 3.0@4.0

the C code goes like this:

typedef struct atvect{double x, y;} atvect;

atvect atv(double x, double y) {
  atvect v = {x, y};
  return v;
}

atvect at34() {
  return atv(3.0, 4.0);
}

The vm is sqcogspur64linuxht 5.0-201910291408. The image is a recent trunk image (update: #19142), image format 68021 (64 bit). Alien is Alien-Core-TorstenBergmann.101.mcz.

What am I missing?

Thanks, Balázs

[1] "The rules for structure results vary slightly by platform. Most functions returning structures expect a “hidden” first parameter holding the result struct’s address. Because the FFI provides no abstraction one must pass this parameter explicitly. ... On linux all struct results are re-turned through the hidden first argument mechanism ... ".



[3] "2. If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first
argument to the function. In effect, this address becomes a “hidden” first ar-
gument. This storage must not overlap any data visible to the callee through
other names than this argument.
On return %rax will contain the address that has been passed in by the
caller in %rdi."




Reply | Threaded
Open this post in threaded view
|

Re: Alien primFFICall returning struct with 64bit vm

Nicolas Cellier
because it won't be solved that easily... It might take some time.

Le mer. 6 nov. 2019 à 14:16, Nicolas Cellier <[hidden email]> a écrit :
Wikipedia tells that ou assumptions are wrong:


or if you prefer going to the reference:


You'll see that the rules are quite complex!
Notably, page 21, the structure has class MEMORY if longer than eight eightbyte (that's 64 bytes !).
So you should open bug report on Smalltalk side...
And we lack many tests...

Le mer. 6 nov. 2019 à 13:56, Nicolas Cellier <[hidden email]> a écrit :
 Hi Balazs,
I confirm what you observe.
If I compile the source with:

    clang --shared -o libalientest.so -Os alientest.c

then disassemble with:

    objdump -D libalientest.so  | less

I get this:

    000000000000057c <atv>:
     57c:   c3                      retq  

    000000000000057d <at34>:
     57d:   f2 0f 10 05 1b 00 00    movsd  0x1b(%rip),%xmm0        # 5a0 <_fini+0x10>
     584:   00
     585:   f2 0f 10 0d 1b 00 00    movsd  0x1b(%rip),%xmm1        # 5a8 <_fini+0x18>
     58c:   00
     58d:   c3                      retq 

Code is highly suspicious for atv...
at34 just load the constants in dregs, then do nothing (inline atv).
So this does not work as advertized...

The non optimized version is very convoluted:

0000000000000580 <atv>:
 580:   55                      push   %rbp
 581:   48 89 e5                mov    %rsp,%rbp
 584:   f2 0f 11 45 e8          movsd  %xmm0,-0x18(%rbp)
 589:   f2 0f 11 4d e0          movsd  %xmm1,-0x20(%rbp)
 58e:   f2 0f 10 45 e8          movsd  -0x18(%rbp),%xmm0
 593:   f2 0f 11 45 d0          movsd  %xmm0,-0x30(%rbp)
 598:   f2 0f 10 45 e0          movsd  -0x20(%rbp),%xmm0
 59d:   f2 0f 11 45 d8          movsd  %xmm0,-0x28(%rbp)
 5a2:   0f 10 45 d0             movups -0x30(%rbp),%xmm0
 5a6:   0f 29 45 f0             movaps %xmm0,-0x10(%rbp)
 5aa:   f2 0f 10 45 f0          movsd  -0x10(%rbp),%xmm0
 5af:   f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
 5b4:   5d                      pop    %rbp
 5b5:   c3                      retq  
 5b6:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 5bd:   00 00 00

00000000000005c0 <at34>:
 5c0:   55                      push   %rbp
 5c1:   48 89 e5                mov    %rsp,%rbp
 5c4:   48 83 ec 10             sub    $0x10,%rsp
 5c8:   f2 0f 10 05 38 00 00    movsd  0x38(%rip),%xmm0        # 608 <_fini+0x10>
 5cf:   00
 5d0:   f2 0f 10 0d 38 00 00    movsd  0x38(%rip),%xmm1        # 610 <_fini+0x18>
 5d7:   00
 5d8:   e8 a3 ff ff ff          callq  580 <atv>
 5dd:   f2 0f 11 45 f0          movsd  %xmm0,-0x10(%rbp)
 5e2:   f2 0f 11 4d f8          movsd  %xmm1,-0x8(%rbp)
 5e7:   f2 0f 10 45 f0          movsd  -0x10(%rbp),%xmm0
 5ec:   f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
 5f1:   48 83 c4 10             add    $0x10,%rsp
 5f5:   5d                      pop    %rbp
 5f6:   c3                      retq  

It's clear that the resulting structure is passed via first 2 dregs (xmm0 and xmm1).
Doesn't it qualify as a clang bug? Or is our interpretation of ABI erroneous?
I would opt for the later...

Le mar. 5 nov. 2019 à 23:54, Balázs Kósi <[hidden email]> a écrit :
Hi all,

I'm having trouble with calling struct returning functions with alien on a 64bit linux vm using the hidden first argument mechanism [1]. The function gets called, but the result is not copied into the struct pointed by the hidden argument.

I tried to debug what happens with gdb, but everything looks fine, except not getting the result back. The address is copied into %rdi (regs[0]) in dax64business.h:92 [2], which is exactly what the spec says [3].

Here is a minimal example calling a C function returning a struct with two doubles:

at34 := Alien lookup: 'at34' inLibrary: 'libalientest.so'.
at34 primFFICallResult: nil with: (r := Alien newC: 16) pointer.
vec := (r doubleAt: 1) @ (r doubleAt: 9). r free. vec " 0.0@0.0 "

returns 0.0@0.0 instead of 3.0@4.0

the C code goes like this:

typedef struct atvect{double x, y;} atvect;

atvect atv(double x, double y) {
  atvect v = {x, y};
  return v;
}

atvect at34() {
  return atv(3.0, 4.0);
}

The vm is sqcogspur64linuxht 5.0-201910291408. The image is a recent trunk image (update: #19142), image format 68021 (64 bit). Alien is Alien-Core-TorstenBergmann.101.mcz.

What am I missing?

Thanks, Balázs

[1] "The rules for structure results vary slightly by platform. Most functions returning structures expect a “hidden” first parameter holding the result struct’s address. Because the FFI provides no abstraction one must pass this parameter explicitly. ... On linux all struct results are re-turned through the hidden first argument mechanism ... ".



[3] "2. If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first
argument to the function. In effect, this address becomes a “hidden” first ar-
gument. This storage must not overlap any data visible to the callee through
other names than this argument.
On return %rax will contain the address that has been passed in by the
caller in %rdi."