Fenner
Compilador

Packages e o .fxd

package, o arquivo .fxd e a RTL como DLL compartilhada — o equivalente ao .bpl + .dcp do Delphi.

A ideia#

Um package é a forma de um projeto que gera uma DLL e um arquivo .fxd do lado. A DLL tem o código, e só. O .fxd leva tudo o que o compilador precisa saber para compilar um cliente e que não cabe num binário ligável.

FormaSaiExportaO que o cliente vê
program.exe——
library.dllsó o que está no exports; o resto o otimizador podanada: escreve-se external ... name ... à mão
package.dll + .fxdtudo o que é public: rotinas, classes, records, interfaces, constantes, genéricoso .fxd; o compilador resolve tudo sozinho

O que o .fxd carrega#

  • o layout dos tipos: SizeOf, alinhamento, deslocamento de cada campo, o que é gerenciado, a cadeia de ancestrais;
  • o slot da VMT de cada método virtual: sem ele o cliente não herda nem chama um virtual;
  • as assinaturas: tipos dos parâmetros e do retorno, a convenção e as marcas que mudam a chamada (pure, impure, vital, noreturn, discardable, abi_win/abi_posix);
  • as constantes, inclusive as tipadas e as de record com variante;
  • os genéricos: o texto da declaração (a monomorfização reparseia texto; sem ele um TList<T> do package seria inutilizável no cliente). É literalmente o que o .dcp do Delphi carrega, e pela mesma razão;
  • os corpos que o cliente reanalisa: inline e o corpo curto =>;
  • os atributos e o que alimenta o .rtti.json.

É um formato binário próprio (uma serialização da árvore sintática), que carrega rápido e se distribui sem fonte. O .fxd leva a data e o hash do compilador que o gerou, e abrir um .fxd de outra versão do compilador é erro, não aviso.

A RTL como package#

A RTL BedRock é o primeiro cliente do mecanismo, não um caso especial: a Fenner.BedRock.dll com seu .fxd, .fxnames e .lib. O que isso resolve, em ordem de importância:

  1. Alocador único. Sem package, cada binário tem o seu alocador; uma cadeia alocada na DLL e liberada no .exe é um crash. Com a runtime em package isso deixa de existir, e é o ganho que muda o que se pode escrever.
  2. Binários pequenos. O .exe de um aplicativo que usa o package tem algo como 1 MB em vez de 12 MB com a RTL embutida, e compila em bem menos tempo (medido no RAD: 22 s contra 39 s no mesmo projeto).
  3. is/as entre módulos. A VMT e a informação de tipo são únicas; sem isso, o mesmo tipo teria duas VMTs e o is mentiria entre .exe e .dll.
  4. Exceção atravessando módulo. As exceções são threadvar e o tratador de hardware é registrado por módulo; com a runtime única, a intenção é que um raise no package possa ser capturado no .exe. É um objetivo do desenho: a primeira entrega do mecanismo prometia só o caso do mesmo lado.

O preço é que o executável só abre com a Fenner.BedRock.dll ao lado: ela se distribui junto.

Ligando um package#

No .config do projeto:

config
Config  All  RtlPackage  C:\Fenner\Common\BedRock\Fenner.BedRock

A chave RtlPackage aponta o package de runtime (o .fxd, .fxnames, .lib e .dll com esse nome-base). LinkPackage liga um .fxd qualquer, e UnlinkPackage cancela um que veio de uma configuração acima. Para ignorar tudo isso numa compilação e embutir a RTL inteira, use --NoPackages.

protected#

No package aparece o sentido da terceira visibilidade de módulo. public significa exportado: o símbolo é publicado e o custo de mantê-lo estável passa a ser seu. protected é visível para todos os módulos do projeto e invisível fora dele: para quem está de fora do package, é como private. Sem isso, empacotar a RTL obrigaria a exportar tudo o que os módulos dela usam entre si; com isso, a superfície exportada passa a ser uma decisão. Veja Módulos e visibilidade.

Por dentro, para quem for mexer#

  • Para a RTL, o mecanismo não é o .fxd. O cliente continua lendo o fonte da RTL (declarações, tipos, o texto dos genéricos, com os layouts idênticos garantidos de graça), mas não emite nada dela: cada corpo vira external com o símbolo vindo do .fxnames. O .fxd é o par das DLLs de usuário, que distribuem sem fonte. É o espelho do que o Delphi faz: o compilador lê o DCU inteiro e o código fica no BPL.
  • A DLL exporta todas as rotinas com corpo, não só as públicas, como um BPL: um genérico da RTL instanciado no cliente compila no cliente e chama os internos da RTL, e o mesmo vale para inline. Os nomes obscuros do .fxnames mantêm a superfície ilegível.
  • VMTs são importação de dado: a de uma classe do cliente que herda de TObject precisa apontar a VMT do TObject da DLL, o que faz o is dizer a verdade. O arranque preenche uma tabela de remendos, e esse arranque é o papel do SysInit.
  • class constructor e initialization da RTL rodam no DllMain da DLL, e o carregador do Windows garante a ordem (a DLL inicializa antes do main).
  • O .fxnames pode mudar entre recompilações do package; um cliente compilado contra um mapa antigo precisa ser recompilado.