Invalid floating point operation calling Trunc() - delphi

I'm getting a (repeatable) floating point exception when i try to Trunc() a Real value.
e.g.:
Trunc(1470724508.0318);
In reality the actual code is more complex:
ns: Real;
v: Int64;
ns := ((HighPerformanceTickCount*1.0)/g_HighResolutionTimerFrequency) * 1000000000;
v := Trunc(ns);
But in the end it still boils down to:
Trunc(ARealValue);
Now, i cannot repeat it anywhere else - just at this one spot. Where it fails every time.
It's not voodoo
Fortunately computers are not magic. The Intel CPU performs very specific observable actions. So i should be able to figure out why the floating point operation fails.
Going into the CPU window
v := Trunc(ns)
fld qword ptr [ebp-$10]
This loads the 8-byte floating point value at ebp-$10 into floating point register ST0.
The bytes at memory address [ebp-$10] are:
0018E9D0: 6702098C 41D5EA5E (as DWords)
0018E9D0: 41D5EA5E6702098C (as QWords)
0018E9D0: 1470724508.0318 (as Doubles)
The call succeeds, and the floating point register the contains the appropriate value:
Next is the actual call to the RTL Trunc function:
call #TRUNC
Next is the guts of Delphi RTL's Trunc function:
#TRUNC:
sub esp,$0c
wait
fstcw word ptr [esp] //Store Floating-Point Control Word on the stack
wait
fldcw word ptr [cwChop] //Load Floating-Point Control Word
fistp qword ptr [esp+$04] //Converts value in ST0 to signed integer
//stores the result in the destination operand
//and pops the stack (increments the stack pointer)
wait
fldcw word ptr [esp] //Load Floating-Point Control Word
pop ecx
pop eax
pop edx
ret
Or i suppose i could have just pasted it from the rtl, rather than transcribing it from the CPU window:
const cwChop : Word = $1F32;
procedure _TRUNC;
asm
{ -> FST(0) Extended argument }
{ <- EDX:EAX Result }
SUB ESP,12
FSTCW [ESP] //Store foating-control word in ESP
FWAIT
FLDCW cwChop //Load new control word $1F32
FISTP qword ptr [ESP+4] //Convert ST0 to int, store in ESP+4, and pop the stack
FWAIT
FLDCW [ESP] //restore the FPCW
POP ECX
POP EAX
POP EDX
end;
The exception happens during the actual fistp operation.
fistp qword ptr [esp+$04]
At the moment of this call, the ST0 register will contains the same floating point value:
Note: The careful observer will note the value in the above screenshot doesn't match the first screenshot. That's because i took it on a different run. I'd rather not have to carefully redo all the constants in the question just to make them consistent - but trust me: it's the same when i reach the fistp instruction as it was after the fld instruction.
Leading up to it:
sub esp,$0c: I watch it push the the stack down by 12 bytes
fstcw word ptr [esp]: i watch it push $027F into the the current stack pointer
fldcw word ptr [cwChop]: i watch the floating point control flags change
fistp qword ptr [esp+$04]: and it's about to write the Int64 into the room it made on the stack
and then it crashes.
What can actually be going on here?
It happens with other values as well, it's not like there's something wrong with this particular floating point value. But i even tried to setup the test-case elsewhere.
Knowing that the 8-byte hex value of the float is: $41D5EA5E6702098C, i tried to contrive the setup:
var
ns: Real;
nsOverlay: Int64 absolute ns;
v: Int64;
begin
nsOverlay := $41d62866a2f270dc;
v := Trunc(ns);
end;
Which gives:
nsOverlay := $41d62866a2f270dc;
mov [ebp-$08],$a2f270dc
mov [ebp-$04],$41d62866
v := Trunc(ns)
fld qword ptr [ebp-$08]
call #TRUNC
And at the point of the call to #trunc, the floating point register ST0 contains a value:
But the call does not fail. It only fails, every time in this one section of my code.
What could be possibly happening that is causing the CPU to throw an invalid floating point exception?
What is the value of cwChop before it loads the control word?
The value of cwChop looks to be correct before the load control word, $1F32. But after the load, the actual control word is wrong:
Bonus Chatter
The actual function that is failing is something to convert high-performance tick counts into nanoseconds:
function PerformanceTicksToNs(const HighPerformanceTickCount: Int64): Int64;
//Convert high-performance ticks into nanoseconds
var
ns: Real;
v: Int64;
begin
Result := 0;
if HighPerformanceTickCount = 0 then
Exit;
if g_HighResolutionTimerFrequency = 0 then
Exit;
ns := ((HighPerformanceTickCount*1.0)/g_HighResolutionTimerFrequency) * 1000000000;
v := Trunc(ns);
Result := v;
end;
I created all the intermeidate temporary variables to try to track down where the failure is.
I even tried to use that as a template to try to reproduce it:
var
i1, i2: Int64;
ns: Real;
v: Int64;
vOver: Int64 absolute ns;
begin
i1 := 5060170;
i2 := 3429541;
ns := ((i1*1.0)/i2) * 1000000000;
//vOver := $41d62866a2f270dc;
v := Trunc(ns);
But it works fine. There's something about when it's called during a DUnit unit test.
Floating Point control word flags
Delphi's standard control word: $1332:
$1332 = 0001 00 11 00 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
0 ;reserved exception mask
0 ;reserved
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
00 ;Rounding control -
0 ;Infinity control - 0 (not used)
The Windows API required value: $027F
$027F = 0000 00 10 01 111111
1 ;Allow invalid numbers
1 ;Allow denormals (very small numbers)
1 ;Allow divide by zero
1 ;Allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
1 ;reserved exception mask
0 ;reserved
10 ;Precision Control - 10B (double precision)
00 ;Rounding control
0 ;Infinity control - 0 (not used)
The crChop control word: $1F32
$1F32 = 0001 11 11 00 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
0 ;reserved exception mask
0 ;unused
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
11 ;Rounding Control
1 ;Infinity control - 1 (not used)
000 ;unused
The CTRL flags after loading $1F32: $1F72
$1F72 = 0001 11 11 01 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
1 ;reserved exception mask
0 ;unused
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
11 ;Rounding control
1 ;Infinity control - 1 (not used)
00011 ;unused
All the CPU is doing is turning on a reserved, unused, mask bit.
RaiseLastFloatingPointError()
If you're going to develop programs for Windows, you really need to accept the fact that floating point exceptions should be masked by the CPU, meaning you have to watch for them yourself. Like Win32Check or RaiseLastWin32Error, we'd like a RaiseLastFPError. The best i can come up with is:
procedure RaiseLastFPError();
var
statWord: Word;
const
ERROR_InvalidOperation = $01;
// ERROR_Denormalized = $02;
ERROR_ZeroDivide = $04;
ERROR_Overflow = $08;
// ERROR_Underflow = $10;
// ERROR_InexactResult = $20;
begin
{
Excellent reference of all the floating point instructions.
(Intel's architecture manuals have no organization whatsoever)
http://www.plantation-productions.com/Webster/www.artofasm.com/Linux/HTML/RealArithmetica2.html
Bits 0:5 are exception flags (Mask = $2F)
0: Invalid Operation
1: Denormalized - CPU handles correctly without a problem. Do not throw
2: Zero Divide
3: Overflow
4: Underflow - CPU handles as you'd expect. Do not throw.
5: Precision - Extraordinarily common. CPU does what you'd want. Do not throw
}
asm
fwait //Wait for pending operations
FSTSW statWord //Store floating point flags in AX.
//Waits for pending operations. (Use FNSTSW AX to not wait.)
fclex //clear all exception bits the stack fault bit,
//and the busy flag in the FPU status register
end;
if (statWord and $0D) <> 0 then
begin
//if (statWord and ERROR_InexactResult) <> 0 then raise EInexactResult.Create(SInexactResult)
//else if (statWord and ERROR_Underflow) <> 0 then raise EUnderflow.Create(SUnderflow)}
if (statWord and ERROR_Overflow) <> 0 then raise EOverflow.Create(SOverflow)
else if (statWord and ERROR_ZeroDivide) <> 0 then raise EZeroDivide.Create(SZeroDivide)
//else if (statWord and ERROR_Denormalized) <> 0 then raise EUnderflow.Create(SUnderflow)
else if (statWord and ERROR_InvalidOperation) <> 0 then raise EInvalidOp.Create(SInvalidOp);
end;
end;
A reproducible case!
I found a case, when Delphi's default floating point control word, that was the cause of an invalid floating point exception (although I never saw it before now because it was masked). Now that i'm seeing it, why is it happening! And it's reproducible:
procedure TForm1.Button1Click(Sender: TObject);
var
d: Real;
dover: Int64 absolute d;
begin
d := 1.35715152325557E020;
// dOver := $441d6db44ff62b68; //1.35715152325557E020
d := Round(d); //<--floating point exception
Self.Caption := FloatToStr(d);
end;
You can see that the ST0 register contains a valid floating point value. The floating point control word is $1372. There floating point exception flag are all clear:
And then, as soon as it executes, it's an invalid operation:
IE (Invalid operation) flag is set
ES (Exception) flag is set
I was tempted to ask this as another question, but it would be the exact same question - except this time calling Round().

The problem occurs elsewhere. When your code enters Trunc the control word is set to $027F which is, IIRC, the default Windows control word. This has all exceptions masked. That's a problem because Delphi's RTL expects exceptions to be unmasked.
And look at the FPU window, sure enough there are errors. Both IE and PE flags are set. It's IE that counts. That's means that earlier in the code sequence there was a masked invalid operation.
Then you call Trunc which modifies the control word to unmask the exceptions. Look at your second FPU window screenshot. IE is 1 but IM is 0. So boom, the earlier exception is raised and you are led to think that it was the fault of Trunc. It was not.
You'll need to trace back up the call stack to find out why the control word is not what it ought to be in a Delphi program. It ought to be $1332. Most likely you are calling into some third party library which modifies the control word and does not restore it. You'll have to locate the culprit and take charge whenever any calls to that function return.
Once you get the control word back under control you'll find the real cause of this exception. Clearly there is an illegal FP operation. Once the control word unmasks the exceptions, the error will be raised at the right point.
Note that there's nothing to worry about the discrepancy between $1372 and $1332, or $1F72 and $1F32. That's just an oddity with the CTRL control word that some of the bytes are reserved and ignore you exhortations to clear them.

Your latest update essentially asks a different question. It asks about the exception raised by this code:
procedure foo;
var
d: Real;
i: Int64;
begin
d := 1.35715152325557E020;
i := Round(d);
end;
This code fails because the job of Round() is to round d to the nearest Int64 value. But your value of d is greater than the largest possible value that can be stored in an Int64 and hence the floating point unit traps.

Related

Inconsistent Results with exAllArithmeticExceptions in Win32 and Win64

A colleague of mine picked up a discrepancy between Win32 and Win64 code compiled by Delphi in how it handles NaN's. Take the following code as an example. When compiled in 32 bit we get no messages but when compiled with 64 bit we get both comparisons returning true.
program TestNaNs;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Math;
var
nanDouble: Double;
zereDouble: Double;
nanSingle: Single;
zeroSingle: Single;
begin
SetExceptionMask(exAllArithmeticExceptions);
nanSingle := NaN;
zeroSingle := 0.0;
if nanSingle <> zeroSingle then
WriteLn('nanSingle <> zeroSingle');
nanDouble := NaN;
zereDouble := 0.0;
if nanDouble <> zereDouble then
WriteLn('nanDouble <> zeroDouble');
ReadLn;
end.
My understanding of the IEEE standard is that <> should return true but all other operations should return false. So in this case, it looks like the 64 bit version is correct and the 32 bit version is incorrect. The code generated by both is very different with the 64 bit version generating SSE code.
For 32 bit:
TestNaNs.dpr.21: if nanSingle <> zeroSingle then
0041A552 D905E01E4200 fld dword ptr [$00421ee0]
0041A558 D81DE41E4200 fcomp dword ptr [$00421ee4]
0041A55E 9B wait
0041A55F DFE0 fstsw ax
0041A561 9E sahf
0041A562 7419 jz $0041a57d
and for 64 bit:
TestNaNs.dpr.21: if nanSingle <> zeroSingle then
000000000042764E F3480F5A05C9ED0000 cvtss2sd xmm0,qword ptr [rel $0000edc9]
0000000000427657 F3480F5A0DC4ED0000 cvtss2sd xmm1,qword ptr [rel $0000edc4]
0000000000427660 660F2EC1 ucomisd xmm0,xmm1
0000000000427664 7A02 jp Project63 + $68
0000000000427666 7420 jz Project63 + $88
My question is this. Is this an issue with the Delphi compiler or a caveat with the Intel CPU's?
The IEEE 754 standard defines arithmetic formats, operations, rounding rules, exceptions etc. for floating point computation. The Delphi compiler implements floating point arithmetic on top of the available hardware units. For the 32 bit Windows compiler this is the x87 unit, and for the 64 bit Windows compiler this is the SSE unit. Both of these hardware units conform to the IEEE 754 standard.
The difference that you are observing arises at the language implementation level. Let us look at the two versions in more detail.
32 bit Windows compiler
The comparison statement is compiled to this:
TestNaNs.dpr.19: if nanDouble <> zeroDouble then
0041C4C8 DD05C03E4200 fld qword ptr [$00423ec0]
0041C4CE DC1DC83E4200 fcomp qword ptr [$00423ec8]
0041C4D4 9B wait
0041C4D5 DFE0 fstsw ax
0041C4D7 9E sahf
0041C4D8 7419 jz $0041c4f3
The Intel software developers manual says that an unordered comparison is indicated by the flags C3, C2 and C0 being set to 1. The full table is here:
Condition C3 C2 C0
ST(0) > Source 0 0 0
ST(0) < Source 0 0 1
ST(0) = Source 1 0 0
Unordered 1 1 1
When you inspect the FPU under the debugger, you can see that this us the case.
0041C4D5 DFE0 fstsw ax
0041C4D7 9E sahf
0041C4D8 7419 jz $0041c4f3
This transfers various bits from of the FPU status register into the CPU flags, see the manual for precise details of which flags go where. The the branch is made if ZF is set. The value of ZF comes from the C3 FPU flag, which, reading from the table above, is set for the unordered case.
In fact, the entire branching code can be expressed in pseudo code as:
jump if C3 = 1
So, looking at the table above, it is clear that if one of the operands is a NaN then any floating point equality comparison evaluates as equals.
64 bit Windows compiler
The comparison statement is compiled to this:
TestNaNs.dpr.19: if nanDouble <> zeroDouble then
0000000000428EB8 F20F100548E50000 movsd xmm0,qword ptr [rel $0000e548]
0000000000428EC0 660F2E0548E50000 ucomisd xmm0,qword ptr [rel $0000e548]
0000000000428EC8 7A02 jp TestNaNs + $5C
0000000000428ECA 7420 jz TestNaNs + $7C
The comparison is performed by the ucomisd instruction. The manual gives this psuedo code:
RESULT ← UnorderedCompare(SRC1[63:0] <> SRC2[63:0]) {
(* Set EFLAGS *)
CASE (RESULT) OF
GREATER_THAN: ZF, PF, CF ← 000;
LESS_THAN: ZF, PF, CF ← 001;
EQUAL: ZF, PF, CF ← 100;
UNORDERED: ZF, PF, CF ← 111;
ESAC;
OF, AF, SF ← 0;
Notice that in this instruction, the ZF, PF and CF flags are exactly analagous to the C3, C2 and C0 flags on the x87 unit.
The branching is handled by this code:
0000000000428EC8 7A02 jp TestNaNs + $5C
0000000000428ECA 7420 jz TestNaNs + $7C
Notice that there is first a test of the parity flag PF (the jp instruction), and then the zero flag ZF (the jz instruction). The compiler has therefore emitted code to handle the unordered case (i.e. one of the operands is NaN). This is handled first with the jp. Once that is handled, the compiler then checks the zero flag ZF which (because NaNs have been dealt with) is set if and only if the two operands are equal.
Conclusion
The different behaviour is down to the different compilers taking different choices in how to implement the comparison operators. In both situations the hardware is IEEE 754 compliant, and perfectly capable of comparing NaNs as specified by the standard.
My best guess would be that the decisions for the 32 bit compiler were taken a very long time ago. Some of these decisions are questionable. In my view an equality comparison with a NaN operand should evaluate not equals irrespective of the other operand. The weight of history, felt through a desire to maintain backwards compatibility, means that these questionable decisions have never been addressed.
When the 64 bit compiler was created, more recently, the Embarcadero engineers decided to right some of these mistakes. They presumably felt that the break to a new architecture allowed them the freedom to do so.
In an ideal world, the 32 bit compiler could be configured to behave the same way as the 64 bit compiler, by setting a compiler switch.

Why does dcc64 say this value is never used?

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

Delphi "for ... to" statement runs from end value to start value

I'm writing a simple app in Embarcadero Delphi 2010. A simple code with two cycles:
procedure TForm1.Button1Click(Sender: TObject);
var
a:array [0..255] of integer;
i:integer;
k,q:integer;
begin
k:=0;
for I := 0 to 255 do
begin
a[i]:=i;
end;
for I := 0 to 255 do
begin
q:= a[i];
k:=k+q;
end;
Label1.Caption:=inttostr(k);
end;
According to Watch List, in second cycle variable "i" starts from value 256 and going to 0 (256, 255, 254, ..., 0), but array's elements is correct (0, 1, 2, 3, ...). Variable "i" declared only locally, no global variables.
Why does this happens? Is it normal behaviour?
The short answer is because of compiler optimization. The long answer is:
In your Pascal code, the integer I has two (actually three) purposes. First, it is the loops control variable (or loop counter), that is, it controls how many times the loop is run. Secondly, it acts as index to the array a. And in the first loop it also acts as the value assigned to the array elements. When compiled to machine code, these roles are handled by different registers.
If optimization is set in compiler settings, the compiler creates code that decrements the control variable from a start value down towards zero, if it can do so, without changing the end result. This it does, because a comparison against a non-zero value can be avoided, thus being faster.
In following disassembly of the first loop, you can see that the roles of variable I are handled as:
Register eax acts as loop control variable and value to be
assigned to array elements
Register edx is pointer to array element (incremented with 4
(bytes) per turn)
disassembly:
Unit25.pas.34: for I := 0 to 255 do
005DB695 33C0 xor eax,eax // init
005DB697 8D9500FCFFFF lea edx,[ebp-$00000400]
Unit25.pas.36: a[i]:=i;
005DB69D 8902 mov [edx],eax // value assignment
Unit25.pas.37: end;
005DB69F 40 inc eax // prepare for next turn
005DB6A0 83C204 add edx,$04 // same
Unit25.pas.34: for I := 0 to 255 do
005DB6A3 3D00010000 cmp eax,$00000100 // comparison with end of loop
005DB6A8 75F3 jnz $005db69d // if not, run next turn
Since eax has two roles, it must count upward. Note that it requires three commands for each loop to manage the loop counting: inc eax, cmp eax, $00000100 and jnz $005db69d.
In the disassembly of the second loop, the roles of variable I are handled similarily as in the first loop, except I is not assigned to the elements. Therefore the loop control only acts as a loop counter and can be run downward.
Register eax is loop control variable
Register edx is pointer to array element (incremented with 4
(bytes) per turn)
disassembly:
Unit25.pas.39: for I := 0 to 255 do
005DB6AA B800010000 mov eax,$00000100 // init loop counter
005DB6AF 8D9500FCFFFF lea edx,[ebp-$00000400]
Unit25.pas.41: q:= a[i];
005DB6B5 8B0A mov ecx,[edx]
Unit25.pas.42: k:=k+q;
005DB6B7 03D9 add ebx,ecx
Unit25.pas.43: end;
005DB6B9 83C204 add edx,$04 // prepare for next turn
Unit25.pas.39: for I := 0 to 255 do
005DB6BC 48 dec eax // decrement loop counter, includes intrinsic comparison with 0
005DB6BD 75F6 jnz $005db6b5 // jnz = jump if not zero
Note that in this case only two commands are needed to manage loop counting: dec eax and jnz $005db6b5.
In Delphi XE7, in the Watches window, variable I is shown during the first loop as incrementing values but during the second loop as E2171 Variable 'i' inaccessible here due to optimization. In earlier versions I recall it was showing decrementing values which I believe you see.
I have copied your exact code and when I ran it the variable "i" counts normally in both for cycles. Have you ran the second cycle step by step? The "i" is really 256 at the start of the second cycle because of the first one but as soon as the second cycle starts "i" becomes 0 and it counts normally to 255.
I don't see how or why would it count from 256 to 0?
UPDATE:
I haven't even thought of this, but here's your explanation I belive: http://www.delphigroups.info/2/45/418603.html
" It's a compiler optimization - you're not using "I" inside your loop
hence compiler thought of a better way to count . Your loop count
will still be accurate..."

Inline asm (32) emulation of move (copy memory) command

I have two two-dimensional arrays with dynamic sizes (guess that's the proper wording). I copy the content of first one into the other using:
dest:=copy(src,0,4*x*y);
// src,dest:array of array of longint; x,y:longint;
// setlength(both arrays,x,y); //x and y are max 15 bit positive!
It works. However I'm unable to reproduce this in asm. I tried the following variations to no avail... Could someone enlighten me...
MOV ESI,src; MOV EDI,dest; MOV EBX,y; MOV EAX,x; MUL EBX;
PUSH DS; POP ES; MOV ECX,EAX; CLD; REP MOVSD;
Also tried with LEA (didn't expect that to work since it should fetch the pointer address not the array address), no workie, and tried with:
p1:=#src[0,0]; p2:=#dest[0,0]; //being no-type pointers
MOV ESI,p1; MOV EDI,p2... (the same asm)
Hints pls? Btw it's delphi 6. The error is, of course, access violation.
This is really a two-fold three-fold question.
What's the structure of a dynamic array.
Which instructions in asm will copy the array.
I'm throwing random assembler at the CPU, why doesn't it work?
Structure of a dynamic array
See: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Internal_Data_Formats
To quote:
Dynamic Array Types
On the 32-bit platform, a dynamic-array variable occupies 4 bytes of memory (and 8 bytes on 64-bit) that contain a pointer to the dynamically allocated array. When the variable is empty (uninitialized) or holds a zero-length array, the pointer is nil and no dynamic memory is associated with the variable. For a nonempty array, the variable points to a dynamically allocated block of memory that contains the array in addition to a 32-bit (64-bit on Win64) length indicator and a 32-bit reference count. The table below shows the layout of a dynamic-array memory block.
Dynamic array memory layout (32-bit and 64-bit)
Offset 32-bit -8 -4 0
Offset 64-bit -12 -8 0
contents refcount count start of data
So the dynamic array variable is a pointer to the middle of the above structure.
How do I access this in asm
Let's assume the array holds records of type TMyRec
you'll need to run this code for every inner array in the outer array to do the deep copy. I leave this as an exercise for the reader. (you can do the other part in pascal).
type
TDynArr: array of TMyRec;
procedure SlowButBasicMove(const Source: TDynArr; var dest);
asm
//insert register pushes, see below.
mov esi,Source //esi = pointer to source data
mov edi,Dest //edi = pointer to dest
sub esi,8
mov ebx,[esi] //ebx = refcount (just in case)
mov ecx,[esi+4] //ecx = element count
mov edx,SizeOf(TMyRec) //anywhere from 1 to zillions
mul ecx,edx //==ecx=number of bytes in array.
//// now we can start moving
xor ebx,ebx //ebx =0
add eax,8 //eax = #data
#loop:
mov eax,[esi+ebx] //Get data from source
mov [edi+ebx],esi //copy it to dest
add ebx,4 //4 bytes at a time
cmp ebx,ecx //is ebx> number of bytes?
jle loop
//Done copying.
//insert register pops, see below
end;
That's the copy done, however in order for the system not to crash, you need to save and restore the non volatile registers (all but EAX, ECX, EDX), see: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Program_Control
push ebx
push esi
push edi
--- insert code shown above
//restore non-volatile registers
pop edi
pop esi
pop ebx //note the restoring must happen in the reverse order of the push.
See the Jeff Dunteman's book assembly step by step if you're completely new to asm.
You will get access violations if:
you try to read from a wrong address.
you try to write to a wrong adress.
you read past the end of the array.
you write to memory you haven't claimed before using GetMem or whatever means.
if you write past the end of your buffer.
if you do not restore all non-volatile registers
Remember you're directly dealing with the CPU. Delphi will not assist you in any way.
Really fast code will use some form of SSE to move 16bytes per instruction in an unrolled loop, see the above mentioned fastcode for examples of optimized assembler.
Random assembler
In assembler you need to know exactly what you're what to do, how and what the CPU does.
Set a breakpoint and run your code. Press ctrl + alt + C and behold the CPU-debug window.
This will allow you to see the code Delphi generates.
You can single step through the code to see what the CPU does.
see: http://www.plantation-productions.com/Webster/index.html
For more reading.
Dynamic Arrays differ from Static Arrays, especially when it comes to multi-dimensionality.
Refer to this reference for internal formats.
The point is that an Array Of Array Of LongInt of dimensions X and Y (in this order!) is a pointer to an array of X pointers that point to an array of Y LongInts.
Since it seems, from your comments, that you have already allocated the space for all elements in Dest, I assume you want to do a Deep Copy.
Here a sample program, where the assembly as been made as simple as possible for the sake of clarity.
Program Test;
Var Src, Dest: Array Of Array Of LongInt;
X, Y, I, J: Integer;
Begin
X := 4;
Y := 2;
setLength(Src, X, Y);
setLength(Dest, X, Y);
For I := 0 To X-1 Do
For J := 0 To Y-1 Do
Src[I,J] := I*Y + J;
{$ASMMODE intel}
Asm
cld ;Be sure movsd increments the registers
mov esi, DWORD PTR [Src] ;Src pointer
mov edi, DWORD PTR [Dest] ;Dest pointer
mov ecx, DWORD PTR [X] ;Repeat for X times
;The number of elements in Src
#_copy:
push esi ;Save these for later
push edi
push ecx
mov ecx, DWORD PTR [Y] ;Repeat for Y times
;The number of element in a Src[i] array
mov edi, DWORD PTR [edi] ;Get the pointer to the Dest[i] array
mov esi, DWORD PTR [esi] ;Get the pointer to the Src[i] array
rep movsd ;Copy sub array
pop ecx ;Restore
pop edi
pop esi
add esi, 04h ;Go from Src[i] to Src[i+1]
add edi, 04h ;Go from Dest[i] to Dest[i+1]
loop #_copy
End;
For I := 0 To X-1 Do
Begin
WriteLn();
For J := 0 To Y-1 Do
Begin
Write(' ');
Write(Dest[I,J]);
End;
End;
End.
Note 1 This source code is intended to be compile with freepascal.
Donation of Spare Time(TM) for downloading and installing Delphi are welcome!
Note 2 This source code is for illustration purpose only, it is pretty obvious, it has already been stated above, but somehow not everybody got it.
If the OP wanted a fast way to copy the array, they should have stated so.
Note 3 I don't save the clobbered registers, this is bad practice, my bad; I forgot, as there are no subroutines, no optimizations and no reason for the compiler to pass data in the registers between the two fors.
This is left as an exercise to the reader.

Why Delphi "for" acting like that?

Delphi XE2, simple code:
function FastSwap(Value: uint16): uint16; register; overload;
asm
bswap eax
shr eax, 16
end;
...
type
PPicEleHdr = ^TPicEleHdr;
TPicEleHdr = packed record
zero, size, count: word;
end;
var
count: integer;
buf: TBytes;
begin
...
peh := #buf[offs];
count := integer(FastSwap(peh.count));
for i := 0 to count - 1 do begin
and here at for I see in CPU window
UnitExtract.pas.279: for i := 0 to count - 1 do begin
0051E459 8B45DC mov eax,[ebp-$24]
0051E45C 48 dec eax
0051E45D 85C0 test eax,eax
0051E45F 0F82CD000000 jb $0051e532
0051E465 40 inc eax
0051E466 8945AC mov [ebp-$54],eax
0051E469 C745F400000000 mov [ebp-$0c],$00000000
so when count is 0 nothing works properly, test eax, eax (eax = $FFFFFFFF after dec eax) not affecting Carry flag while jb acting by Carry flag.
Is there something I don't understand about using for?
By a process of reverse engineering, I infer that i is an unsigned 32 bit integer, Cardinal. So the compiler performs the for loop arithmetic in an unsigned context. This means that Count-1 is interpreted as unsigned, and so your loop runs from 0 to high(i).
To flesh this out, this is what happens step by step:
Count is $00000000.
Count-1 is evaluated and has value $FFFFFFFF.
Interpreted as an unsigned integer $FFFFFFFF is 232-1.
Your loop body executes for all values 0 <= i < 232.
The solution is to make your loop variable be a signed integer, for example Integer.
When you switch i to be of type Integer, the following happens:
Count is $00000000.
Count-1 is evaluated and has value $FFFFFFFF.
Interpreted as a signed integer $FFFFFFFF is -1.
The loop body does not execute.
As written, this won't compile, since you don't have a declaration for i.
But my psychic debugging senses say that i is declared somewhere as a cardinal (unsigned integer), and thus when it tries to evaluate 0 - 1, it gets MAXINT instead of -1, because unsigned integers can't represent negative values.
You should never use unsigned integers as either the index variable or the bounding variables of a for loop if there's any chance at all that they can go negative. Otherwise, you get errors like this. In fact, you should probably just not use unsigned integers in general. They're not as useful as they look (if you need a value higher than the maximum signed value for a size, it's likely that you'll end up needing a value higher than twice that at some point, so what you really need is the next larger integer size) and they tend to cause strange bugs like this one.

Resources