Cesium icon indicating copy to clipboard operation
Cesium copied to clipboard

Functions with varargs support

Open kant2002 opened this issue 3 years ago • 9 comments
trafficstars

We need varargs support, since almost any simple tutorial use printf. Without that support we cannot go to public probably, otherwise Cesium always look as toy project.

Look for the number 196 in the code to find clues to implement this feature.

kant2002 avatar Aug 14 '22 05:08 kant2002

I agree on that point. Any ideas how such a function could work? The problem is that sometimes it's hard to distinguish a vararg function from the call-site point of view: it may look (and even be declared?) as a normal function.

Could we reuse the existing facilities of vararg functions in the CLI? Do these facilities even exist?

ForNeVeR avatar Aug 14 '22 15:08 ForNeVeR

https://github.com/dotnet/runtime/issues/10478 that's not possible today, even if it would be possible in the future. So plan would be to mimic this. I think we can pass variadic arguments as object[] and that's it. That's will make implementing va_start ambiguous when used incorrectly, but I think that fine. That unlock interop scenarios, which is what's important at this stage. If variadic function would be declared as regular one, and defined as variadic that's we should be able resolve at "link" time, and that's pretty advanced and arcane scenario. I do not think we can afford think too much about it, give that we do not know space very well

kant2002 avatar Aug 14 '22 18:08 kant2002

I think we can pass variadic arguments as object[] and that's it.

To be honest, I'm not sure we can put a pointer into an object[]. Is a pointer even an object? 🤔

Maybe some workaround using stack arrays or linked lists will be required.

ForNeVeR avatar Aug 16 '22 20:08 ForNeVeR

I do find some interesting sample which definitely will complicate our life.

#include <stdio.h>
#include <stdarg.h>

void function1(char* s, ...)
{
    va_list ap;
    int tmp;

    va_start(ap, s);
    tmp = va_arg(ap, int);
    printf(s, tmp);
    va_end(ap);
}

void function2(char* s, int d)
{
    printf(s, d);
}

typedef void (*functionX_t)(char*, int);
typedef void (*functionY_t)(char*, ...);

int main(int argc, char* argv[])
{
    int x = 42;

    /* swap! */
    functionX_t functionX = (functionX_t) function1;
    functionY_t functionY = (functionY_t) function2;

    function1("%d\n", x);
    function2("%d\n", x);
    functionX("%d\n", x);
    functionY("%d\n", x);

    return 0;
}

kant2002 avatar Aug 26 '22 19:08 kant2002

What does the standard say about this? I'm not sure these signatures are technically compatible.

ForNeVeR avatar Aug 26 '22 19:08 ForNeVeR

We can use __arglist and ArgIterator, which are built into DotNet (or copy the implementation, hehehehehe). They work well on Windows, but not so well on Unix. It would also solve the problem with 4 or 8 bytes in VA_ARG.

BadRyuner avatar Feb 25 '24 10:02 BadRyuner

Well, I'd say we want something that will reliably work on all the supported platforms and runtimes.

ForNeVeR avatar Feb 25 '24 11:02 ForNeVeR

We can switch to native va_list. I messed around with linux and windows and made a universal va_list that works on both operating systems. Source: https://gist.github.com/BadRyuner/170d7900d1860730192fdfabd12921ce (produces same output on windows 11 and debian 12) But a problem appears with functions that uses ... instead of va_list (e.g. printf instead of vprintf) This is the only problem blocking the way to a full transition to natively-like variadic functions, which works perfectly with native functions. For dotnet functions, we can use calli hax to get all arguments (RCX, RDX, R8, R9) with a stack pointer and initialize the native va_list or use old va_list. But here pinvoke is the problem. For extern dllimport dotnet creates a wrapper method that nullifies unnecessary pointers and stack to protect the environment. Accordingly, additional arguments will not pass there (if we are talking about ... and not va_list). My solution: use NativeLibrary.Load and NativeLibrary.GetExport for further use in calli, which can send any number of arguments. In doing so, calli does not use wrappers unlike PInvoke. But here another problem arises... NetStandard 2.0 does not provide a NativeLibrary class. Here we have to either create two separate classes for PInvoke (LoadLibrary & GetProcAddress - Windows; dlopen & dlsym - Linux/MacOS) or drop it.

Also msvc generates its own wrapper over printf, where it packs the arguments into a va_list and calls vprintf.

BadRyuner avatar Mar 27 '24 13:03 BadRyuner

I do not like the solution involving any such hacks. Remember that we have to support at least 9 OS/CPU combinations (x86/x64/ARM64 macOS/Win/Lin). I do not believe there's a reliable solution for all those at once.

And why would we want it anyway? Currently we aren't targeting that class of interop.

ForNeVeR avatar Mar 27 '24 16:03 ForNeVeR