Artigo técnico

Migrar o assembly inline _ftol do Delphi para builds de 64 bits

· Programação Delphi

Código Delphi antigo às vezes incluía um pequeno helper _ftol para converter um valor de ponto flutuante em inteiro. Em projetos de 32 bits era comum escrever esse tipo de helper com assembler inline, especialmente em código gráfico e PDF ajustado para compiladores antigos.

1
2
3
4
5
6
7
8
function _ftol( f: double) : Integer; cdecl;
begin
  asm
    lea   eax, f
    fstp  qword ptr [eax]
  end;
  result := Trunc(f);
end;

Essa abordagem quebra quando a mesma unit é compilada para um destino de 64 bits. O compilador Delphi de 64 bits informa E1025 Unsupported language feature: 'ASM' porque o DCC64 não aceita Pascal misturado com assembly inline dentro de rotinas Pascal normais.

Por que o compilador de 64 bits rejeita isso

O DCC64 pode montar rotinas assembly de 64 bits, mas a rotina deve ser escrita como uma rotina assembler completa, considerando a convenção de chamada de 64 bits. Você não pode copiar assembler inline de 32 bits para dentro do corpo de uma função Pascal e esperar que o compilador traduza uso de registradores, layout de pilha ou comportamento x87.

Uma versão assembler completa é possível, e código como o seguinte mostra o tipo de tratamento da control word que pode ser necessário quando o valor já está na pilha FPU.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function _ftol: Integer; cdecl;
// Assumes double value is in FPU stack on entry
// Make a truncation to integer and put it into function result
var
  TmpVal: Int64;
  SaveCW, ScratchCW: word;
 
asm
  .NOFRAME
  fnstcw word ptr [SaveCW]
  fnstcw word ptr [ScratchCW]
  or word ptr [ScratchCW], 0F00h  ;// trunc toward zero, full precision
  fldcw word ptr [ScratchCW]
  fistp qword ptr [TmpVal]
  fldcw word ptr [SaveCW]
  mov rax, TmpVal
end;

Prefira Pascal quando o objetivo for apenas truncar

Para a maior parte do código de aplicação, a rotina assembler é desnecessária. Se o requisito real é truncar um Double em direção a zero e retornar um inteiro, a versão clara e portátil é simplesmente:

1
2
3
4
function _ftol(f: double): integer; cdecl;
begin
  Result := Trunc(f);
end;

Trunc expressa a intenção diretamente, compila para destinos de 32 e 64 bits e evita o custo de manutenção de assembler específico de arquitetura. Também fica mais fácil para futuros mantenedores raciocinar sobre checagem de faixa e comportamento de overflow.

Orientação de migração

  • Use a implementação Pascal quando você controla o ponto de chamada e só precisa de conversão numérica.
  • Mantenha uma exportação assembler apenas quando compatibilidade binária exigir um símbolo e uma convenção de chamada específicos.
  • Não copie código de registradores de 32 bits para uma rotina de 64 bits sem revisar a convenção de chamada e a largura dos dados.
  • Teste casos de borda como valores negativos, valores grandes e valores fora da faixa do inteiro de destino.

Considerações de faixa e comportamento

Substituir assembler por Trunc ainda deve ser tratado como uma mudança de comportamento que merece testes. Confirme como seu código deve se comportar para números negativos, frações perto de um limite inteiro e valores fora da faixa de Integer. O assembler antigo pode ter dependido do estado da control word da FPU, enquanto a versão Pascal segue a implementação RTL do compilador de destino.

Se o helper fica dentro de um componente PDF ou gráfico, teste saídas que usam texto rotacionado, coordenadas escaladas e comandos de desenho transformados. Essas áreas têm mais chance de revelar diferenças sutis de conversão de ponto flutuante para inteiro do que exemplos simples.

Caminho de migração recomendado

Primeiro, substitua helpers privados de conversão por código Pascal claro quando possível. Segundo, mantenha a implementação original de 32 bits apenas atrás de uma condição de 32 bits se ela ainda for necessária. Terceiro, adicione um branch separado de 64 bits somente quando um requisito medido de desempenho ou compatibilidade binária justificar. Isso mantém o caminho portátil simples e transforma o caminho específico da arquitetura em exceção, não no padrão.