Executáveis
Contrato para os programas construídos por clau -o ou por uma compilação de
projeto. As entradas posicionais, ou exatamente um alvo de projeto selecionado,
são ligadas no caminho de -o; uma compilação de projeto liga cada alvo
executável selecionado ao output do seu manifesto
(ligação, projetos).
Seleção da entrada
A entrada é uma função exportada de aridade 1 que recebe a lista de argumentos.
Uma FUNCTION selecionada sem FUNCTION/1 pode, em vez disso, ser uma
FUNCTION/0 exportada: é executada sem os argumentos do programa (as opções do
runtime continuam a ser retiradas). Um sufixo /0 ou /1 seleciona apenas
essa aridade, pelo que --entry benchmarks:run/0 executa run/0 mesmo quando
run/1 também está exportada. A entrada por omissão (sem seleção) é sempre
main/1.
| Origem | Grafia | Âmbito |
|---|---|---|
| CLI | --entry MODULE[:FUNCTION[/ARITY]] | Lote posicional, ou o único alvo de projeto selecionado |
| Manifesto | entry = "MODULE[:FUNCTION[/ARITY]]" numa tabela [[targets]] | Esse alvo |
| Por omissão | O único escript, caso contrário o único módulo que exporta main/1 | Só quando é pedido um executável (-o, ou um alvo de projeto com output) |
FUNCTIONassume por omissãomain. Os nomes são texto de átomo sem aspas: 1–255 escalares Unicode, UTF-8 válido, sem carateres de controlo e sem:. Um/DIGITSfinal depois deFUNCTIONé a aridade e tem de ser0ou1; as outras barras fazem parte do nome. Outras grafias são erros de utilização (CLI, código de saída 2) ou erros de manifesto (código de saída 1).- O manifesto inteiro é descodificado, pelo que um
entrymal formado falha mesmo em alvos não selecionados (manifesto). --entryna CLI sobrepõe-se à chave do manifesto e exige exatamente um alvo selecionado. Entra em conflito com as ações de verificação/impressão e com--new-project; essas ações ignoram oentrydo manifesto (tal como ignoramoutput).- Uma entrada explícita é validada em todos os modos de compilação (por
omissão,
--emit, inspeção de IR/tipos), juntamente com os diagnósticos semânticos comuns.
| Falha | Diagnóstico (código de saída 1) |
|---|---|
| Módulo fora do lote | <origin>: entry module M is not among the compiled modules (origem: --entry ou o manifesto file:line:col [target t] (entry)) |
Nem F/1 nem F/0 | <file>:<line>:<col>: entry function M:F/1 is not defined na declaração do módulo |
F/A explícita em falta | ... entry function M:F/A is not defined; found F/N numa definição com o mesmo nome, caso contrário na declaração do módulo |
| Só outras aridades | ... is not defined; found F/N, but the entry takes the argument list (FUNCTION/1) or nothing (FUNCTION/0) nessa definição |
F/1 (ou então F/0) não exportada | <file>:<line>:<col>: entry function M:F/A is not exported na definição |
Sem seleção, sem export de main/1 | no entry point: no module exports main/1; choose the entry with --entry MODULE[:FUNCTION[/ARITY]] (an exported FUNCTION/1, or FUNCTION/0 run without the program arguments; FUNCTION defaults to main, ARITY to 1, else 0) |
| Sem seleção, várias | ambiguous entry point: main/1 is exported by a, b; choose the entry with ... (a mesma sugestão) |
Para alvos de projeto, a sugestão indica também a chave do manifesto: ... --entry MODULE[:FUNCTION[/ARITY]] or with entry = "MODULE[:FUNCTION[/ARITY]]" in this target's [[targets]] table of the project manifest ..., por exemplo:
[[targets]]
name = "app"
sources = ["src/*.erl"]
entry = "app:start" # calls app:start/1 (or app:start/0); "app:start/0" calls app:start/0; plain "app" calls app:main/1
Argumentos
Entry(Argv) recebe uma lista própria de strings (listas de pontos de código
Unicode), excluindo o nome do programa e as
opções do runtime iniciais, de resto inalterados e pela
mesma ordem, como no escript.
- POSIX: os bytes de cada argumento são descodificados como UTF-8; um byte que não inicia uma sequência válida torna-se o ponto de código desse byte (alternativa Latin-1).
- Windows: o vetor de argumentos largo (UTF-16) do CRT, dividido pelas mesmas
regras que
argv; um surrogate isolado torna-se U+FFFD. - O runtime não faz qualquer outra análise de opções, expansão de padrões de ficheiros (globbing) ou expansão de variáveis de ambiente.
Opções do runtime
O runtime lê as suas opções da variável de ambiente CLAUSE_FLAGS (palavras
separadas por espaços e tabulações, sem aspas) e depois dos argumentos iniciais
da linha de comandos, pelo que a linha de comandos prevalece. Ambas são
analisadas da mesma forma; um valor vai no argumento seguinte ou depois de =.
| Opção | Efeito |
|---|---|
--max-atoms N | Tamanho da tabela de átomos, de 1 a 2^26 (67 108 864); por omissão 2^20 (1 048 576), como o +t do OTP |
--max-heap BYTES | Limite do bloco de heap, dos fragmentos e dos binaries off-heap de cada processo, pelo menos o heap mínimo (233 palavras); por omissão sem limite |
--max-stack BYTES | Limite da pilha de frames de cada processo; por omissão sem limite |
--min-heap-size WORDS | min_heap_size por omissão de todos os processos, pelo menos 1, arredondado por excesso para um tamanho de heap, como o +hms do OTP; no máximo --max-heap; por omissão 233 (dimensionamento) |
--max-memory BYTES | Limite da memória de todos os processos em conjunto (heaps, binaries off-heap, pilhas); por omissão sem limite |
--schedulers N | Workers do escalonador que executam processos, de 1 a 1024; por omissão um por CPU de --scheduler-cpus, ou então um por processador lógico em que o programa pode executar (a sua máscara de afinidade), como os escalonadores online do OTP (workers) |
--scheduler-cpus SET | Vincula o worker N ao N-ésimo CPU de SET, de forma circular, como o +sbt/+sct do OTP: performance (a classe de núcleos mais rápida: núcleos P de um CPU híbrido, todos os CPUs nos restantes casos), all, ou uma lista como 0-15 ou 2,4,6; cada CPU listado tem de estar na máscara de afinidade. Por omissão sem vinculação (vinculação a CPUs) |
--profile FILE | Escreve em FILE, quando o programa termina, um relatório das entradas e do tempo próprio por função e por processo (perfilagem); desativado por omissão |
--args-file FILE | Ficheiro de opções como o vm.args do OTP: reservado, indica runtime option --args-file is not implemented |
-- | Termina as opções do runtime; os argumentos seguintes vão todos para Entry |
Na linha de comandos, a análise para no primeiro argumento que não seja uma
opção do runtime, pelo que prog data --max-atoms 9 passa os três argumentos ao
programa. Em CLAUSE_FLAGS, cada palavra tem de ser uma opção do runtime. Um
valor inválido, uma palavra na variável que não seja uma opção, ou
--args-file param o programa antes de qualquer módulo ser registado:
clau: runtime failure: <reason>, código de saída 70. O número de processos
não é limitado e a memória, por omissão, não tem limite. Os valores em bytes
são decimais sem sufixos e são arredondados por defeito para palavras inteiras;
um programa que atinja um limite falha com resource_limit, código de saída 70
(limite de memória do runtime).
Código de saída
| Resultado | Código |
|---|---|
| A entrada retorna (qualquer valor) | 0 |
erlang:halt() | 0 |
erlang:halt(N), inteiro não negativo | N (os sistemas POSIX mantêm os 8 bits menos significativos) |
erlang:halt(Slogan) com uma string | Slogan no stderr, depois 1 (sem crash dump) |
erlang:halt(abort) | Abort nativo (sem esvaziar buffers) |
Qualquer exceção que escape da entrada, incluindo throw e exit(normal) | Relatório no stderr, 1 |
| Processo de entrada terminado por um sinal de saída (processos) | Indicado como um exit não capturado, 1; razão normal: 0 |
| Falha no arranque do runtime ou de infraestrutura (incompatibilidade de ABI, registo, memória antes da entrada) | Mensagem no stderr, 70 |
| Memória do anfitrião esgotada (crescimento do heap, de um binary off-heap ou da pilha recusado; sem limite de memória por omissão, esgotamento de memória) | clau: runtime failure: entry call failed: out_of_memory, 70 |
Argumentos inválidos de halt/1 lançam badarg no chamador. Quando a entrada
termina, o programa sai: os outros processos são parados sem continuarem a
correr, como acontece com o halt/1 do OTP depois de o escript retornar. Um
halt em qualquer processo termina o programa com o seu código, e uma falha do
runtime em qualquer processo termina-o com 70; uma exceção num processo lançado
com spawn termina apenas esse processo (processos).
halt/0,1, error/1,2,3, exit/1 e throw/1 podem ser chamadas com ou sem o
prefixo erlang:; uma definição local ou -compile({no_auto_import, ...})
mantém o nome não qualificado local, como no OTP. halt/2 não está disponível.
halt(N) mantém os 31 bits menos significativos de qualquer inteiro não
negativo, como faz o OTP. Um slogan é uma lista própria de no máximo 1023
pontos de código Unicode. Um halt desenrola a entrada através do canal de erros
verificado, tal como um erro, pelo que só para o programa depois da limpeza
gerada.
Fluxos de saída
- stdout: saída de
standard_io(io:format/1,2,io:put_chars/1,erlang:display/1). Com buffer; esvaziado em todos os caminhos de saída excetoabort. - stderr: o relatório de exceção não capturada, a saída de
standard_error, as falhas do runtime e os relatórios de erro de outros processos que falham (processos). - O relatório é uma linha
uncaught exception <class>: <reason in ~w form>, sem frames da pilha. O seu texto exato não é uma interface estável; os testes comparam-no por padrão.
Objeto de arranque
Compilar com uma entrada explícita (--entry ou entry do manifesto)
acrescenta um módulo de arranque depois dos módulos do lote. Com --emit, é
publicado como clausev1_start.{obj,o,ll,bc} ao lado dos artefactos dos
módulos (o nome não pode colidir com um artefacto de módulo). Contém um
abi::v1::StartupDescriptor constante
(startup.hpp): revisão da ABI,
largura dos termos, todos os descritores de módulos pela ordem do código-fonte,
as grafias do módulo/função de entrada e um indicador de escript. O seu
int main(int, char **) chama o CLAUSE_main_v1 do runtime, que:
- Verifica a revisão da ABI e a largura do descritor de arranque e de todos os descritores de módulos antes de qualquer registo; uma incompatibilidade sai com 70.
- Inicia o runtime e regista todos os módulos; qualquer falha para antes da entrada e descarta o runtime (código de saída 70), pelo que nenhum código Erlang corre sobre um lote parcial.
- Cria o processo de entrada, constrói o argv e coloca em fila a chamada de
M:F/1como processo principal, executando-o depois, juntamente com todos os processos que lança, nos workers do escalonador (processos) até terminar. - Converte o resultado no código de saída acima, imprimindo os relatórios
depois de esvaziar o stdout, e em seguida liberta todos os processos e
encerra o runtime em todos os caminhos (exceto
halt(abort)).
O clau -o liga estes objetos por si próprio (ligação). Ligação
manual (a
receita do harness nativo sem um
ficheiro-fonte de harness):
& $tool --emit obj --entry app --artifact-dir build/app app.erl helper.erl
clang-cl /MT build/app/*.obj build/debug/lib/clause_runtime.lib /Fe:app.exe
Qualquer ligação dos objetos com Clause::generated_program compatível com o
Clang funciona da mesma forma (ver tests/compiler/linking/startup.cmake).
Ligação
clau [-O0|-O2|-Os] -o PATH a.erl b.erl ... (ou
--project FILE [--target T] -o PATH para um alvo selecionado) compila o lote
em memória, acrescenta o objeto de arranque para a
entrada e liga um executável:
clau -O2 -o build/demo examples/compile/answer.erl examples/compile/client.erl
./build/demo # build/demo.exe on Windows
-Os coloca, além disso, cada função e objeto de dados gerados e do runtime na
sua própria secção e liga com --gc-sections (ELF), -dead_strip (Mach-O) ou
/OPT:REF /OPT:ICF (MSVC), pelo que o código que nenhum caminho de entrada
alcança é removido.
--lto compila os módulos e o módulo de arranque para bitcode LLVM em vez de
objetos e liga com -flto -fuse-ld=lld, pelo que o LLD otimiza o código gerado
do programa como uma única unidade em conjunto com o arquivo nativo do runtime.
Os descritores de módulos e de arranque, as entradas exportadas e main
continuam referenciados e sobrevivem; os programas comportam-se como sem a
opção. Só o LLD lê o bitcode deste compilador, pelo que --lto é aceite para
alvos Windows MSVC e ELF; os outros falham antes da ligação com
--lto is not supported for target <triple>: it needs LLD. --lto exige uma
saída executável (-o ou uma compilação de projeto com ligação). O Clang/LLD
instalado tem de ser da mesma versão do LLVM que o clau (23.1).
Vivacidade dos exports
Um executável deixa de fora os exports que não consegue alcançar, pelo que a
eliminação de código morto de -Os e --lto removem o código de biblioteca
que o programa nunca chama. Logo após a resolução das chamadas, antes da
inferência de tipos, uma análise de todo o lote começa na função de entrada e
marca como vivo tudo o que uma função viva chama diretamente (também através de
-import e dos auto-imports do OTP), cada fun F/A e fun M:F/A literal, e
os alvos literais de apply/3, spawn/3, spawn_link/3, spawn_monitor/3,
spawn_opt/4,5, spawn_request/3,5, hibernate/3 e make_fun/3 (todas as
aridades de M:F quando a lista de argumentos não é literal), e file:write/2
assim que io:format/3, io:fwrite/3, io:put_chars/2 ou io:nl/1 estiver
vivo (os seus builtins escrevem através dele para um dispositivo que é um
processo). A análise é conservadora:
- um módulo cujo átomo de nome é usado como valor em qualquer ponto do código
vivo (passado, guardado, enviado,
?MODULE, uma etiqueta de record,M:F(...)com uma função determinada em tempo de execução,fun M:F/Acom uma parte determinada em tempo de execução) mantém todos os exports, o que também mantém os callbacks dos módulos de behaviour iniciados pelo nome; - assim que o código vivo tenha simultaneamente uma chamada com um módulo
determinado em tempo de execução (
M:F(...),apply/3ou um spawn com um módulo variável,fun M:F/Acom um módulo variável,binary_to_term/1,2) e um builtin que produz átomos (list_to_atom/1,list_to_existing_atom/1,binary_to_atom/1,2,binary_to_existing_atom/1,2,binary_to_term/1,2,fun_info/1,2,fun_info_mfa/1,process_info/1,2,loaded/0,pre_loaded/0), todos os exports de todos os módulos ficam; - um módulo compilado com
{clause_liveness, strict}garante que os seus átomos nunca designam código chamado: as suas etiquetas de record e os literais de átomo que não sejam o seu próprio nome (mas nãoM:F(...)ouapply/3com um módulo literal e uma função determinada em tempo de execução), os builtins que produzem átomos ebinary_to_term/1,2não contam para nenhuma das regras (vivacidade estrita).
Uma função que nada vivo alcança não recebe qualquer código. Um export deixado
de fora mantém o seu nome na tabela de exports do módulo sem código, pelo que
function_exported/3 e module_info(exports) respondem como se estivesse
exportado, enquanto uma chamada dinâmica que o alcance lança undef
(diferenças). Isso só pode
acontecer quando um nome de módulo vem de fora da análise: um rastreio de
pilha, um termo de exceção ou dados de fora do programa. --verbose regista o
resultado por módulo (phase=liveness detail="dropped-exports=3", ou
detail="kept-all-exports reason='m' is used as a value in main:main/1").
Uma função que nada vivo alcança também nunca tem os tipos inferidos nem é
verificada em relação à sua -spec: a inferência cobre apenas o código vivo, o
que é mais rápido para programas que usam pouco de grandes módulos de
biblioteca e só pode estreitar os factos das funções vivas, uma vez que os
chamadores inalcançáveis deixam de juntar os seus argumentos. Uma
especificação contrariada apenas em código inalcançável é, por isso, comunicada
para os artefactos --emit, mas não para o executável.
--keep-exports (ou a opção de projeto keep_exports = true) mantém todos os
exports chamáveis. A análise aplica-se aos executáveis (-o ou uma compilação
de projeto com ligação) e às inspeções de IR e de assembly com uma entrada
(--entry ou o entry do manifesto do alvo), que mostram então o código do
executável; os artefactos --emit, destinados a outros programas, mantêm todos
os exports.
-g mantém as tabelas de linhas Erlang para os depuradores: no executável
(ELF), ou num <name>.pdb publicado ao lado dele (alvos MSVC)
(depuração).
PATHé relativo à invocação. Para alvos Windows,.exeé acrescentado quando o nome do ficheiro não tem extensão. O seu diretório tem de existir.- Ligador (linker):
--linker PATH(um caminho ou nome de programa), caso contrárioclang++ouclanga partir doPATH, depois (Windows)%ProgramFiles%/LLVM/bin. É executado como<clang> --driver-mode=g++ --target=<triple> -o <staged> <objects> <runtime>, pelo que o Clang escolhe o ligador da plataforma e as bibliotecas de runtime de C/C++ (no Windows localiza ele próprio o MSVC e o SDK; não é necessária uma developer shell). - Runtime:
--runtime-library PATH, caso contrário o arquivoclause_runtimeda compilação que produziu oclau(caminho registado em relação ao executável, p. ex.bin/../lib/clause_runtime.lib). Cada objeto nativo no arquivo tem de corresponder à arquitetura e ao formato de objeto do alvo;--target-triplepara outro alvo precisa, portanto, de um runtime compilado para ele. - Os objetos e o executável são preparados num diretório privado
.clause-link-*ao lado da saída, que é removido no fim; fica aí, e não no diretório de compilação, porque a mudança de nome final não pode atravessar volumes. A saída só é substituída depois de uma ligação bem-sucedida, pelo que qualquer falha mantém inalterado um ficheiro existente. A saída não pode ser um diretório nem um alias de uma entrada. - Os avisos do ligador são reencaminhados para o stderr;
--linkere--runtime-libraryexigem--outputou uma compilação de projeto com ligação. - As compilações de projeto sem
-oligam cada alvo selecionado que tenhaoutputouentryà saída do seu manifesto, criando os diretórios em falta, e só substituem as saídas depois de todos os alvos selecionados terem sido ligados (projetos).
| Falha (código de saída 1) | Diagnóstico |
|---|---|
| Sem Clang | cannot find clang++ or clang on PATH; install LLVM/Clang or pass --linker / linker not found: X |
| Sem runtime | runtime library not found: P; build the clause_runtime target or pass --runtime-library |
| Não é um arquivo | runtime library is not a static library: P: ... |
| Alvo errado | runtime library P contains x86_64 coff objects, but the executable targets T; ... |
| Erro de ligação | linking O failed: <clang> exited with status N: seguido da saída do ligador (primeiros 64 KiB) |
| Destino inválido | output directory does not exist: D, artifact destination is not a regular file: O, artifact destination aliases an input: O |
Escripts
Um ficheiro-fonte cuja primeira linha começa por #! é compilado como escript,
em qualquer modo e com qualquer nome de ficheiro (entradas posicionais ou
ficheiros .erl de projeto). As regras seguem o escript do OTP 29 para
scripts em código-fonte:
- A linha
#!é ignorada. Um comentário opcional na linha 2 e uma linha de emulador%%!(linha 2, ou linha 3 depois do comentário) são comentários; os argumentos de%%!não se podem aplicar a código compilado e produzem um aviso. - Se a primeira forma não for
-module(...), o módulo é<file name with '.' replaced by '_'>__escript(incluindo?MODULE). O OTP acrescenta um sufixo de timestamp/único; o Clause mantém o nome determinístico. A declaração sintetizada ocupa a linha 1, pelo que os números das linhas seguintes não se alteram. main/1é obrigatória (escript does not define main/1) e exportada implicitamente; as outras funções seguem as regras normais de export.-mode(compile | interpret | debug | native)é aceite e ignorado; outros valores são erros. Fora dos escripts,-modecontinua sem suporte.- Entrada: sem
--entry, o único escript do lote é a entrada (prevalece sobre os módulos que exportammain/1); vários escripts são ambíguos. - Código de saída como no
escriptdo OTP: uma exceção que escape da entrada do escript sai com 127 eescript: exception <class>: <reason>no stderr; as outras linhas da tabela de códigos de saída aplicam-se sem alterações. - Os ficheiros sem
#!são módulos comuns. (Oescript file.erldo OTP saltaria a sua primeira linha; o Clause não o faz.) Os escripts beam pré-compilados e em arquivo não são suportados.
Comparação com o OTP
Os goldens das fixtures de programas
executam a entrada no OTP com as mesmas regras
(oráculo); o mesmo oráculo gera os
casos golden de executáveis que o
executor de ponta a ponta liga e executa sob todas as políticas. Diferenças em
relação ao escript para módulos comuns: as exceções não capturadas saem com 1
em vez de 127, e main/1 tem de ser exportada. Os ficheiros-fonte de escripts
mantêm as regras do OTP (ver acima).
Clause