Friday, February 23, 2007

Nasty inline codegen bug in BDS 2006

I'm using assign-test-and-do-something code pattern quite frequently. You know, code like this:

Result := CalculateSomething(some, param); // assign
if not Result then // test
Exit; // do something

As all real programmers, I'm lazy and tend to rewrite such code as:

if Asgn(Result, CalculateSomething(some, param)) then
Exit;

Most of the dirty work is done by the Asgn function, which acts as a splitter - it copies one value to two destinations - output parameter and function result.

function Asgn(var output: integer; const value: integer): integer;
begin
output := value;
Result := value;
end;

(Actually, I have four overloaded Asgn functions, but that's beside the point.)


Yesterday, I did two things almost at the same time (which was, in hindsight, very lucky - it helped me to find the problem in "mere" 30 minutes). Firstly, I changed all Asgn functions to be inlined on modern Delphis and secondly, I used same variable as part of the value calculation and as the output parameter. In other words, I did this: 

function Asgn(var output: integer; const value: integer): integer; inline;
begin
output := value;
Result := value;
end;

copy := Asgn(v1, v1+v2);

(It was slightly more complicated than that but the code above nicely summarizes the problem.)


This code should calculate a sum of two variables and assign it to both v1 and copy. It should, I said, but it didn't. Correct value was assigned to v1, but something completely different was assigned to the copy variable. When I removed the inline, Delphi immediately started to generate correct code.


So, for now, I removed inline from all my Asgn functions. I also know that I'll be extremely causious when using inline in the future.


CodeGear has been notified of the problem (QC #41166) and I hope they'll be able to fix the codegen before Delphi 2007 is out.


If you don't believe me, try it for yourself. Demo is below. It should output four numbers 17, but it doesn't.

program Project8;

{$APPTYPE CONSOLE}

uses
SysUtils;

function Asgn1(var output: integer; const value: integer): integer;
begin
output := value;
Result := value;
end;

function Asgn2(var output: integer; const value: integer): integer; inline;
begin
output := value;
Result := value;
end;

var
v1, v2: integer;

begin
v1 := 10;
v2 := 7;
Writeln(Asgn1(v1, v1+v2));
Writeln(v1);
v1 := 10;
v2 := 7;
Writeln(Asgn2(v1, v1+v2));
Writeln(v1);
Readln;
end.

7 comments:

  1. I've tried your example and it seems that the compiler is translating the inlined function to something like this:

    v1 := v1 + v2;
    Result := v1 + v2;

    this is why v2 is doubled, but it seems to correct if you change the function to this:

    output := value;
    Result := Output;

    In any case, it's a nasty inline codegen bug.

    Hope this helps...

    ReplyDelete
  2. Thanks for the analysis (and for the workaround).

    ReplyDelete
  3. Dear reader, don't forget to vote if you want this problem to be fixed soon.

    ReplyDelete
  4. This bug has been fixed in Delphi 2007.

    ReplyDelete
  5. Anonymous00:18

    This discovery was very important to me, because i think having the opportunity of inlining some routines is extremely useful for optimization.
    (those low level pixel writing operations that get called a million times, for example)

    Delphi is a high level language, but as in any professional compiler it should go down to machine level if necessary. Giving as much options as possible to avoid things like call/ret pairs unnecessary push/pops or stack frame preparation.

    Thanks a lot

    Patrick
    pvb256(at)hotmail.com

    ps: I you like to see a REAL awesome (i wish delphi did it this way) use of inline, check openwatcom C compiler.

    It goes beyond inlining, because
    -first: it allows ASSEMBLER inline
    -second (and one of the best features) on inline functions it allows you to inform in the function header, what registers got changed/corrupted so you don´t have to restore them, by having this information, the compiler DEALS with it and do push pops only if necessary. (i´m a lazy programmer too, it´s obligation of the compiler to know which registers need to be restored if you inform them).
    It results in an extremely fast inlined machine code, which helped me once by me to do a brute force password cracking program.

    ReplyDelete
  6. Anonymous15:59

    In my opinion, this is not a bug. I've been programming in Delphi for more than 4 yrs.

    If you compile your func. with 'inline' directive, the compiler will insert the whole code of the Asgn function into the asm code of your executable to every point where a call to the func. would originally have taken place.
    So if you use inline compiler will insert the whole code - not just a CALL method to the function -, and it will change the behaviour of your software.

    Try your code in a different way: remove the 'var' expr. from the declarations of Asgn and use a global integer-typed variable (which is declared in the interface section, otherwise inline won't work (read delphi help)).

    I wrote the same app and - as I know how assembly exactly looks like - I managed to understand that

    it's not a bug in the compiler:
    it's a 'bug' in our mentality

    ;)

    ReplyDelete
  7. It was considered a bug by the CodeGear crew and is fixed in D2007.

    ReplyDelete