May 30, 2002
"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
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
"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
"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
"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
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
"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
"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
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
"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) ...