I come across a problem while porting Delphi BASM32 code to FPC:
program MulTest;
{$IFDEF FPC}
{$mode delphi}
{$asmmode intel}
{$ELSE}
{$APPTYPE CONSOLE}
{$ENDIF}
function Mul(A, B: LongWord): LongWord;
asm
MUL EAX,EDX
end;
begin
Writeln(Mul(10,20));
Readln;
end.
The above code compiles in Delphi XE and works as expected; FPC outputs compile-time error on MUL EAX,EDX
line:
Error: Asm: [mul reg32,reg32] invalid combination of opcode and operands
I am using Lazarus 1.4.4/FPC2.6.4 for Win32 (the current stable version)
Any workaround or fix for the problem?
FreePascal is correct. There are only 3 forms of MUL
:
MUL r/m8
MUL r/m16
MUL r/m32
Performs an unsigned multiplication of the first operand (destination operand) and the second operand (source operand) and stores the result in the destination operand. The destination operand is an implied operand located in register AL, AX or EAX (depending on the size of the operand); the source operand is located in a general-purpose register or a memory location.
In other words, the first operand (used for both input and output) is specified in AL
/AX
/EAX
, and the second input operand is explicitly specified as either a general-purpose register or a memory address.
So, MUL EAX,EDX
is indeed an invalid assembly instruction.
If you compile this code in Delphi and use the debugger to look at the generated assembly, you would see that the call to Mul(10,20)
generates the following assembly code:
// Mul(10,20)
mov edx,$00000014
mov eax,$0000000a
call Mul
//MUL EAX,EDX
mul edx
So, as you can see, Delphi is actual parsing your source code, sees that the first operand is EAX
and strips it off for you, thus producing the correct assembly. FreePascal is not doing that step for you.
The workaround? Write proper assembly code to begin with. Don't rely on the compiler to re-interpret your code for you.
function Mul(A, B: LongWord): LongWord;
asm
MUL EDX
end;
Or, you could simply not write assembly code directly, let the compiler do the work for you. It knows how to multiple two LongWord
values together:
function Mul(A, B: LongWord): LongWord;
begin
Result := A * B;
end;
Though Delphi does use IMUL
instead of MUL
in this case. From Delphi's documentation:
The value of
x / y
is of typeExtended
, regardless of the types ofx
andy
. For other arithmetic operators, the result is of typeExtended
whenever at least one operand is a real; otherwise, the result is of typeInt64
when at least one operand is of typeInt64
; otherwise, the result is of typeInteger
. If an operand's type is a subrange of an integer type, it is treated as if it were of the integer type.
It also uses some unsightly bloated assembly unless stackframes are disabled and optimizations are enabled. By configuring those two options, it is possible to get Mul()
to generate a single IMUL EDX
instruction (plus the RET
instruction, of course). If you don't want to change the options project-wide, you can isolate them to just Mul()
by using the {$STACKFRAMES OFF}
/{$W-}
and {$OPTIMIZATION ON}
/{$O+}
compiler instructions.
{$IFOPT W+}{$W-}{$DEFINE SF_Was_On}{$ENDIF}
{$IFOPT O-}{$O+}{$DEFINE O_Was_Off}{$ENDIF}
function Mul(A, B: LongWord): LongWord;
begin
Result := A * B;
end;
{$IFDEF SF_Was_On}{W+}{$UNDEF SF_Was_On}{$ENDIF}
{$IFDEF O_Was_Off}{O-}{$UNDEF O_Was_Off}{$ENDIF}
Generates:
imul edx
ret