[gambit-list] returning multiple values from a c-lambda?

Christian Jaeger christian at pflanze.mine.nu
Fri Apr 20 10:41:40 EDT 2007


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.




More information about the Gambit-list mailing list