May 30, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Martin M. Pedersen | "Martin M. Pedersen" <mmp@www.moeller-pedersen.dk> wrote in message news:ad5iq8$1ri2$1@digitaldaemon.com... > 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. It doesn't. The C++ standard does not specify how it is done, AFAIK, it just says that function must be reentrant regardless of what it returns. I never checked output of most used C++ compilers you've mentioned, and the way I described (callee allocates struct on heap, caller deallocates), I've seen in one incomplete C++ compiler a friend of mine was trying to write. At least it worked... |
May 30, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Karl Bochert |
I thought that the caller would leave the space on the stack to hold the return values. Basically that's equivalent to your version just that the first part (before the push EBP instruction) is done by the caller.
int, int, int foo()
{
return 1, 2, 3;
}
compiles to:
push EBP
mov EBP, ESP
mov [EBP+16], 1
mov [EBP+12], 2
mov [EBP+8], 3
mov ESP, EBP
pop EBP
ret
This function does not necessarily need the frame pointer EBP (In GCC, you can compile with option -fomit-frame-pointer), in this case the function could be even smaller:
mov [ESP+12], 1
mov [ESP+8], 2
mov [ESP+4], 3
ret
But this is an optimisation which is not necessary at the moment. Now let's look at some calling code:
int a, b, c;
a, b, c = foo();
In my model, the caller leaves room on the stack for the return values to accumulate. So the example would compile to:
sub ESP, 12 ; make room for 3 int on the stack
call _foo ; call foo
mov _a, [ESP+8] ; store results
mov _b, [ESP+4]
mov _c, [ESP]
add ESP, 12 ; clean up stack
Then, my second example function bar(), which is an ordinary function that takes 3 parameters and returns 1 int.
int bar( int x, int y, int z )
{
return x + y + z;
}
I assume that a single integer value is not returned on the stack but in register EAX, as for instance GCC does. Then the function might compile into this code (again with full frame pointer enabled):
push EBP
mov EBP, ESP
mov EAX, [EBP+16]
add EAX, [EBP+12]
add EAX, [EBP+8]
mov ESP, EBP
pop EBP
ret
As you can see, the first return value of function foo() is addressed as [EBP+16] within foo(), which is the same location as the first parameter of function bar(). So you can substitute the return of function foo() for any 3 int parameters for a function call. I follow my example:
int sum =
bar( foo() );
which should compile to:
sub ESP, 12 ; prepare to call function foo
call _foo ; will leave 3 return values on the stack
call _bar ; same as if we had pushed 3 single parameters
add ESP, 12 ; clean up stack
mov _sum, EAX
So what do you think is this easy to implement in the compiler?
Karl Bochert <kbochert@ix.netcom.com> schrieb in im Newsbeitrag: 1103_1022775181@bose...
> > 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 31, 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:ad61eg$242$1@digitaldaemon.com... > int sum = > bar( foo() ); > > which should compile to: > > sub ESP, 12 ; prepare to call function foo > call _foo ; will leave 3 return values on the stack > call _bar ; same as if we had pushed 3 single parameters > add ESP, 12 ; clean up stack > mov _sum, EAX > > So what do you think is this easy to implement in the compiler? Yes, you've convinced me... it's all simplier than I thought. Practically, it's the same out-parameters, only hidden. Oh well. I guess it would be a nice bit of syntactic sugar. |
June 01, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | "Pavel Minayev" <evilone@omen.ru> wrote in message news:ad5kma$2bro$1@digitaldaemon.com... > "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. > Can you explain your POV? I apparently missed it. |
June 01, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to anderson | "anderson" <anderson@firestar.com.au> wrote in message news:ad9bos$12oh$1@digitaldaemon.com... > > > PS - Does D allow for overloading of return types? > > > > No, and I hope it never will. > > > > Can you explain your POV? I apparently missed it. Function overloading based on return type is a complex, hardly predictable feature, especially in a Cish weak type environment used by D. I think it adds more confusion than benefits. And, after all, one may always use out parameters to imitate it... |
June 01, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | But if one can use out to imitated it, why not use the same rules for the return type? Why do we need to use workarounds to do the same thing? I would be inclined to agree with you if your argment was stronger. "Pavel Minayev" <evilone@omen.ru> wrote in message news:ad9qlf$1iue$1@digitaldaemon.com... > "anderson" <anderson@firestar.com.au> wrote in message news:ad9bos$12oh$1@digitaldaemon.com... > > > > > PS - Does D allow for overloading of return types? > > > > > > No, and I hope it never will. > > > > > > > Can you explain your POV? I apparently missed it. > > Function overloading based on return type is a complex, hardly predictable > feature, especially in a Cish weak type environment used by D. I think > it adds more confusion than benefits. And, after all, one may always use out > parameters to imitate it... > > |
June 01, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to anderson | "anderson" <anderson@firestar.com.au> wrote in message news:adag2i$e2q$1@digitaldaemon.com... > But if one can use out to imitated it, why not use the same rules for the return type? Why do we need to use workarounds to do the same thing? Out arguments cannot be typecasted: void foo(out short n); void foo(out byte n); ... int n; foo(n); // error! You cannot cast it to short, or do something else like that. Function wants a short oR byte, and you have to provide it a short or byte. Ambiguity is impossible. Now, if we had overloading based on return arguments, we'd have this: short foo(); byte foo(); ... int n; n = foo(); // which version is called? A headache for both the compiler (to find out such cases), and the developer. Okay, so this case is simple, but call to foo() might have happened in a complex expression, and due to the weak nature of D type system, many type conversions could happen, so it is hard to tell the desired type for the return value of foo() at some point. |
June 02, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | "Pavel Minayev" <evilone@omen.ru> wrote in message news:adak4l$j29$1@digitaldaemon.com... > "anderson" <anderson@firestar.com.au> wrote in message news:adag2i$e2q$1@digitaldaemon.com... > > > But if one can use out to imitated it, why not use the same rules for the > > return type? Why do we need to use workarounds to do the same thing? > > Out arguments cannot be typecasted: > > void foo(out short n); > void foo(out byte n); > ... > int n; > foo(n); // error! > > You cannot cast it to short, or do something else like that. Function wants a short oR byte, and you have to provide it a short or byte. Ambiguity is impossible. > > Now, if we had overloading based on return arguments, we'd have this: > > short foo(); > byte foo(); > ... > int n; > n = foo(); // which version is called? > > A headache for both the compiler (to find out such cases), and the developer. Okay, so this case is simple, but call to foo() might have happened in a complex expression, and due to the weak nature of D type system, many type conversions could happen, so it is hard to tell the desired type for the return value of foo() at some point. Ok, seems resonable. So it's D fault for having a weak type system for returns types. And you like D having weak return types? |
June 02, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to anderson | Sorry I ment
Ok, seems resonable. So it's D fault for having a weak type system for returns types. And you like D having weak typing for return?
>
> Ok, seems resonable. So it's D fault for having a weak type system for returns types. And you like D having weak return types?
>
>
|
June 02, 2002 Re: Multiple returns / was: x, y, z = 0 | ||||
---|---|---|---|---|
| ||||
Posted in reply to anderson | "anderson" <anderson@firestar.com.au> wrote in message news:adck5l$2mun$1@digitaldaemon.com... > Ok, seems resonable. So it's D fault for having a weak type system for returns types. And you like D having weak typing for return? Yes. I don't want to perform a cast each time I assign a value returned by a double function to int. BTW, this applies not only to the return statement, but to the entire system. It's not Java, and it's another reason why I like D. ... now if we only didn't need cast to assign a void* pointer to something else (like in C) ... |
Copyright © 1999-2021 by the D Language Foundation