Returning multiple values: semantics
June 03, 2023This is an announcement of an upcoming feature. Please see the previous post for syntactic aspect of WCPL multiple values support.
Implementing support for returning and accepting multiple values in WCPL is
easy: WASM stack architecture makes collecting and spreading the values as
natural as passing arguments. In fact, the internal mechanism is exactly
the same, so the same code could be used to generate pass-values / return-values
code sequences. WCPL already uses the multiple-values convention by representing
void
functions as WASM procedures returning no values, so extending it to
cover 2 and more values is straightforward.
There is one problem though: programmers would expect that multiple values will
behave as single values with respect to type promotion, e.g. if one returns an
int
and a float
, but the accepting interface expects, say, a long long
and a
double
, WCPL should generate the appropriate conversion/promotion instructions.
It is easy with a single value because instructions work with the value on the
top of the stack, but harder if values deeper in stack need to be promoted.
WASM has no stack manipulation instructions allowing one to reach deeper into
the stack, so WCPL has to generate temporary variables (registers), move the
values from the stack to the variables, and then promote them to the new types
while putting them back on the stack.
There are also several design decisions that need to be made. WCPL’s multiple values implementation stops short of implementing the following potentially useful features:
-
Expressions returning multiple values. Current implementation allows the
return
statement and right-hand-side of the multiple values definition or assignment to be an initializer-style display (a list of expressions in curly braces) or a call to a function returning multiple values, but nothing else. -
Multiple-values casts, e.g.
({double, double})foo()
. Automatic promotions are supported, but explicit mv casts are not. -
Dropping “extra” values as a part of an automatic promotion. WCPL supports “casting to void”, i.e. dropping all return values when mv function call is executed in statement context, but does not allow passing N values to a context expecting K values where K < N unless K is 0.
-
Splicing multiple return values into another call. Current implementation requires accepted values to be stored in variables and other locations, but does not allow feeding them into another function call or operation directly. Some languages allow that, e.g.:
bar(x, foo()..., y)
splices values returned byfoo
intobar
’s argument list, while(+)(foo()...)
adds two values returned byfoo
.
We will end this post with a complete example, demonstrating most aspects of multiple values manipulation:
Please note that both in function definitions and prototypes the specification of the types for returned values allows naming the values, e.g.:
We don’t recommend this practice for function definitions: the names of the returned values are just ignored by the compiler, but may confuse the reader of the code: they are not variables and can neither be read nor modified.