Hello! Is there any plans for callconv that allows passing variable number of arguments and also receiving argument count on callee side? This is a must-have when making a Scheme implementation. At the moment I am having runstack which is stored in per-thread storage and I push/pop arguments there and store TLS in pinned reg but it seems like not the best solution
UPD: I've tested doing return_call_indirect
with 32 arguments to a function which accepts 24 arguments and it seems like it works and runs properly? The computation result does not change so stack is not broken and Rust code runs fine after function returns. How unsafe is this way of doing calls?
We don’t support variadic ABIs directly (PRs welcome) but in various places where we need a dynamic number of args eg in wasmtime at hostcall boundaries we emulate it by building an arg array in memory. I wouldn’t recommend calling a function with a statically incorrect signature — that’s undefined behavior even if it happens to work by luck in some cases
@Adel Prokurov What i gather the guile scheme wasm backend does to supported variadic functions is :
Make all functions have the same number of parameters as the number of registers available for parameter passing ( can be the vast majority of them at the discretion of the compiler )
Then treat each parameter as a register.
GHC also does this when compiling to the llvm backend ( but for other reasons )
I would like to know if this would result in ineficient code or a more complicated compiler compared to if cranelift supported variadic parameter passing in a way that fit scheme. I guess the answer to the second question is no, since the vast majority of archs have either 16 o 32 registers
I think that in general such schemes will tend to produce suboptimal code by virtue of losing information. For a simple example: if every call has N arguments (where N is the maximal number of register-passed args) from the point of view of the compiler backend, then every call needs to put something in all of those registers, so you'll have a lot of extraneous moves (even if move-immediate-0). The right answer from a global complexity perspective is to support this properly in the ABI code of the compiler -- we just haven't had a compelling use-case that would justify spending the time of the few maintainers (over all the other things we need to be working on).
If I were building my own calling convention / language implementation on top of Cranelift, I'd probably reach for the "array trick" I mentioned above for the variadic part -- so e.g. printf(char* fmt, ...)
would pass fmt
as a normal arg, and then the ...
would be lowered to a pointer/length of args in memory (could be a stackslot in caller's frame)
of course that doesn't match the native host's ABI for variadic functions, but I gather that's not the question here if Guile's lowering is also an option
Due to the dynamic shenanigans of scheme. Anything can be variadic. For example you can make a function that returns a callable that varies arity wise at runtime
(define (make-function n)
(cond [(= n 2) (lambda (x y) (+ x y))] ;; 2-argument function
[(= n 3) (lambda (x y z) (* x y z))] ;; 3-argument function
[else (lambda args args)]))
Also, calling a non variadic function with n arguments is the same as calling a variadic function. There is no straightforward way to separate variadic and non variadic parameters. What i am trying to say, is that the "array trick" would have to be the only argument used by functions. Native ones couldnt be used
fun fact, chatgpt produced that code, but it does work as intended ( tested in DrRacket )
fun fact, chatgpt produced that code
in general, I would recommend not using LLMs to generate code for compiler or programming language runtime implementations -- the subtle bugs that that can introduce will waste your time far more than you gain. Not interested in discussing further, just an observation.
But that code isnt a runtime implementation? Is just a bog standard example of scheme code code
Well, anything PL-related really. I'm not really interested in the normalization of using LLMs to work on topics that require precision, so I'll be bowing out. All the best!
Anyhorse. You mentioned variadic calling convention could be added to cranelift. What would its interface? How would that work behind the scenes?
-One possible mitigation to the performance problem you mentioned with using a single calling convention for all functions. Would be to tell the compiler anything could go in an argument
Function1( var1, var2 , ANYTHING)
Last updated: Feb 27 2025 at 23:03 UTC