changeset 557:b36a738c8975

- dyncallback: fix for calling back win/x64 C++ methods returning non-trivial aggregates (thanks Raphael!) - test/callback_plain_c++: * added test code for C++ method callbacks returning non-trivial aggregates * makefile linker command fix (was linking assuming c and not c++) - test/plain_c++: comments for completeness
author Tassilo Philipp
date Sat, 20 Aug 2022 21:04:15 +0200
parents 49b60ca068c2
children 9eb5d92e5c5d
files dyncallback/dyncall_args_x64.c test/callback_plain_c++/Makefile.embedded test/callback_plain_c++/Makefile.generic test/callback_plain_c++/test_main.cc test/plain_c++/test_main.cc
diffstat 5 files changed, 51 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/dyncallback/dyncall_args_x64.c	Mon Jul 11 23:17:50 2022 +0200
+++ b/dyncallback/dyncall_args_x64.c	Sat Aug 20 21:04:15 2022 +0200
@@ -149,30 +149,28 @@
 }
 
 
-/* A 16 byte struct would be sufficient for System V (because at most two of the four registers can be full). */
-/* But then it's much more complicated to copy the result to the correct registers in assembly. */
-typedef struct {
-  DClonglong r[2]; /* rax, rdx   */
-  DCdouble   x[2]; /* xmm0, xmm1 */
-} DCRetRegs_SysV;
-
 void dcbReturnAggr(DCArgs *args, DCValue *result, DCpointer ret)
 {
   int i;
   DCaggr *ag = *(args->aggrs++);
 
-  if(!ag) {
-    /* non-trivial aggr: all we can do is to provide the ptr to the output space, user has to make copy */
-    result->p = (DCpointer) args->reg_data.i[0];
-    return;
-  }
-
-  if (args->aggr_return_register >= 0) {
+  if(args->aggr_return_register >= 0) {
     DCpointer dest = (DCpointer) args->reg_data.i[args->aggr_return_register];
-    memcpy(dest, ret, ag->size);
+    if(ag)
+      memcpy(dest, ret, ag->size);
+    else {
+      /* non-trivial aggr: all we can do is to provide the ptr to the output space, user has to make copy */
+    }
     result->p = dest;
   } else {
 #if defined(DC_UNIX)
+    /* A 16 byte struct would be sufficient for System V (because at most two of the four registers can be full). */
+    /* But then it's much more complicated to copy the result to the correct registers in assembly. */
+    typedef struct {
+      DClonglong r[2]; /* rax, rdx   */
+      DCdouble   x[2]; /* xmm0, xmm1 */
+    } DCRetRegs_SysV;
+
     /* a max of 2 regs are used in this case, out of rax, rdx, xmm0 and xmm1 */
     /* space for 4 qwords is pointed to by (DCRetRegs_SysV*)result */
     DClonglong *intRegs = ((DCRetRegs_SysV*)result)->r;
--- a/test/callback_plain_c++/Makefile.embedded	Mon Jul 11 23:17:50 2022 +0200
+++ b/test/callback_plain_c++/Makefile.embedded	Sat Aug 20 21:04:15 2022 +0200
@@ -11,8 +11,8 @@
 
 .PHONY: all clean install
 
-${APP}: ${OBJS} 
-	${CC} ${OBJS} ${LDFLAGS} ${LDLIBS} -o ${APP} 
+${APP}: ${OBJS}
+	${CXX} ${OBJS} ${LDFLAGS} ${LDLIBS} -o ${APP}
 
 clean:
 	rm -f ${APP} ${OBJS}
--- a/test/callback_plain_c++/Makefile.generic	Mon Jul 11 23:17:50 2022 +0200
+++ b/test/callback_plain_c++/Makefile.generic	Sat Aug 20 21:04:15 2022 +0200
@@ -7,7 +7,7 @@
 .PHONY: all clean install
 all: ${APP}
 ${APP}: ${OBJS} 
-	${CC} ${CFLAGS} ${LDFLAGS} ${OBJS} ${LDLIBS} -o ${APP}
+	${CXX} ${CFLAGS} ${LDFLAGS} ${OBJS} ${LDLIBS} -o ${APP}
 clean:
 	rm -f ${APP} ${OBJS}
 install:
--- a/test/callback_plain_c++/test_main.cc	Mon Jul 11 23:17:50 2022 +0200
+++ b/test/callback_plain_c++/test_main.cc	Sat Aug 20 21:04:15 2022 +0200
@@ -111,7 +111,7 @@
 
 char cbNonTrivAggrReturnHandler(DCCallback* cb, DCArgs* args, DCValue* result, void* userdata)
 {
-  printf("reached callback\n");
+  printf("reached callback (for sig \"%s\")\n", *(const char**)userdata);
   dcbReturnAggr(args, result, NULL);    // this sets result->p to the non-triv aggr space allocated by the calling convention
   *(NonTriv*)result->p = NonTriv(1, 3); // explicit non-copy ctor and assignment operator, so not using NonTriv's statics a and b
 
@@ -119,31 +119,49 @@
 }
 
 
+// class with single vtable entry, used to get the compiler to generate a
+// method call; entry is at beginning w/o offset (see plain_c++/test_main.cc
+// about potential vtable entry offsets)
+struct Dummy { virtual NonTriv f() = 0; };
+
+
 int testNonTrivAggrReturnCallback()
 {
   int ret = 1;
 
+  const char *sigs[] = {
+    ")A",   // standard call
+    "_*p)A"  // thiscall w/ this-ptr arg (special retval register handling in win/x64 calling conv)
+  };
+
+
+  for(int i=0; i<sizeof(sigs)/sizeof(sigs[0]); ++i)
   {
-    DCCallback* cb;
+    int is_method = (sigs[i][0] == '_' && sigs[i][1] == '*');
+
     DCaggr *aggrs[1] = { NULL }; // one non-triv aggr
-    cb = dcbNewCallback2(")A", &cbNonTrivAggrReturnHandler, NULL, aggrs);
+    DCCallback* cb = dcbNewCallback2(sigs[i], &cbNonTrivAggrReturnHandler, sigs+i, aggrs);
 
-    NonTriv result = ((NonTriv(*)())cb)(); // potential copy elision on construction
+    DCpointer fakeClass[sizeof(Dummy)/sizeof(sizeof(DCpointer))];
+    fakeClass[0] = &cb; // write method ptr f
+
+    // potential copy elision on construction
+    NonTriv result = is_method ? ((Dummy*)&fakeClass)->f() : ((NonTriv(*)())cb)();
 
     int a = NonTriv::a-1;
     int b = NonTriv::b-1;
-    printf("successfully returned from callback 1/2\n");
+    printf("successfully returned from callback 1/2 of \"%s\"\n", sigs[i]);
     printf("retval w/ potential retval optimization and copy-init (should be %d %d for init or %d %d for copy, both allowed by C++): %d %d\n", 1, 3, a, b, result.i, result.j);
 
     ret = ((result.i == 1 && result.j == 3) || (result.i == a && result.j == b)) && ret;
 
-	// avoid copy elision on construction
-	result.i = result.j = -77;
-	result = ((NonTriv(*)())cb)(); // potential copy elision
+    // avoid copy elision on construction
+    result.i = result.j = -77;
+    result = is_method ? ((Dummy*)&fakeClass)->f() : ((NonTriv(*)())cb)(); // potential copy elision
 
     a = NonTriv::a-1;
     b = NonTriv::b-1;
-    printf("successfully returned from callback 2/2\n");
+    printf("successfully returned from callback 2/2 of \"%s\"\n", sigs[i]);
     printf("retval w/ potential retval optimization and copy-init (should be %d %d for init or %d %d for copy, both allowed by C++): %d %d\n", 1, 3, a, b, result.i, result.j);
 
     dcbFreeCallback(cb);
--- a/test/plain_c++/test_main.cc	Mon Jul 11 23:17:50 2022 +0200
+++ b/test/plain_c++/test_main.cc	Sat Aug 20 21:04:15 2022 +0200
@@ -87,11 +87,16 @@
 
 /*
  * the layout of the VTable is non-standard and it is not clear what is the initial real first method index.
- * so far it turns out that:
- * on vc/x86  : 1
- * on GCC/x86 : 2
+ * so far it turns out, *iff* dtor is defined, that:
+ * on msvc/x86  : 1
+ * on msvc/x64  : 1
+ * on gcc/x86   : 2
+ * on gcc/x64   : 2
+ * on clang/x86 : 2
+ * on clang/x64 : 2
  */
 
+// vtable offset to first func of class Value and class ValueMS, skipping dtor
 #if defined DC__C_MSVC
 #define VTBI_BASE 1
 #else