2

我在将 Delphi BASM32 代码移植到 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.

上面的代码在 Delphi XE 中编译并按预期工作;FPC 在线输出编译时错误MUL EAX,EDX

错误:Asm: [mul reg32,reg32] 操作码和操作数的无效组合

我正在使用 Lazarus 1.4.4/FPC2.6.4 for Win32(当前稳定版本)

任何解决方法或解决该问题的方法?

4

2 回答 2

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
于 2015-12-30T06:27:05.570 回答
6

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

于 2015-12-30T06:26:51.863 回答