Consider the following code, which is a naive way of generating an integer identifier based on the current time, where Result
is Int64:
dtRef := Now;
Result := YearOf(dtRef) * 100000000000 +
MonthOf(dtRef) * 1000000000 +
DayOf(dtRef) * 10000000 +
HourOf(dtRef) * 100000 +
MinuteOf(dtRef) * 1000 +
SecondOf(dtRef) * 10 +
m_nLastTaskID;
For instance, an ID generated today gives 20190503163412142 (< 2^55), which is well within range of Int64 (2^63 - 1).
However, this gives an integer overflow on both Berlin and Rio. It compiles to:
MyUnitU.pas.580: Result := YearOf(dtRef) * 100000000000 +
007BCEAE 6A17 push $17
007BCEB0 6800E87648 push $4876e800
007BCEB5 FF75EC push dword ptr [ebp-$14]
007BCEB8 FF75E8 push dword ptr [ebp-$18]
007BCEBB E84CF2D3FF call YearOf
007BCEC0 0FB7C0 movzx eax,ax
007BCEC3 33D2 xor edx,edx
007BCEC5 E8FA0AC5FF call @_llmulo
007BCECA 7105 jno $007bced1
007BCECC E83FC7C4FF call @IntOver
007BCED1 52 push edx
007BCED2 50 push eax
007BCED3 FF75EC push dword ptr [ebp-$14]
007BCED6 FF75E8 push dword ptr [ebp-$18]
007BCED9 E852F2D3FF call MonthOf
007BCEDE 0FB7C0 movzx eax,ax
007BCEE1 BA00CA9A3B mov edx,$3b9aca00
007BCEE6 F7E2 mul edx
007BCEE8 7105 jno $007bceef
007BCEEA E821C7C4FF call @IntOver
The first multiplication uses _llmulo
(64-bit signed multiply, with overflow check), whereas the second uses plain mul
. Below is the second multiplication commented by me:
007BCED9 E852F2D3FF call MonthOf // Put the month (word) on AX
007BCEDE 0FB7C0 movzx eax,ax // EAX <- AX as a double word
007BCEE1 BA00CA9A3B mov edx,$3b9aca00 // EDX <- 1000000000
007BCEE6 F7E2 mul edx // EDX:EAX <- EAX * EDX
007BCEE8 7105 jno $007bceef // Jump if overflow flag not set
007BCEEA E821C7C4FF call @IntOver // Called (overflow flag set!)
I thought the overflow flag was being set on _llmulo
because of this bug report on problems with _llmulo
(there's also a comment on the source stating that overflow checking is not working).
However, when debugging the overflow flag is actually set after mul
! According to Intel's manual:
The OF and CF flags are set to 0 if the upper half of the result is 0; otherwise, they are set to 1.
In this case EDX
is 0x2A05F200
and EAX
is 0x00000001
after mul
, so it seems the OF flag should have been set indeed. The question is, is mul
correct here? Is this a problem with the compiler or a problem with my code?
I noticed that if I attribute the result of MonthOf
etc to Int64 variables before multiplying by 1000..., all multiplications are done using _llmulo
and it works fine.