Hi Gambit List,
Is there any way to return multiple values from a c-lambda? (other than e.g. creating a list and returning that)
Cheers,
Phil
Afficher les réponses par date
Phil Dawes wrote:
Hi Gambit List,
Is there any way to return multiple values from a c-lambda? (other than e.g. creating a list and returning that)
I don't know what Marc would suggest, but basically you have the same ways as from Scheme code: (a) passing the values to a continuation procedure (manual cps style); I'm not sure about how to achieve tail call optimization though when calling a Scheme function (or, usually, the C wrapper being defined by c-define) from C. (b) calling the "values" function with the values in question (same as (a) but there's no reason to worry about tco since "values" is returning immediately), or alternatively "list" or "vector" or "make-somestructure" (c) writing the values into a vector/tuple/structure which has been passed as argument. (Gambit implements "values" using tuples, which are the same as vectors but with a different type tag.)
It's also possible to allocate scheme objects from C more directly than through calling back to a scheme function. The easier/official variant of those is still objects. These don't move, but are about 3 times slower to allocate/garbage collect than movable objects (which are the default for small objects allocated from scheme). (Allocating movable objects from C is possible from ##c-code but probably not from c-lambda, and messy.)
If you know how many values you're going to return before calling the c-lambda, I'd recomment to allocate a values tuple in advance and fill it from the C code:
(let ((ret (values #f #f #f)));;hmm(*) ((c-lambda (scheme-object ...) scheme-object " ... ___VECTORSET(___arg1,___FIX(0), firstvalue); ___VECTORSET(___arg1,___FIX(1), secondvalue); ___VECTORSET(___arg1,___FIX(2), thirdvalue); ___result=___arg1; ") ret ...))
Since the values tuple is a movable object here, you still have to be careful not to allocate scheme memory in the c-lambda (___arg1 will point to an invalid location after garbage collection has happened).
(*) Note: iirc I've used that approach with ##make-vector and ##make-string, but not with values tuples. There's no ##make-values function. The compiler could theoretically infer from my call that the values tuple above is constant and not allocate a new one, which would be bad for the purpose. If that's true, you'd be forced to either call (values somevariable ..) to prevent that optimization, or allocate it yourself, e.g. from C as still object:
___SCMOBJ res = ___alloc_scmobj (___sBOXVALUES, num_vals<<___LCS, ___STILL); if (___FIXNUMP(result)) { // an error happened ___result=___fal; // then check for #f outside } else { ___VECTORSET(res,.... ___still_obj_refcount_dec(res); ___result=res; }
(In gambit.h there are also: #define ___BEGIN_ALLOC_VALUES(n)___hp[0]=___MAKE_HD_WORDS(n,___sBOXVALUES); #define ___ADD_VALUES_ELEM(i,val)___hp[i+1]=(val); #define ___END_ALLOC_VALUES(n)___ALLOC(n+1); #define ___GET_VALUES(n)___TAG((___hp-n-1),___tSUBTYPED)
but those are probably only used by the C output of the compiler and would only work in ##c-code sections.)
I did use the pre-allocate idea e.g. in cj-posix (see definition of _pipe / pipe in http://scheme.mine.nu/gambit/scratch/cj-posix/2006-12-06/cj-posix.scm). Here it's particularly elegant because I'm feeding a s32vector directly as pointer to the unix pipe(2) function, which fills in the s32 file descriptor number values directly. No type conversion involved :).
Christian.