C::DynaLib progress

I took over the first perl FFI, C::DynaLib some years ago, when gcc still defaulted to the simple cdecl calling convention. Push args with alloca on the stack, and call the functions without args, but the alloca'ed args were picked up by the called func. Simple and fast.

For some time now cdecl does not work anymore OOTB, and I thought it's the new gcc-4 stack-protector, tls or such which broke the ABI. It was not. It also fails with gcc-3.4 with the same new layout, but at least I understand now the new gcc ABI and found a way to continue with cdecl. Interestingly some platforms like freebsd work OOTB with gcc and cdecl, msvc also.
The fallback to cdecl is the hack30 scheme, which is ugly. hack30 cannot do floats nor double.

The new layout which I called cdecl3 forbids writing the first 3 words on the stack. You can write to it but before calling the function the compiler puts 3 words there again. So the three first args are always lost. fstack-protector play similar games, but only with structs or char*, so it might be some tls support or such. Anyway.
See cdecl3.c at github.

I'm testing this now with an enhanced testcall.c in the perl Makefile.PL step:
layout adjustment (-4 currently is my favorite),
stack_reserve (place 3/6 args at the front to overcome gcc),
and another new cdecl parameter:
arg_align: 64 bit requires you to put 32 bit values 64-bit aligned on the call-stack. This didn't work before, I think I have fixed that now.

If anyone is interested in C ABI's look at Wikipedia - X86_calling_conventions

Why bothering about C::DynaLib at all?
It is besides P5NCI the only FFI without external library and it has more features than the other FFI's.
CTypes (non-public yet, only for mentors) will have more, but currently C::DynaLib is leading, if it would work.

At http://gist.github.com/395160 I started another nice header parser, independent on Convert::Binary::C.
With Convert::Binary::C you can only parse structs, with the new parser - based on gcc-ftranslation-unit - everything, mostly functions and structs if used as its args.
This will also be used for CTypes, the gsoc project by Ryan "doubi" Jendoubi, which I'm mentoring, but so far I'm building it for C::DynaLib only, because the CTypes specs are missing.
This will need a few months until this is clear.
But at least I continued on struct header parsing, which "almost" works now.

Any FFI without external library should really be in core, so that any poor user without compiler can use external libs (dll/so). But first the FFI must be stable and have enough bindings to succeed. Win32::API has much more (samples and users), but is limited to one platform only. C::DynaLib can do all platforms and has similar nice semantics.

BTW: I wrote my first FFI 2001 and then I wanted to push another one to emacs - http://autocad.xarch.at/lisp/ffis.html Long time ago. But almost nothing changed.
Just the gcc ABI.

msvc6 and icc still use the simple cdecl ABI which works out of the box, btw.
No protected words at the top of the stack.
freebsd gcc also worked fine in all versions.
cygwin, mingw and linux gcc is changed and needs cdecl3 resp. cdecl6 on 64bit.

Testing other compilers, such as msvc8, msvc9, clang, hpux cc, solaris cc later.
These should just work with the defaults, I guess.

Before:


$ gcc -save-temps -O0 -fno-unit-at-a-time -I/usr/lib/perl5/5.10/i686-cygwin/CORE testcall.c -o testcall -DVERBOSE && ./testcall
stack_align=32,do_adjust=0,grows_downward=1,one_by_one=0
a0-8: 0003709d,0009675a,003acacb,359c46d6,05261433,00fca2d6,00016fcf,004aa9c6,04a2cd24
do_adjust=0
b0-8: 0022cc80,00403020,00000004,00000000,0003709d,0009675a,003acacb,359c46d6,05261433

offset 4 words =>


test -4,do_adjust=0 => which=-16
one_arg=0,reverse=0,adjust=[-16,0]
do_adjust=-16
b0-8: 0022cc70,00403020,00000004,359c46d6,05261433,00fca2d6,00016fcf,004aa9c6,04a2cd24

with adjust=-16 the offset is okay, but the first 3 args are not there. 359c46d6 is the forth, but 1-3 are missing.


test 0,do_adjust=-16 => which=0
three_args=0,adjust=[-16,0]
do_adjust=0
b0-8: 0022cc80,00403020,00000004,359c46d6,0003709d,0009675a,003acacb,359c46d6,05261433
test -4,do_adjust=0 => which=-16
one_arg=0,adjust=[-16,0],do_adjust=0
do_adjust=0
b0-8: 0022cc80,00403020,00000004,359c46d6,0003709d,0009675a,003acacb,359c46d6,05261433
test -4,do_adjust=0 => which=-16
three_args=0,adjust=[-16,-16],do_adjust=0
try adjust=-16
do_adjust=-16
b0-8: 0022cc70,00403020,00000004,359c46d6,05261433,00fca2d6,00016fcf,004aa9c6,04a2cd24
test 0,do_adjust=-16 => which=0
one_arg=0,adjust=[0,-16],reverse=0
do_adjust=-16
b0-8: 0022cc70,00403020,00000004,359c46d6,05261433,00fca2d6,00016fcf,004aa9c6,04a2cd24
test 0,do_adjust=-16 => which=0
three_args=0,adjust=[0,0],reverse=0
cdecl failed

Afterwards:


Testing how to pass args to a function...
$ gcc -DPERL_USE_SAFE_PUTENV -U__STRICT_ANSI__ -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include -O0 -DINCLUDE_ALLOCA -I/usr/lib/perl5/5.10/i686-cygwin/CORE testcall.c -o testcall -g -DVERBOSE
$ ./testcall
./testcall.exe
stack_align=32,do_adjust=0,grows_downward=1,one_by_one=0
a0-8: 0003709d,0009675a,003acacb,359c46d6,05261433,00fca2d6,00016fcf,004aa9c6,04a2cd24
compute adjust[0],stack_reserve,reverse
test: one_by_one=0,do_adjust=0,stack_reserve=0,do_reverse=0,arg_align=4
b0-8: 0022cc00,00403040,00000004,00000001,0003709d,0009675a,003acacb,359c46d6,05261433
test -4,do_adjust=0,stack_reserve=0 => which=-16
one_arg=0,reverse=0,adjust=[-16,0]
try with computed do_adjust=-16
test: one_by_one=0,do_adjust=-16,stack_reserve=0,do_reverse=0,arg_align=4
b0-8: 0022cbf0,00403040,00000004,359c46d6,05261433,00fca2d6,00016fcf,004aa9c6,04a2cd24
test 0,do_adjust=-16,stack_reserve=3 => which=0
one_arg=0,stack_reserve=3,adjust=[0,0]
verify and compute adjust[1] for more args
test: one_by_one=0,do_adjust=-16,stack_reserve=3,do_reverse=0,arg_align=4
test ok
three_args=1,adjust=[0,0]
adjust[1] maybe different?
try a last time adjust=-16 (aligned stack computed p2-p1/2)
test: one_by_one=0,do_adjust=-16,stack_reserve=3,do_reverse=0,arg_align=4
test ok
one_arg=1,adjust=[0,0],reverse=0
test: one_by_one=0,do_adjust=-16,stack_reserve=3,do_reverse=0,arg_align=4
test ok
three_args=1,adjust=[0,0],reverse=0
cdecl3 ok
Succeeded.

2 Comments

Very nice article! As a strictly non-C guy who tried to create bindings to external libraries multiple times and mostly failed, I really welcome all of this attention to these kinds of projects.

By the way, your CTypes link requires a Sign In and doesn't seem to be public :)

About Reini Urban

user-pic Working at cPanel on cperl, B::C (the perl-compiler), parrot, B::Generate, cygwin perl and more guts, keeping the system alive.