The code below is from DDetours.pas. When it compiles for 32 bit, there are no warnings emitted. When it compiles for 64-bit, it emits this warning: (Delphi Berlin Update 2)
[dcc64 Hint] DDetours.pas(1019): H2077 Value assigned to 'Prf' never used
Here is the function in question
function GetPrefixesCount(Prefixes: WORD): Byte;
var
Prf: WORD;
i: Byte;
begin
{ Get prefixes count used by the instruction. }
Result := 0;
if Prefixes = 0 then
Exit;
Prf := 0;
i := 0;
Prefixes := Prefixes and not Prf_VEX;
while Prf < $8000 do
begin
Prf := (1 shl i);
if (Prf and Prefixes = Prf) then
Inc(Result);
Inc(i);
end;
end;
It sure looks to me like the very first time Prf is compared against $8000 that initial value is used.
It's a compiler bug. There are a few of this nature. Quite frustrating. Sometimes the 32 bit compiler will complain in an irrational manner and then when you workaround that the 64 bit compiler in turn complains in an irrational manner about your workaround.
I don't think that Embarcadero habitually compiler with hints and warnings enabled, because their library code is full of hints and warnings.
Anyway in this case the compiler sees the two writes to the variable but for some reason does not recognise the intervening read of the variable.
There's not a whole lot that you can do. You could submit a bug report. I expect that you don't want to change the code because it is third party code. If you don't change it then you'll have to put up with the bogus hint.
Notifying the author of the library might allow them to workaround the issue. Perhaps by suppressing hints for that function.
I suspect the 64bit compiler has a small amount of smarts built-in to allow it to recognize that
the variable is initialized and not touched again until the loop is entered.
the loop condition is comparing the variable to a literal.
the initial value of 0 satisfies the loop condition, thus ensuring the loop will always run at least once at runtime, effectively making it act like a repeat..until loop.
Since the compiler still has to generate code for the initialization, but knows the initial value is not needed at runtime to enter the loop, it can issue a warning that the initial value will be unused.
If you don't initialize the variable, the compiler doesn't know at compile-time whether the loop will be entered or not since the behavior is undefined, so the compiler issues a different warning about the variable being uninitialized.
Viewing the disassembly, you can see that the loop is turned into a repeat..until loop and the Prf := 0; assignment is removed by optimization:
Project87.dpr.17: Result := 0;
00000000004261AA 4833D2 xor rdx,rdx
Project87.dpr.18: if Prefixes = 0 then
00000000004261AD 6685C0 test ax,ax
00000000004261B0 7460 jz GetPrefixesCount + $72
Project87.dpr.22: i := 0;
00000000004261B2 4D33C0 xor r8,r8
Project87.dpr.23: Prefixes := Prefixes and not Prf_VEX;
00000000004261B5 0FB7C0 movzx eax,ax
00000000004261B8 81E02DFBFFFF and eax,$fffffb2d
00000000004261BE 81F8FFFF0000 cmp eax,$0000ffff
00000000004261C4 7605 jbe GetPrefixesCount + $2B
00000000004261C6 E8050FFEFF call #BoundErr
Project87.dpr.26: Prf := (1 shl i);
00000000004261CB 41C7C101000000 mov r9d,$00000001
00000000004261D2 418BC8 mov ecx,r8d
00000000004261D5 41D3E1 shl r9d,r9b
00000000004261D8 4489C9 mov ecx,r9d
00000000004261DB 81F9FFFF0000 cmp ecx,$0000ffff
00000000004261E1 7605 jbe GetPrefixesCount + $48
00000000004261E3 E8E80EFEFF call #BoundErr
Project87.dpr.27: if (Prf and Prefixes = Prf) then
00000000004261E8 448BC9 mov r9d,ecx
00000000004261EB 664423C8 and r9w,ax
00000000004261EF 66443BC9 cmp r9w,cx
00000000004261F3 750A jnz GetPrefixesCount + $5F
Project87.dpr.28: Inc(Result);
00000000004261F5 80C201 add dl,$01
00000000004261F8 7305 jnb GetPrefixesCount + $5F
00000000004261FA E8F10EFEFF call #IntOver
Project87.dpr.29: Inc(i);
00000000004261FF 4180C001 add r8b,$01
0000000000426203 7305 jnb GetPrefixesCount + $6A
0000000000426205 E8E60EFEFF call #IntOver
Project87.dpr.24: while Prf < $8000 do
000000000042620A 6681F90080 cmp cx,$8000
000000000042620F 72BA jb GetPrefixesCount + $2B
well, I guess the answer is that dcc64 is a buggy compiler when it comes to messages. Because if you comment out the offending line, "value never used" becomes "might not have been initialized." Same compiler.
[dcc64 Warning] DDetours.pas(1022): W1036 Variable 'Prf' might not have been initialized
Related
The intuitive answer would be that the loop is never entered. And this seems to be case in all tests I could come up with. I'm still anxious and always test it before entering the loop.
Is this necessary?
No, it is not necessary.
The documentation clearly states :
for counter := initialValue to finalValue do statement
or:
for counter := initialValue downto finalValue do statement
...
If initialValue is equal to finalValue, statement is executed exactly once. If initialValue is greater than finalValue in a for...to statement, or less than finalValue in a for...downto statement, then statement is never executed.
There is no need for anxiety.
If we want to examine further what happens, let's make a few examples. Consider first :
program Project1;
{$APPTYPE CONSOLE}
var
i : integer;
begin
for i := 2 to 1 do WriteLn(i);
end.
This produces a compiler hint:
[dcc32 Hint] Project1.dpr(6): H2135 FOR or WHILE loop executes zero times - deleted
So the compiler will simply throw away a loop with constants that produce no loop iterations. It does this even with optimizations turned off - no code is produced for the loop at all.
Now let's be a bit more clever :
program Project1;
{$APPTYPE CONSOLE}
var
i, j, k : integer;
begin
j := 2;
k := 1;
for i := j to k do WriteLn(i);
end.
This actually compiles the loop. The output is as below:
Project1.dpr.8: for i := j to k do WriteLn(i);
004060E8 A1A4AB4000 mov eax,[$0040aba4] {$0040aba4 -> j = 2}
004060ED 8B15A8AB4000 mov edx,[$0040aba8] {$0040aba8 -> k = 1}
004060F3 2BD0 sub edx,eax {edx = k - j = -1}
004060F5 7C2E jl $00406125 {was k-j < 0? if yes, jmp to end.}
004060F7 42 inc edx {set up loop}
004060F8 8955EC mov [ebp-$14],edx
004060FB A3A0AB4000 mov [$0040aba0],eax
00406100 A118784000 mov eax,[$00407818] {actual looped section}
00406105 8B15A0AB4000 mov edx,[$0040aba0]
0040610B E8E8D6FFFF call #Write0Long
00406110 E8C3D9FFFF call #WriteLn
00406115 E8EECCFFFF call #_IOTest
0040611A FF05A0AB4000 inc dword ptr [$0040aba0] {update loop var}
00406120 FF4DEC dec dword ptr [ebp-$14]
00406123 75DB jnz $00406100 {loop ^ if not complete}
Project1.dpr.9: end.
00406125 E88EE1FFFF call #Halt0
So, the very first thing a loop does is to check whether it needs to execute at all. If the initial is greater than the final (for a for..to loop) then it skips straight past it entirely. It doesn't even waste the cycles to initialize the loop counter.
There are some edge-cases in which you may be surprised to discover that the code does unexpectedly enter the loop. And still other cases where you may be tempted to pre-check whether to call the loop. But before I get into those details, I want to try impress on you the importance of not pre-checking your loop with an if condition.
Every line of code, no matter how easy to understand draws attention. It's more to read and more to confirm is correct. So if it's not important, or if it's technically redundant: it's best left out.
A for loop is conceptually translated as follows:
Initialise loop index to starting value.
If iteration constraint is valid (e.g. Index <= EndValue in case of forward loop):
Perform iteration (code within loop block/statement)
Perform loop control operations (increment loop index)
Repeat 2
Otherwise continue at first instruction after loop.
The way in which Step 2 is checked, makes an extra if condition before the loop completely redundant.
So if you (or another developer) is later maintaining code with a redundant if condition, they're left to wonder:
Is the line correct?
It seems redundant; is there a special condition it's trying to handle?
If it currently serves no purpose, perhaps it was intended to prevent calling the loop on a different condition?
In simple case, redundant lines of code can create some confusion. In more complex cases, they can result in whole new sections of irrelevant code being developed; that tries to cater for irrelevant scenarios implied by legacy redundant code.
Recommendation: Stamp out redundant code as much as possible. Including redundant pre-checks for "should the loop execute at all".
The most important benefit of stamping out redundant code is that: it correctly draws attention to peculiar cases whenever special handling actually is required.
There are 2 potential pitfalls, and the first is the more dangerous one as it deals with implicit type conversion. So it may not always be easy to detect. The following code was tested on rextester using fpc, but I have verified the same issue on Delphi 2007 / 2009 in the past.
//fpc 2.6.2
program UnexpectedForIteration;
{$MODE DELPHI}
{ Ensure range-checking is off. If it's on, a run-time error
prevents potentially bad side-effects of invalid iterations.}
{$R-,H+,W+}
var
IntStart, IntEnd, IntIndex: Integer;
UIntStart, UIntEnd, UIntIndex: Cardinal;
IterCount: Integer;
begin
Writeln('Case 1');
IntStart := High(Integer) - 1;
IntEnd := -IntStart;
UIntStart := Cardinal(IntStart);
UIntEnd := Cardinal(IntEnd);
{This gives a clue why the problem occurs.}
Writeln('From: ', IntStart, ' To: ', IntEnd);
Writeln('From: ', UIntStart, ' To: ', UIntEnd, ' (unsigned)');
Writeln('Loop 1');
IterCount := 0;
for IntIndex := IntStart to IntEnd do Inc(IterCount);
Writeln(IterCount);
Writeln('Loop 2');
IterCount := 0;
{ The loop index variable is a different type to the initial &
final values. So implicit conversion takes place and:
IntEnd **as** unsigned is unexpectedly bigger than IntStart }
for UIntIndex := IntStart to IntEnd do Inc(IterCount);
Writeln(IterCount, ' {Houston we have a problem}');
Writeln();
Writeln('Case 2');
UIntStart := High(Cardinal) - 2;
UIntEnd := 2;
IntStart := Integer(UIntStart);
IntEnd := Integer(UIntEnd);
{This gives a clue why the problem occurs.}
Writeln('From: ', UIntStart, ' To: ', UIntEnd);
Writeln('From: ', IntStart, ' To: ', IntEnd, ' (signed)');
Writeln('Loop 3');
IterCount := 0;
for UIntIndex := UIntStart to UIntEnd do Inc(IterCount);
Writeln(IterCount);
Writeln('Loop 4');
IterCount := 0;
{ The loop index variable is a different type to the initial &
final values. So implicit conversion takes place and:
UIntStart **as** signed is unexpectedly less than UIntEnd }
for IntIndex := UIntStart to UIntEnd do Inc(IterCount);
Writeln(IterCount, ' {Houston we have a problem}');
end.
The output is as follows:
Case 1
From: 2147483646 To: -2147483646
From: 2147483646 To: 2147483650 (unsigned)
Loop 1
0
Loop 2
5 {Houston we have a problem}
Case 2
From: 4294967293 To: 2
From: -3 To: 2 (signed)
Loop 3
0
Loop 4
6 {Houston we have a problem}
In many cases the problem is resolved by ensuring the same types are used for loopIndex, initialValue and finalValue. As this means there won't be an implicit type conversion, and the loop will reliably iterate as the initialValue and finalValue would suggest.
It would be easier if the compiler emits appropriate warnings for implicit type conversion in for loops. Unfortunately fpc didn't; I don't recall whether Delphi 2007/2009 does; and have no idea whether any recent versions do.
However, the preferred approach would be to favour container iteration syntax (pushing responsibility for 'correct' iteration on the enumerators). E.g.: for <element> in <container> do ...;. This should not iterate empty containers provided the enumerator's methods are implemented correctly.
The only time I'd say a pre-check is worth considering is:
when for in is not feasible for some reason
and the loop index needs to be zero-based
and support large unsigned integers (High(Integer) < index < High(Cardinal))
because this leaves no space for a reliable sentinel less than all possible initial values.
Even in this case, consider using an Int64 loop index instead of if (initialValue <= finalValue) then for ....
The second pitfall involves what I would in any case consider to be a design flaw. So the problem can be avoided entirely by rather being aware of this design consideration. It is demonstrated in code that looks as follows:
if Assigned(AnObject) then
for LIndex := 0 to AnObject.Count - 1 do ...;
In this case the if condition may in fact be necessary as a result of dubious design. Certainly, if AnObject hasn't been created, you do not want to access its Count property/method. But the dubious aspect of the design is the fact that you're uncertain whether AnObject exists. Yes, you may have employed a lazy-initialisation pattern. But it doesn't change the fact that in the above code, there's no way to differentiate between: "zero iterations" because AnObject doesn't exist or because AnObject.Count = 0.
I'd like to point out that when code has many redundant if Assigned(AnObject) then (or similar) lines, it leads to one of the problems I described in section 1. Local code caters for 2 possibilities. And by extension, client code also caters for 2 possibilities. And by induction, this problem eventually leaks throughout the code-base.
The solution is to first and foremost limit the cases where existence of AnObject is uncertain.
It's much easier to ensure an empty object with Count = 0 is guaranteed to be created (typically only affecting a small number of places in code).
It's far more work to deal with the ripple effects of a large number of places where the object might not exist yet; yielding 2 possible states and code paths.
If lazy-initialisation is required, try to ensure the code surface where existence is optional is kept as small as possible.
I am trying to convert the Delphi TBits.GetBit to inline assembler for the 64 bit version. The VCL source looks like this:
function TBits.GetBit(Index: Integer): Boolean;
{$IFNDEF X86ASM}
var
LRelInt: PInteger;
LMask: Integer;
begin
if (Index >= FSize) or (Index < 0) then
Error;
{ Calculate the address of the related integer }
LRelInt := FBits;
Inc(LRelInt, Index div BitsPerInt);
{ Generate the mask }
LMask := (1 shl (Index mod BitsPerInt));
Result := (LRelInt^ and LMask) <> 0;
end;
{$ELSE X86ASM}
asm
CMP Index,[EAX].FSize
JAE TBits.Error
MOV EAX,[EAX].FBits
BT [EAX],Index
SBB EAX,EAX
AND EAX,1
end;
{$ENDIF X86ASM}
I started converting the 32 bit ASM code to 64 bit. After some searching, I found out that I need to change the EAX references to RAX for the 64 bit compiler. I ended up with this for the first line:
CMP Index,[RAX].FSize
This compiles but gives an access violation when it runs. I tried a few combinations (e.g. MOV ECX,[RAX].FSize) and get the same access violation when trying to access [RAX].FSize. When I look at the assembler that is generated by the Delphi compiler, it looks like my [RAX].FSize should be correct.
Unit72.pas.143: MOV ECX,[RAX].FSize
00000000006963C0 8B8868060000 mov ecx,[rax+$00000668]
And the Delphi generated code:
Unit72.pas.131: if (Index >= FSize) or (Index < 0) then
00000000006963CF 488B4550 mov rax,[rbp+$50]
00000000006963D3 8B4D58 mov ecx,[rbp+$58]
00000000006963D6 3B8868060000 cmp ecx,[rax+$00000668]
00000000006963DC 7D06 jnl TForm72.GetBit + $24
00000000006963DE 837D5800 cmp dword ptr [rbp+$58],$00
00000000006963E2 7D09 jnl TForm72.GetBit + $2D
In both cases, the resulting assembler uses [rax+$00000668] for FSize. What is the correct way to access a class field in Delphi 64bit Assembler?
This may sound like a strange thing to optimize but the assembler for the 64bit pascal version doesn't appear to be very efficient. We call this routine a large number of times and it takes anything up to 5 times as long to execute depending on various factors.
The basic problem is that you are using the wrong register. Self is passed as an implicit parameter, before all others. In the x64 calling convention, that means it is passed in RCX and not RAX.
So Self is passed in RCX and Index is passed in RDX. Frankly, I think it's a mistake to use parameter names in inline assembler because they hide the fact that the parameter was passed in a register. If you happen to overwrite either RDX, then that changes the apparent value of Index.
So the if statement might be coded as
CMP EDX,[RCX].FSize
JNL TBits.Error
CMP EDX,0
JL TBits.Error
FWIW, this is a really simple function to implement and I don't believe that you will need to use any stack space. You have enough registers in x64 to be able to do this entirely using volatile registers.
I have following problem.
When I try to run the code with arun file.obj (I have compiled with algol.exe file)
BEGIN
INTEGER PROCEDURE fun(tab,index,lower,upper);
INTEGER tab,index,lower,upper;
BEGIN
INTEGER t;
text (1, "Start");
t := 0;
FOR index := lower STEP 1 UNTIL upper DO
t := t + tab;
fun := t;
END;
INTEGER ARRAY t[1:10];
INTEGER i,result,lower,upper;
lower := 1;
upper := 10;
FOR i := 1 STEP 1 UNTIL 10 DO
t[i] := i;
i := 1;
result := fun(t[i],i,lower,upper);
END FINISH;
I am still getting error:
ERROR 3
ADD PBASE PROC LOC
07D4 0886 1 13
083A 0842 0 115
The compiler I use is "The Rogalgol Algol60" product of RHA (Minisystems) Ltd.
Error 3 means "3 Procedure called where the actual and the formal parameter types do not match."
But I do not understand why. The reason of error is t[i] (If I change to i - it is ok).
Someone knows what I am doing wrongly?
I compile the code on the dosbox (linux)
Problem is that the index of the integer array that you're passing to your procedure isn't the same as the integer that he's expecting. I can't remember what an integer array is full of, but I guess it isn't integers... Have to admit I never use them, but can't remember why. Possibly because of limitations like this. I stick to Real arrays and EBCDIC ones.
You can almost certainly fix it by defining a new integer, j; inserting "j := t[i];" before your invocation of 'fun'; then invoking 'fun' with 'j' rather than t[i].
BTW you may want to make the array (and the 'for' loop) zero-relative. ALGOL is mostly zero-relative and I think it may save memory if you go with the flow.
Let me know if this helps....
I'm working on a simple PIC18 MCPU mnemonic simulation in Delphi pascal. And yes, I intend to use Delphi IDE.
I'm able to simulate any asm instruction, but it stops at labels.
In some cases I need to know the address of Delphi label.
Is there any possibility to cast label in to pointer variable?
As in my example?
procedure addlw(const n:byte); //emulation of mcpu addlw instruction
begin
Carry := (wreg + n) >= 256;
wreg := wreg + n;
Zero := wreg = 0;
inc(CpuCycles);
end;
procedure bnc(p: pointer ); //emulation of mcpu bnc instruction
asm
inc CpuCycles
cmp byte ptr Carry, 0
jnz #exit
pop eax //restore return addres from stack
jmp p
#exit:
end;
//EMULATION OF MCPU ASM CODE
procedure Test;
label
Top;
var
p: pointer;
begin
//
Top:
addlw(5); //emulated mcpu addlw instruction
bnc(Top); //emulated mcpu bnc branch if not carry instruction
//
end;
No, you can't interact with labels that way. Since you're emulating everything else, you may as well emulate assembler labels, too, instead of trying to force Delphi labels to do something they're not designed for.
Suppose you could use code like this instead of the "assembler" code you wrote (without worrying for now exactly how to implement it):
procedure Test;
var
Top: TAsmLabel;
begin
//
DefineLabel(Top);
addlw(5); //emulated mcpu addlw instruction
bnc(Top); //emulated mcpu bnc branch if not carry instruction
//
end;
The syntax looks similar enough, I think. Upon running that code, you'll want Top to refer to the next instruction, which is the one that calls addlw.
Inside the hypothetical function DefineLabel, that address corresponds to the return address, so write DefineLabel to store its return address in the given parameter:
type
TAsmLabel = Pointer;
procedure DefineLabel(out Result: TAsmLabel);
asm
mov ecx, [esp] // copy return address
mov [eax], ecx // store result
end;
Beware that this code corrupts the stack. Your bcn function leaves its return address on the stack, so when the carry flag eventually gets set, you've left a trail of previous return addresses on the stack. If you don't get a stack overflow first, you'll hit strange results when you get to the end of the containing function. It will try to return, but instead of going to the caller, it will find bnc's return address instead, and jump back into the middle of your code. And that's all assuming there aren't any other stack-relative references in the code. If there are, then even calling bnc(Top) might give problems because the relative position of Top will have changed, and you'll end up reading the wrong value off the stack.
The difference between Chr and Char when used in converting types is that one is a function and the other is cast
So: Char(66) = Chr(66)
I don't think there is any performance difference (at least I've never noticed any, one probably calls the other).... I'm fairly sure someone will correct me on this!
EDIT Thanks to Ulrich for the test proving they are in fact identical.
EDIT 2 Can anyone think of a case where they might not be identical, e.g. you are pushed towards using one over the other due to the context?
Which do you use in your code and why?
I did a small test in D2007:
program CharChr;
{$APPTYPE CONSOLE}
uses
Windows;
function GetSomeByte: Byte;
begin
Result := Random(26) + 65;
end;
procedure DoTests;
var
b: Byte;
c: Char;
begin
b := GetSomeByte;
IsCharAlpha(Chr(b));
b := GetSomeByte;
IsCharAlpha(Char(b));
b := GetSomeByte;
c := Chr(b);
b := GetSomeByte;
c := Char(b);
end;
begin
Randomize;
DoTests;
end.
Both calls produce the same assembly code:
CharChr.dpr.19: IsCharAlpha(Chr(b));
00403AE0 8A45FF mov al,[ebp-$01]
00403AE3 50 push eax
00403AE4 E86FFFFFFF call IsCharAlpha
CharChr.dpr.21: IsCharAlpha(Char(b));
00403AF1 8A45FF mov al,[ebp-$01]
00403AF4 50 push eax
00403AF5 E85EFFFFFF call IsCharAlpha
CharChr.dpr.24: c := Chr(b);
00403B02 8A45FF mov al,[ebp-$01]
00403B05 8845FE mov [ebp-$02],al
CharChr.dpr.26: c := Char(b);
00403B10 8A45FF mov al,[ebp-$01]
00403B13 8845FE mov [ebp-$02],al
Edit: Modified sample to mitigate Nick's concerns.
Edit 2: Nick's wish is my command. ;-)
The help says: Chr returns the character with the ordinal value (ASCII value) of the byte-type expression, X. *
So, how is a character represented in a computer's memory? Guess what, as a byte*. Actually the Chr and Ord functions are only there for Pascal being a strictly typed language prohibiting the use of bytes* where characters are requested. For the computer the resulting char is still represented as byte* - to what shall it convert then? Actually there is no code emitted for this function call, just as there is no code omitted for a type cast. Ergo: no difference.
You may prefer chr just to avoid a type cast.
Note: type casts shall not be confused with explicit type conversions! In Delphi 2010 writing something like Char(a) while a is an AnsiChar, will actually do something.
**For Unicode please replace byte with integer*
Edit:
Just an example to make it clear (assuming non-Unicode):
var
a: Byte;
c: char;
b: Byte;
begin
a := 60;
c := Chr(60);
c := Chr(a);
b := a;
end;
produces similar code
ftest.pas.46: a := 60;
0045836D C645FB3C mov byte ptr [ebp-$05],$3c
ftest.pas.47: c := Chr(60);
00458371 C645FA3C mov byte ptr [ebp-$06],$3c
ftest.pas.48: c := Chr(a);
00458375 8A45FB mov al,[ebp-$05]
00458378 8845FA mov [ebp-$06],al
ftest.pas.49: b := a;
0045837B 8A45FB mov al,[ebp-$05]
0045837E 8845F9 mov [ebp-$07],al
Assigning byte to byte is actually the same as assigning byte to char via CHR().
chr is a function, thus it returns a new value of type char.
char(x) is a cast, that means the actual x object is used but as a different type.
Many system functions, like inc, dec, chr, ord, are inlined.
Both char and chr are fast. Use the one that is most appropriate each time,
and reflects better what you want to do.
Chr is function call, it is a bit (tiny-tiny) more expensive then type cast. But i think Chr is inlined by compiler.
They are identical, but they don't have to be identical. There's no requirement that the internal representation of characters map 1-to-1 with their ordinal values. Nothing says that a Char variable holding the value 'A' must hold the numeric value 65. The requirement is that when you call Ord on that variable, the result must be 65 because that's the code point designated for the letter A in your program's character encoding.
Of course, the easiest implementation of that requirement is for the variable to hold the numeric value 65 as well. Because of this, the function calls and the type-casts are always identical.
If the implementation were different, then when you called Chr(65), the compiler would go look up what character is at code point 65 and use it as the result. When you write Char(65), the compiler wouldn't worry about what character it really represents, as long as the numeric result stored in memory was 65.
Is this splitting hairs? Yes, absolutely, because in all current implementations, they're identical. I liken this to the issue of whether the null pointer is necessarily zero. It's not, but under all implementations, it ends up that way anyway.
chr is typesafe, char isn't: Try to code chr(256) and you'll get a compiler error. Try to code char(256) and you will either get the character with the ordinal value 0 or 1, depending on your computers internal representation of integers.
I'll suffix the above by saying that that applies to pre-unicode Delphi. I don't know if chr and char have been updated to take unicode into account.