2

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?

kludg
  • 27,213
  • 5
  • 67
  • 118

2 Answers2

10

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 type Extended, regardless of the types of x and y. For other arithmetic operators, the result is of type Extended whenever at least one operand is a real; otherwise, the result is of type Int64 when at least one operand is of type Int64; otherwise, the result is of type Integer. 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
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Just an independent question, Remy: I can see that you are setting the compiler directives within the BEGIN/END pair and that you don't reset them afterwards. Is this because - by setting them within the function body - they are local to that function only? Whenever I need to temporarily set some compiler directives, I have always done something along {$IFOPT W+}{$DEFINE WASON}{$W-}{$ENDIF} and then {$IFDEF WASON}{$W+}{$ENDIF} but if it is possible to have "local" compiler directives by placing them within the body, then this is unncessary. – HeartWare Dec 30 '15 at 07:39
  • The two directives I mention both have **local** scope, but I think my understanding of local scope in relation to compiler directives was wrong. I have updated my example. – Remy Lebeau Dec 30 '15 at 10:52
  • In FPC you can use $push/$pop to restore the state. – Marco van de Voort Dec 31 '15 at 12:52
  • @MarcovandeVoort: True. Delphi does not have an equivalent to that (I wish it did). – Remy Lebeau Dec 31 '15 at 20:52
6

MUL always multiplies by AL, AX or EAX (more details), so you should specify only the other operand.

Sergey Salnikov
  • 321
  • 1
  • 5