14

Is the order in which parameters are calculated before a procedure is called defined in Delphi?

IOW, if I have this ugly code (found something like this in a legacy application) ...

function A(var err: integer): integer;
begin
  err := 42;
  Result := 17;
end;

Test(A(err), err);

... is Test guaranteed to receive parameters (17, 42) or could it also be (17, undefined)?


Edit:

Although David's example returns different result with 32-bit and 64-bit compiler, this (luckily) doesn't affect my legacy code because Test(A(err), err) only stores an address of 'err' in the register and it doesn't matter whether the compiler does this before calling A(err) or after.

gabr
  • 26,580
  • 9
  • 75
  • 141
  • 2
    This article may be of interest http://blog.barrkel.com/2008/04/c-evaluation-order-gotcha.html – David Heffernan Jun 13 '12 at 08:14
  • 1
    Another post about this: http://stackoverflow.com/questions/3054526/delphi-compiler-directive-to-evaluate-arguments-in-reverse – Stefan Glienke Jun 13 '12 at 09:58
  • @David, I was aware of this C 'feature' and I learned on SO that Java and C# evaluate from left to right but I couldn't remember where Delphi has this documented (and as we learned, the documentation is wrong). – gabr Jun 13 '12 at 13:07
  • Regarding your edit, a minor correction. The address of err is passed in a register rather than on the stack. But the point is well made. Naturally, changing the code to make the evaluation order explicit would be worthwhile. – David Heffernan Jun 13 '12 at 17:04
  • Fixed. And we did change the code. – gabr Jun 14 '12 at 05:41

2 Answers2

12

The order of parameter evaluation in Delphi is not defined.

As an interesting demonstration of this, the following program has different output depending on whether you target 32 or 64 bit code:

program ParameterEvaluationOrder;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function SideEffect(A: Integer): Integer;
begin
  Writeln(A);
  Result := A;
end;

procedure Test(A, B: Integer);
begin
end;

begin
  Test(SideEffect(1), SideEffect(2));
  Readln;
end.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • In Win64, the order of evaluation is defined by the one and only calling convention, AFAIK. In Win32, there are different calling conventions, and they do not specify the order of evaluation, only the order of passing. – Rudy Velthuis Jun 13 '12 at 09:41
  • 4
    @RudyVelthuis To the best of my knowledge, calling conventions specify the order of *passing* but do not specify order of *evaluation*. So I dispute that comment. – David Heffernan Jun 13 '12 at 09:50
  • IMO, calling conventions can specify whatever they want, WRT calling functions. If the order of evaluation is specified too, for whatever reason the platform developer may have, you better follow the specs. But I agree that, AFAIK, until now, no calling convention has done this. – Rudy Velthuis Jun 13 '12 at 09:53
  • As it happens your SideEffect does not have any side effect. The compiler possibly feels it doesn't have to honor any rules. If 'A' would be 'var', and 'Test' would pass same variable both times (i.e. like in Gabr's example), both 32 and 64 bit comply to documentation. – Sertac Akyuz Jun 13 '12 at 10:41
  • @SertacAkyuz Yes it does have a side effect. `Writeln` is a classic I/O side effect. – David Heffernan Jun 13 '12 at 10:43
  • @David - It doesn't have any side effect as to what values are to be passed to Test. I didn't mean no side affects as in using evaluation order as program flow control. – Sertac Akyuz Jun 13 '12 at 10:45
  • Thanks, David, this is proof enough. – gabr Jun 13 '12 at 10:54
5

Edited: it seems that the compiler may violate the behavior described in the help:

From Calling Conventions help topic (emphasis mine):

The register and pascal conventions pass parameters from left to right; that is, the left most parameter is evaluated and passed first and the rightmost parameter is evaluated and passed last.

MBo
  • 77,366
  • 5
  • 53
  • 86
  • It turns out that the documentation you link to is incorrect. – David Heffernan Jun 13 '12 at 08:25
  • it makes one sad that the behavior of the compiler does not correspond to the only official documentation source – MBo Jun 13 '12 at 08:37
  • I agree. I personally feel it is fine for the evaluation order to be undefined, but the documentation should not state otherwise. – David Heffernan Jun 13 '12 at 08:42
  • The documentation is correct! You have to know that x64 only has one calling convention which is fastcall as Allen described in his blog (http://blogs.embarcadero.com/abauer/2011/10/10/38940) – Stefan Glienke Jun 13 '12 at 09:32
  • @StefanGlienke The documentation is not correct. For example, in x86 register convention, parameters are often evaluated right to left. More discussion here: http://blog.barrkel.com/2008/04/c-evaluation-order-gotcha.html As for the calling convention on x64, I would name it *x64-MS* because `fastcall` is typically used for the specific x86 calling convention that MS tools use. Now, the Delphi x86 `register` convention and the x64-MS are examples "fast-call" calling conventions since they use registers. – David Heffernan Jun 13 '12 at 09:36
  • The docs are indeed wrong. The order of passing is defined, the order of evaluation is not. I would not call it x64-MS, I would simply call it the Win64 calling convention. – Rudy Velthuis Jun 13 '12 at 09:44
  • @RudyVelthuis Win64 would be fine too. There is another x64 calling convention and that's the one used by non MS systems: http://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions – David Heffernan Jun 13 '12 at 09:47
  • @Stefan: but that is not the same "fastcall" as in C++Builder. Note that even in Win32, there are several ways of fastcall, e.g. the one defined by MS, the one defined by MS for method calls and the one defined by Borland. Unlike stdcall and cdecl, fastcall is not one single convention. In C++Builder, the compiler recognizes __fastcall (for the C++Builder and Delphi register convention) and __msfastcall for the MSVC++ variety, which uses different registers. In Win64, there is only one calling convention, and that uses registers you won't even find in Win32. – Rudy Velthuis Jun 13 '12 at 09:49
  • If you follow the link in Allens post it says that it uses the __fastcall model which is why I said fastcall. I agree that the documentation misses the fact that the register calling convention passes the first 3 parameters right-to-left and after the ones pushed on the stack. – Stefan Glienke Jun 13 '12 at 09:51
  • FWIW, apparently someone from the documentation team read this and already updated the docwiki documentation. It is almost correct now. It should, IMO, mention that in Win64 there is only one calling convention for everything (well, almost -- there are a few exceptions). – Rudy Velthuis Jun 16 '12 at 13:22