O idioma original _ftol em Delphi de 32 bits assemelha-se a uma engenhosa instrução de linha única: um wrapper de função Pascal que mergulha no assembly inline para manipular a palavra de controlo x87 FPU, truncar o valor na pilha FPU e extrair o resultado. Este formato compilou perfeitamente no DCC32 durante bastante tempo, sendo essa a razão exata pela qual acabou em tantas unidades mais antigas de gráficos e PDF sem que alguém o questionasse
Mude o destino da compilação para 64 bits e o compilador termina com a mensagem E1025 Unsupported language feature: 'ASM'. Esse erro não constitui um aviso de compatibilidade. O que significa é que o DCC64 não irá sequer compilar a rotina de todo, independentemente da eficácia com que o assembly tenha funcionado no passado
A versão original de 32 bits apresentava, regra geral, o seguinte aspeto:
function _ftol(f: Double): Integer; cdecl;
begin
asm
lea eax, f
fstp qword ptr [eax]
end;
Result := Trunc(f);
end;
Esse bloco asm no interior de um corpo begin...end em Pascal é exatamente aquilo que o DCC64 recusa. Os dois compiladores possuem regras diferentes relativamente à zona em que o uso do assembly é autorizado, e a fronteira entre eles tem relevância
A razão pela qual o DCC64 estabelece o limite de forma diferente
O DCC32 permite assembly inline no interior de rotinas Pascal comuns. O compilador conhece a convenção de chamada de 32 bits e deduz onde residem as variáveis locais e os parâmetros, pelo que tolera fragmentos de assembly que acedem à "stack frame" por nome. O DCC64 adota uma posição mais restrita: o assembly tem de estar numa função assembler dedicada, na qual todo o corpo é assembly e a convenção de chamada é tratada explicitamente. A mistura de Pascal com asm não é de todo suportada
O motivo subjacente é de natureza arquitetural. Na convenção de chamada do Windows de 64 bits (ABI da Microsoft), os quatro primeiros parâmetros chegam em RCX, RDX, R8 e R9 para os tipos inteiros, ou em XMM0 a XMM3 para os tipos de vírgula flutuante. A FPU x87 não tem qualquer envolvimento na passagem normal de parâmetros; a FPU x87 está tecnicamente disponível, mas a ABI não a utiliza para transporte de argumentos. Um código em assembly que presuma que um valor se encontra "na pilha da FPU" está a raciocinar sobre um estado que a ABI de 64 bits nunca chega a criar
Logo, o antigo fragmento não tem apenas um problema de sintaxe. Mesmo que o DCC64 o aceitasse, os pressupostos subjacentes aos registos estariam errados
Escrever uma versão assembler adequada de 64 bits
Quando tem uma necessidade genuína de exportar um símbolo _ftol com a convenção cdecl por motivos de compatibilidade binária, a função tem de ser escrita como uma rotina puramente em assembler. Ao abrigo da ABI de 64 bits, um parâmetro Double chega em XMM0 e o resultado inteiro tem de estar presente em RAX no momento do retorno. A diretiva .NOFRAME informa o DCC64 de que a rotina procede à gestão da sua própria pilha (stack), o que se revela adequado para uma função folha tão curta quanto esta:
function _ftol: Integer; cdecl;
// Double value expected in XMM0 per 64-bit ABI
asm
.NOFRAME
cvttsd2si rax, xmm0 // truncate-to-integer, result in rax
end;
CVTTSD2SI é a instrução SSE2 para converter um número de vírgula flutuante de precisão dupla num inteiro com sinal através de truncamento em direção ao zero, que é exatamente o que _ftol supostamente deve fazer. Trata-se de apenas uma instrução, obtém o parâmetro de forma direta no local onde a ABI o deixou e coloca o resultado no local exato em que a ABI o espera. Não é necessário nenhum tipo de malabarismo com a palavra de controlo da FPU
Tenha em atenção que, se a entrada ultrapassar o intervalo de um inteiro de 32 bits com sinal, CVTTSD2SI devolve o valor de um inteiro indefinido ($80000000). É exatamente este o comportamento que se regista no caso do x87 fistp face a uma entrada fora dos limites. Confirme sempre, e antes de dar a migração como terminada, se quem efetua as chamadas é capaz de gerar tais valores
Quando Trunc é a melhor resposta
A versão em assembler apresentada acima só vale a pena quando se tem uma verdadeira exigência de compatibilidade binária: um interveniente de chamada externa aguarda o símbolo _ftol com uma convenção de chamada específica, e o programador não pode alterar esses intervenientes de chamada (callers). Trata-se de uma situação fora do comum. Na maior parte do tempo, o _ftol não passou de um simples auxiliar de natureza privada confinado e operante em exclusivo no seio da mesma unidade, e não subsiste nela e na sua conceção qualquer dependência externa associada ao seu nome ou convenção de chamada
Para esses casos, substitua-a pelo simples Pascal:
function _ftol(f: Double): Integer; cdecl;
begin
Result := Trunc(f);
end;
A instrução Trunc trunca em direção ao zero, procedimento este que coincide com o que a instrução _ftol realizava com a palavra de controlo do x87 configurada no modo de truncamento. Compila tanto no DCC32 quanto no DCC64, não carecendo sequer de modificação da sua estrutura. O compilador tratará de gerar a instrução conveniente para cada um dos destinos: no destino x64, emitirá habitualmente CVTTSD2SI, configurando a mesmíssima instrução observável numa versão escrita manualmente. A conduta é estritamente idêntica, as condicionantes da plataforma deixam de constituir um impedimento, e deixa de existir assembly para ser mantido
A única diferença semântica que vale a pena analisar é a seguinte: Trunc produz invariavelmente uma exceção EInvalidOp na configuração predefinida do Delphi quando a entrada é do tipo NaN ou corresponde a um infinito. O anterior x87 fistp presente no código original limitava-se a gravar um padrão de bits sem despoletar nada. Caso o código submeta valores invulgares de vírgula flutuante a esta função e o antigo comportamento se mantiver silencioso, salvaguarde, chamando à função as instruções IsNaN e IsInfinite da Math antes de invocar Trunc
Compilação condicional quando ambos os destinos (targets) permanecem ativos
Alguns projetos são forçados a distribuir de forma contínua binários em edições quer de 32 quer de 64 bits. Caso pretenda manter a implementação original em assembly da variante de 32 bits e introduzir a nova rotina destinada e vocacionada estritamente aos sistemas a 64 bits, efetue-o recorrendo neste panorama perante as ferramentas condicionais e use CPUX64:
function _ftol(f: Double): Integer; cdecl;
begin
{$IFDEF CPUX64}
Result := Trunc(f);
{$ELSE}
// 32-bit path: DCC32 accepts inline asm
asm
lea eax, f
fstp qword ptr [eax]
end;
Result := Trunc(f);
{$ENDIF}
end;
Essa consiste na correção mecânica mínima e justifica ser tratada como provisória. Uma base de código que mantiver um código assembly específico de arquitetura a funcionar como mero auxiliar em prol e visando unicamente o truncamento de float para inteiro carrega um débito técnico dispensável. O ramo orientado à variante 32 bits acaba unicamente por se poder suprimir do código se e quando o programador confirmar plenamente não existir dependência perante os efeitos colaterais resultantes inerentes da intervenção sob alçada respeitante aos trâmites focando a FPU face aos ditames consagrados pelo modelo antigo
Se a função aparecer num componente utilizado em múltiplas unidades, pesquise em toda a base de código por _ftol antes de decidir como migrar. Um símbolo com esse nome pode ser declarado em mais do que um lugar; o linker escolhe um e ignora os restantes silenciosamente, o que significa que o programador pode corrigir uma cópia e continuar a ligar (link) a uma diferente que não sofreu qualquer alteração