Fenner
A linguagem

Diretivas e compilação condicional

{$if}, {$define}, {$include}, {$macro}, {$message}, {$config} e as dicas de otimização.

Uma diretiva tem a forma {$nome …} (ou (*$nome …*)). O compilador a reconhece antes do comentário de chaves: {$ abre diretiva, { abre comentário.

Compilação condicional#

Fenner
{$define TRACE}

{$ifdef TRACE}
  WriteLn('depurando');
{$else}
  ...
{$endif}
DiretivaEfeito
{$define X} / {$undef X}liga e desliga um símbolo
{$ifdef X} / {$ifndef X}texto condicional por símbolo
{$if expr} … {$elseif expr} … {$else} … {$endif}condicional por expressão (veja abaixo)
{$include arquivo}traz o conteúdo de um .fxi
{$message hint|warning|error|fatal 'texto'}mensagem do compilador (veja abaixo)

Os símbolos da plataforma (MSWin64, MSWin…) são definidos pelo compilador; -D<símbolo> na linha de comando e Define na configuração definem outros.

{$if} e as funções de tipo#

{$if} aceita expressões sobre tipos e símbolos (Is(T, X), SizeOf(T), IsManaged(T), Defined(X), Declared(X)…). Dentro de um genérico ele é reavaliado na instanciação, com o T já trocado pelo tipo real.

conditional.fxp
program Conditional;

{$define TRACE}

type
  TInfo<T> = class(TObject)
  public
    class function Describe: UString;
  end class;

// dentro de um genérico, o {$if} é reavaliado na instanciação, com o T já trocado
class function TInfo<T>.Describe: UString;
begin
  {$if Is(T, UInt8)}
  Exit('um byte');
  {$elseif IsString(T)}
  Exit('um texto');
  {$elseif SizeOf(T) < 8}
  Exit('pequeno (' + IntToUStr(SizeOf(T)) + ' bytes)');
  {$else}
  Exit('grande (' + IntToUStr(SizeOf(T)) + ' bytes)');
  {$endif}
end function;

begin
  WriteLn(TInfo<UInt8>.Describe);
  WriteLn(TInfo<UString>.Describe);
  WriteLn(TInfo<Int32>.Describe);
  WriteLn(TInfo<Int128>.Describe);

  {$ifdef TRACE}
  WriteLn('TRACE está ligado');
  {$endif}
  {$ifdef MSWin64}
  WriteLn('compilado para Windows 64 bits');
  {$endif}
end.
Saída
um byte
um texto
pequeno (4 bytes)
grande (16 bytes)
TRACE está ligado
compilado para Windows 64 bits

{$message}#

FormaO que faz
{$message hint 'texto'}sai na compilação; não muda o resultado
{$message warning 'texto'}o mesmo, com outro rótulo
{$message error 'texto'}entra na lista de erros; a leitura segue, e no fim ela mostra todos
{$message fatal 'texto'}entra na lista e para ali

Sem uma severidade que se reconheça, o texto inteiro é a mensagem e ela vale como hint.

Macros#

{$macro Nome(P1, P2) corpo} guarda texto. Na invocação, %P1% é substituído e o resultado é lido pela mesma pilha do {$include}, então uma macro pode invocar outra, e um erro dentro da expansão aponta <macro Nome>(linha,coluna).

macros.fxp
program Macros;

// {$macro} guarda TEXTO: na invocação, %Parâmetro% é substituído e o resultado
// é lido como se estivesse escrito ali.

{$macro MaxFunc(Type, Name)
function %Name%(const A, B: %Type%): %Type%;
begin
  if A > B then
    Exit(A);
  Exit(B);
end;}

MaxFunc('Int32', 'MaxInt');
MaxFunc('Float64', 'MaxFloat');

begin
  WriteLn(MaxInt(3, 9));
  WriteLn(MaxFloat(2.5, 1.5));
end.
Saída
9
2.5
  • %% é um % literal.
  • Uma macro pode expandir para comandos, não só para declarações.
  • O corpo pode ter chaves { } dentro; o comentário não termina a macro antes da hora.
  • {$undefmacro Nome} devolve o nome, e depois ele aceita outra definição.
  • Uma macro sem parâmetros é {$macro Nome() …}.

As sobrecargas de Min e Max da RTL são geradas assim: vinte cópias das mesmas linhas, mudando só o tipo.

{$config}#

{$config Chave valor} ajusta o compilador. O alcance (local, global ou region) diz até onde vale: este módulo (omissão), a compilação toda, ou até o end; da região em vigor.

ChaveO que faz
AppType GUI|CONSOLEsubsistema do executável (padrão CONSOLE; GUI não abre janela de console)
Manifest 'arquivo' · Icon 'arquivo'liga o manifesto e o ícone ao executável
Resource 'arquivo'embute um arquivo como recurso (RCData)
VerInfo …informações de versão do executável
WCLStyle 'arquivo'embute um estilo visual (.vsf) da WCL
platform MSWin64|Linux64alvo e símbolos da plataforma (e o triple do IR)
inside CRT · outside CRTa CRT dentro do binário (sempre roda) ou fora (binário menor)
generate_llvm on|offgravar o .ll e usar o clang, ou IR em memória (omissão)
rangechecks on|offverificação de faixa em execução (padrão off)
overflowchecks on|offverificação de transbordo em execução (padrão off)
divcheck on|offchecagem de divisão por zero antes do sdiv (padrão on)
allocsite on|off16 bytes na frente de cada bloco com o arquivo(linha) de quem o pediu, para o relatório de vazamento
devirtualization on|offvirtual sem override vira chamada direta (padrão on)
align N · minenumsize Nalinhamento dos records e largura mínima dos enumerados
typedaddress on|off · pointermathendereçamento tipado e aritmética de ponteiro
RecordLayoutKind · ObjectLayoutKind Sequential|Autoordem dos campos (Auto autoriza reordenar)
Record/Object PublishTrimmed, Record/Object/Global ParamsTrimmedautorizações para os passes de otimização
rtti on|offgrava o RTTI externo (.rtti.json)
DefField, DefGetVisibility, DefSetVisibilityos acessores sintetizados por DefReadWrite
discard 'ID' · cancel_discard 'ID'a lista de descarte do discardable

Outras diretivas ligam bibliotecas de objetos ({$ObjLink 'arquivo.lib'}, usada pela RTL para trazer o OpenSSL, o zlib e o pcre) e controlam o modo de código ({$code unsafe} / {$code safe}).

Dicas de otimização#

Palavras que dizem ao compilador o que a rotina promete. Ele pode usar isso para descartar chamadas e eliminar código.

PalavraEfeito
purea chamada pode ser eliminada quando não fizer diferença: em posição de comando (resultado jogado fora), ela some na compilação
forcena chamada: esta específica não pode ser eliminada (force TryStrToInt('1', X);)
discardable 'ID'rotina, bloco (begin discardable 'ID' … end), campo, classe inteira (end discardable 'ID';) ou região descartáveis por identificador; com {$config discard 'ID'} o trecho é descartado sem nem ser resolvido, e citá-lo de código vivo é erro (Declared(X) responde False)
preserveo item não pode ser alterado automaticamente (assinatura intocável para os cortes de parâmetros); a exclusão por não-uso continua valendo
abi_win / abi_posixforça a ABI da rotina, seja qual for a plataforma alvo
noreturna chamada não volta (exceção ou fim do processo); o que vem depois é inalcançável
Fenner
function TryStrToInt(const S: UString; out I: UInt128): Bool8; pure;
function Log(const S: UString): Bool8; discardable 'LOG';
procedure Halted(const ACode: Int32); noreturn;

Existem ainda:

  • impure: o oposto de pure. A chamada nunca é eliminada, em hipótese nenhuma.
  • vital, no parâmetro out: sem ele, escrever no parâmetro do chamador seria efeito colateral e proibiria o pure. vital diz que essa escrita é a única razão de a chamada existir, então ela vale enquanto alguém ler o que foi escrito. Um parâmetro também pode ser marcado pure.
  • {$config PureImpure Ignore|Hint|Warning|Error|Fatal} (padrão Ignore): exige que toda rotina se declare pure ou impure. O alcance é léxico, como o do divcheck: quem quer a cobrança a pede, e a RTL inteira não responde junto.
Fenner
function TryStrToUInt32(const pure S: UString; out vital I: UInt32): Bool8; pure;
function IncrementCounter: Bool8; impure;