【问题标题】:FPC BASM32 MUL bug?FPC BASM32 MUL 错误?
【发布时间】:2015-12-30 18:51:01
【问题描述】:

我在将 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(当前稳定版本)

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

【问题讨论】:

    标签: delphi lazarus freepascal fpc basm


    【解决方案1】:

    FreePascal 是正确的。 MUL只有3种形式:

    MUL r/m8
    MUL r/m16
    MUL r/m32
    

    执行第一个操作数(目标操作数)和第二个操作数(源操作数)的无符号乘法,并将结果存储在目标操作数中。 目标操作数是位于寄存器 AL、AX 或 EAX 中的隐含操作数(取决于操作数的大小);源操作数位于通用寄存器或内存位置

    换句话说,第一个操作数(用于输入和输出)在AL/AX/EAX 中指定,第二个输入操作数明确指定为通用寄存器或内存地址。

    所以,MUL EAX,EDX 确实是一条无效的汇编指令。

    如果您在 Delphi 中编译此代码并使用调试器查看生成的程序集,您会看到对 Mul(10,20) 的调用会生成以下程序集代码:

    // Mul(10,20)
    mov edx,$00000014
    mov eax,$0000000a
    call Mul
    

    //MUL    EAX,EDX
    mul edx
    

    因此,如您所见,Delphi 正在实际解析您的源代码,发现第一个操作数是 EAX 并为您剥离它,从而生成正确的程序集。 FreePascal 不会为你做这一步。

    解决方法?首先编写适当的汇编代码。不要依赖编译器为您重新解释代码。

    function Mul(A, B: LongWord): LongWord;
    asm
             MUL    EDX
    end;
    

    或者,您可以不直接编写汇编代码,让编译器为您完成工作。它知道如何将两个LongWord 值组合在一起:

    function Mul(A, B: LongWord): LongWord;
    begin
      Result := A * B;
    end;
    

    虽然 Delphi 在这种情况下使用 IMUL 而不是 MUL。来自德尔福的documentation

    x / y 的值是Extended 类型,与xy 的类型无关。对于其他算术运算符,只要至少有一个操作数是实数,结果就是Extended 类型;否则,当至少一个操作数为Int64 类型时,结果为Int64 类型; 否则,结果的类型为Integer。如果操作数的类型是整数类型的子范围,则将其视为整数类型。

    它还使用一些难看的臃肿程序集,除非堆栈帧被禁用并启用优化。通过配置这两个选项,可以让Mul() 生成单个IMUL EDX 指令(当然还有RET 指令)。如果您不想在项目范围内更改选项,可以使用 {$STACKFRAMES OFF}/{$W-}{$OPTIMIZATION ON}/{$O+} 编译器指令将它们隔离为 Mul()

    {$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}
    

    生成:

    imul edx
    ret
    

    【讨论】:

    • 只是一个独立的问题,Remy:我可以看到您正在 BEGIN/END 对中设置编译器指令,并且之后您没有重置它们。这是因为 - 通过将它们设置在函数体内 - 它们仅对该函数是本地的吗?每当我需要临时设置一些编译器指令时,我总是沿着 {$IFOPT W+}{$DEFINE WASON}{$W-}{$ENDIF} 然后 {$IFDEF WASON}{$W+}{$ENDIF}但如果可以通过将“本地”编译器指令放置在主体中来获得它们,那么这是不必要的。
    • 我提到的两个指令都具有 local 作用域,但我认为我对与编译器指令相关的局部作用域的理解是错误的。我已经更新了我的示例。
    • 在FPC中可以使用$push/$pop来恢复状态。
    • @MarcovandeVoort:是的。 Delphi 没有类似的东西(我希望有)。
    【解决方案2】:

    MUL 总是乘以 AL、AX 或 EAX (more details),因此您应该只指定另一个操作数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多