Thread overview | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
May 29, 2002 Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
I'm moving this discussion to a new thread. What about multiple return values, like in LUA: --- LUA code begin --- function foo() return 1, 2, 3 end a, b, c = foo() function bar( x, y, z ) return x + y + z end sum = bar( foo() ) --- LUA code end --- |
May 29, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Christian Schüler | "Christian Schüler" <cschueler@gmx.de> wrote in message news:ad36rt$16n9$1@digitaldaemon.com... > I'm moving this discussion to a new thread. > What about multiple return values, like in LUA: And how do you want this to be implemented? Lua is a bytecode interpreter, it has its own stack-based VM, and return values are passed on the stack... it's all easily handled. But I don't think you can easily do it in a language which compiles to native code. BTW, out-parameters are supposed to exist for such cases =) |
May 29, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | Pavel Minayev wrote: > BTW, out-parameters are supposed to exist for such cases =) I keep forgetting about those! Hey Walter, is there any way to omit an out parameter? Of course, inout parameters are required...but there any way to skip an out parameter? I'd hate to make a temp variable whenever there was an out parameter I didn't use. This would probably require the compiler to declare a temporary for you on the stack, and pass that as the out parameter...but better the compiler than me, I always say! :) -- The Villagers are Online! villagersonline.com .[ (the fox.(quick,brown)) jumped.over(the dog.lazy) ] .[ (a version.of(English).(precise.more)) is(possible) ] ?[ you want.to(help(develop(it))) ] |
May 29, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | > But I don't
> think you can easily do it in a language which compiles to
> native code.
Yes, that's the point. I was thinking about it how such a feature could be implemented. Let's suppose you can declare a function to have multiple returns in a compiled language (like C or D):
int, int, int foo()
{
return 1, 2, 3;
}
If this function was to be compiled into assembler, it would leave, on the stack, three integers. There's basically nothing wrong with that, as the caller is responsible for cleaning up the stack, and, through the function prototype, it is known at compile time what return values a function leaves. (Think as an alternative that the function foo() would have returned a structure containing 3 integers, pretty legal in C++).
Now suppose that the assignment operator = is a function that can take an arbitrary number of parameters, not just one. Let's suppose the assignment operator is declared as:
inline operator =( ... )
That is, the assignment operator can take any number of parameters, from the stack. If there is written:
int a, b, c;
a, b, c = foo();
the assignment operator takes the 3 values from the stack that the function foo() has left, assigning them to a, b and c. The compiler would have to make sure, at compile time, that the number and the types of the arguments match. Of course, in the final implementation when the language is optimized, things doesn't take the detour via the physical stack, but this view is helpful in understanding whats going on.
With this in mind, the forwarding of multiple return values as function parameters is a no-brainer:
int bar( int x, int y, int z )
{
return x + y + z;
}
int sum = bar( foo() );
The function bar() takes three parameters, which must be pushed onto the stack before calling the function. Instead of pushing single arguments, they could pretty much be left behind from the call to the function foo(). Again the compiler will check at compile time from the prototypes that everything matches together.
Thoughts, anyone?
|
May 30, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Christian Schüler | "Christian Schüler" <cschueler@gmx.de> wrote in message news:ad3cnv$1dr6$1@digitaldaemon.com... > Yes, that's the point. I was thinking about it how such a feature could be implemented. Let's suppose you can declare a function to have multiple returns in a compiled language (like C or D): > > int, int, int foo() > { > return 1, 2, 3; > } > > If this function was to be compiled into assembler, it would leave, on the stack, three integers. There's basically nothing wrong with that, as the caller is responsible for cleaning up the stack, and, through the function D does not use the C calling convention (not always, at least). So it's wrong to assume that the caller will clean up the stack. Also, how exactly do you want to pass something on the stack in such a way that callee can read it? Don't forget that you also have the PUSHed value of EBP, and then the return address. You can POP EBP first, of course, and then push the return values, but: 1) You then don't have access to local variables (since EBP is restored to its old value), and registers might not be enough (what if function returns 10 values?) 2) RET expects return address to be on top. This can be done by POPping it into the register, then pushing all the return values, then PUSHing the return address again. Kinda messy. > prototype, it is known at compile time what return values a function leaves. > (Think as an alternative that the function foo() would have returned a > structure containing 3 integers, pretty legal in C++). But the structure isn't pushed on the stack! It is allocated somewhere, and pointer to it is passed in EAX. By the way, in C, it was even worse: the structure was allocated once, and only one copy of it existed for one function... so each call rewrote data generated by previous one. C++ provides a workaround for this, at a cost of efficiency. |
May 30, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | I think it's a cool idea, I staying away from the ASM part. int, int, int a() { ... return x,y,z; } interprate the same as int a(int out c, int out d) { ... return x,y,z; //for all out params } interprate which would interparte to int a(int out c, int out d) { ... c = y; d = z; return x; } or alternativly make it the same as doing, {int a, int b , int c} a() { ... return {x,y,z}; } If speed is a concern users can just use the single version (but they'd probably end up using one of the later algorithms anyways) , because speed is not an issue in all parts of a problem. PS - Does D allow for overloading of return types? "Pavel Minayev" <evilone@omen.ru> wrote in message news:ad4djm$8na$1@digitaldaemon.com... > "Christian Schüler" <cschueler@gmx.de> wrote in message news:ad3cnv$1dr6$1@digitaldaemon.com... > > > Yes, that's the point. I was thinking about it how such a feature could be > > implemented. Let's suppose you can declare a function to have multiple returns in a compiled language (like C or D): > > > > int, int, int foo() > > { > > return 1, 2, 3; > > } > > > > If this function was to be compiled into assembler, it would leave, on the > > stack, three integers. There's basically nothing wrong with that, as the caller is responsible for cleaning up the stack, and, through the function > > D does not use the C calling convention (not always, at least). So it's wrong to assume that the caller will clean up the stack. > > Also, how exactly do you want to pass something on the stack in such a way that callee can read it? Don't forget that you also have the PUSHed value of EBP, and then the return address. You can POP EBP first, of course, and then push the return values, but: > > 1) You then don't have access to local variables (since EBP > is restored to its old value), and registers might not be enough > (what if function returns 10 values?) > > 2) RET expects return address to be on top. This can be done by POPping it into the register, then pushing all the return values, then PUSHing the return address again. Kinda messy. > > > prototype, it is known at compile time what return values a function > leaves. > > (Think as an alternative that the function foo() would have returned a > > structure containing 3 integers, pretty legal in C++). > > But the structure isn't pushed on the stack! It is allocated somewhere, and pointer to it is passed in EAX. > > By the way, in C, it was even worse: the structure was allocated once, and only one copy of it existed for one function... so each call rewrote data generated by previous one. C++ provides a workaround for this, at a cost of efficiency. > > > > |
May 30, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | > D does not use the C calling convention (not always, at least). So it's wrong to assume that the caller will clean up the stack. > It is quite easy for the compiler to force an appropriate calling convention on a multiple-return function. > Also, how exactly do you want to pass something on the stack in such a way that callee can read it? Don't forget that you also have the PUSHed value of EBP, and then the return address. You can POP EBP first, of course, and then push the return values, but: > > 1) You then don't have access to local variables (since EBP > is restored to its old value), and registers might not be enough > (what if function returns 10 values?) > > 2) RET expects return address to be on top. This can be done by POPping it into the register, then pushing all the return values, then PUSHing the return address again. Kinda messy. > Compared to what else the compiler must do, this seems trivial. Another approach would be to use an 'on-stack' calling convention. (The D calling convention??) Roughly (I think): int a, int b foo ( int c ) { ... Compiler creates 'pseudo-parameters' for returns-- pop EAX push 0 push 0 push EAX And then the normal entry code -- push EBP mov EBP, ESP Now: a is at [ebp + 8] b is at [ebp + 12] c is at [ebp + 16] Return is just a normal return: mov ESP, EBP ret and the caller does: call foo pop EAX ; get 'a' mov [EBP - 4], eax ; put it somewhere pop EAX ; get 'b' add ESP, 8 ; standard parameter trim > By the way, in C, it was even worse: the structure was allocated once, and only one copy of it existed for one function... so each call rewrote data generated by previous one. C++ provides a workaround for this, at a cost of efficiency. I suppose the 'C calling convention' was invented prior to recursion or threading!! :-) Karl Bochert |
May 30, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | Hi, "Pavel Minayev" <evilone@omen.ru> wrote in message news:ad4djm$8na$1@digitaldaemon.com... > But the structure isn't pushed on the stack! It is allocated somewhere, and pointer to it is passed in EAX. > > By the way, in C, it was even worse: the structure was allocated once, and only one copy of it existed for one function... so each call rewrote data generated by previous one. C++ provides a workaround for this, at a cost of efficiency. This is new to me. I just checked the code generated by Microsoft C/C++ in C mode. It allocates an anonymous stack variable for the return value in the calling code for each function call (not each invocation), which is perfectly reentrant. Small structs are returned in registers. I checked GCC too, and it also allocates the structure on the stack in the calling code. The behaviour you describe above sounds like a compiler bug to me. Regards, Martin M. Pedersen |
May 30, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to anderson | "anderson" <anderson@firestar.com.au> wrote in message news:ad5clq$1jm8$1@digitaldaemon.com... > PS - Does D allow for overloading of return types? No, and I hope it never will. |
May 30, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Karl Bochert | "Karl Bochert" <kbochert@ix.netcom.com> wrote in message news:1103_1022775181@bose...
> I suppose the 'C calling convention' was invented prior to recursion or threading!! :-)
Not really. I guess those guys were just too lazy to cover this case properly, bearing in mind that C was a tool made for private use at first...
|
Copyright © 1999-2021 by the D Language Foundation