Clause
← Toda a documentação

O original em inglês mudou desde esta tradução, pelo que partes dela podem estar desatualizadas. Ler em inglês

Traduzido do original em inglês · 1e3a39c8-dirty · 2026-10-26

ABI do código gerado (revisão 21)

Contrato privado entre a saída do compilador e o runtime. É C++23 interno ao projeto, não compatível com a BEAM e não é uma FFI de uso geral. Os objetos e o runtime têm de provir da mesma compilação; revisões de descritores mais antigas são rejeitadas antes da utilização.

Cabeçalhos: v1.hpp (tipos de termo, contexto e função), term.hpp (codec de inteiros imediatos), status.hpp, builtins.hpp, startup.hpp (arranque do programa).

Termos

Um termo é uma palavra sem sinal com a largura de um ponteiro do alvo (32 ou 64 bits). As disposições derivam do alvo LLVM configurado, pelo que o código para outro alvo usa as larguras desse alvo.

Funções e símbolos

Assinatura de entrada (convenção de chamada C nativa, sem exigir ligação C):

TermWord function(ProcessContext *context, const TermWord *arguments);

Os argumentos são um array emprestado, alinhado à palavra, pela ordem do código-fonte (nulo com aridade 0). O contexto está vivo e é passado inalterado através das chamadas. Uma palavra devolvida só é utilizável depois de verificar o canal de falhas.

Símbolos: clausev1_<hex module>_<hex function>_<arity>, hexadecimal em minúsculas dos bytes UTF-8, aridade decimal canónica; reversíveis e independentes do anfitrião. As entradas exportadas são externas, as restantes internas.

Registo de módulos

Cada módulo emite clausev1_<hex module>__0.descriptor e .register. O descritor contém a versão da ABI, a largura do termo, a tabela de exportações (nome, aridade, entrada do anfitrião e o FrameDescriptor em que entram as chamadas dinâmicas), as grafias dos átomos (pares ponteiro/tamanho UTF-8), os descritores de native records (slots de átomos do módulo, do nome e dos campos, flag de exportação; native records) numa tabela externa <prefix>.records que os outros módulos de um lote referenciam, os descritores de funs numa tabela privada <prefix>.funs (funs), os literais codificados do módulo e a cache de literais (áreas), as caches graváveis de átomos e de funs que o código gerado lê inline, e a code_convention dos seus corpos. O registo chama CLAUSE_register_module_v4(Runtime*), que valida a versão/largura e todas as exportações, interna os átomos, constrói um registo congelado e publica-o juntamente com a imagem do código numa única transação. Módulos duplicados nunca substituem código; qualquer falha não publica nada (os átomos já internados permanecem na tabela limitada).

Canal de falhas (revisão 2)

Os erros não são codificados nos bits do termo. Após cada chamada gerada que não seja de cauda, o chamador verifica o canal antes de usar o resultado ou de avaliar o argumento seguinte: lê a palavra failed do FrameState do contexto, que o runtime mantém igual a CLAUSE_call_failed_v2(context). Em caso de falha, o chamado devolve uma palavra zero inválida.

ResultadoTransporte
Padrão não corresponde, rejeição pela guardContinuação para o candidato seguinte; canal intocado
Cláusulas esgotadaserror:function_clause; error:{case_clause, Value} com carga útil própria para um case; error:if_clause; error:{try_clause, Value} para as cláusulas of de um try; error:{else_clause, Value} para as cláusulas else de um maybe
Falha de correspondência no corpoerror:{badmatch, Value} com carga útil própria
Comprehensionserror:{bad_generator, Tail}, error:{bad_filter, Value}, error:{bad_generators, Inputs} (ErrorReason 16-18); a rejeição de um gerador estrito é {badmatch, Element}
Acesso a records, argumentos inválidos, aritmética, mapsbadrecord, badarg, badarith, badmap/badkey
Campo de native record em faltaErrorReason::badfield (20), carga útil {{Module, Name}, Field}
Chamar um valor (F(Args))ErrorReason::badfun (22, carga útil o valor), badarity (23, carga útil {Fun, Args}), undef (24), registados por CLAUSE_apply_v1 (funs)
Chamadas dinâmicas (M:F(Args), apply/2,3, fun M:F/A com variáveis)badarg para um módulo ou função que não seja átomo, uma lista de argumentos imprópria ou uma aridade inválida; undef quando nenhum módulo do programa exporta a função; badfun/badarity como acima (funs)
Construção nativa externa sem valorErrorReason::novalue (21), carga útil {{Module, Name}, Field}
Resultado inteiro acima do limite de tamanhoResultado de serviço ValueOutcome::system_limit (3): uma guard rejeita, um corpo lança error:system_limit (ErrorReason 19)
Operando esquerdo preguiçoso inválido{badarg, Value}
Infraestrutura (falta de memória, limites, posse, interno)CallError::runtime_failure com o Status exato
erlang:error/1,2,3, exit/1, throw/1, erlang:raise/3raised_error/raised_exit/raised_throw: a classe vem do ID, a carga útil própria é a razão completa
erlang:halt/0,1CallError::halted com halt_status (e slogan)

As razões são IDs tipados registados por CLAUSE_raise_v2; os três IDs raised_* selecionam a classe exit ou throw (caso contrário error) e transportam qualquer termo como razão. error/2,3 lançam através de CLAUSE_error_v1(context, reason, args), que também guarda uma lista args para o frame de topo da pilha. A primeira falha prevalece; as invocações aninhadas partilham o canal. GeneratedInvocation é o âmbito do anfitrião: verifica falhas pendentes antes da entrada e após o retorno, copia o resultado ou o erro e limpa apenas na saída mais exterior (também em exceções C++). Nenhuma exceção atravessa entradas geradas. Quem chama entradas em bruto tem de abrir um GeneratedInvocation; os anfitriões normais usam ResolvedFunction::call.

catch Expr redireciona todas as verificações de falha e lançamentos dentro de Expr para um bloco de tratamento que chama CLAUSE_catch_v1(context, slot). Para uma exceção Erlang pendente, escreve o valor do catch no slot de raiz e limpa o canal: o termo lançado, {'EXIT', Reason} para um exit, ou {'EXIT', {Reason, []}} para um error (as razões tipadas tornam-se os seus termos OTP, como {badmatch, V}; a pilha é descrita abaixo). Os halts e as falhas de infraestrutura permanecem pendentes, e a verificação do próprio bloco de tratamento continua para o bloco de tratamento envolvente ou para a saída da função. As vinculações feitas dentro de Expr são inseguras depois disso, pelo que a junção apenas funde o valor.

try Body of ... catch ... end protege apenas Body da mesma forma. O seu bloco de tratamento chama CLAUSE_exception_v2(context, class_slot, reason_slot, stack_slot), que escreve o átomo da classe (error, exit ou throw), a razão e o termo do rastreio de pilha em slots de raiz e limpa o canal (os halts e as falhas de infraestrutura permanecem pendentes, tal como no catch). As cláusulas catch fazem então corresponder Class:Reason com padrões e guards comuns; uma classe omitida corresponde a throw, e uma variável de pilha nomeada vincula o termo da pilha. Quando nenhuma corresponde, CLAUSE_reraise_v2(context, class, reason, stack) regista novamente a exceção com uma razão raised_* e a mesma pilha, que é relatada e capturada exatamente como a original. As cláusulas of selecionam pelo valor do corpo e lançam {try_clause, Value} (ErrorReason::try_clause = 14); as exceções dentro das cláusulas of e dos blocos de tratamento vão para o bloco de tratamento envolvente.

maybe não precisa de nenhum serviço: cada ?= é uma correspondência comum cuja aresta de não correspondência sai do corpo para a saída do maybe com o valor não correspondido (já enraizado). Sem else, esse valor é o resultado; caso contrário, as cláusulas else selecionam por ele como as cláusulas case e lançam {else_clause, Value} (ErrorReason::else_clause = 15) quando nenhuma corresponde.

As comprehensions usam os serviços existentes com algumas operações: os elementos acumulados são invertidos pela construção reverse (ContainerConstruction::reverse = 2, valores {List, Tail}), que constrói o resultado numa única reserva de heap; os geradores de lista testam e leem células cons inline; uma binary comprehension junta as suas partes com BitOperation::concat ({List}) e uma map comprehension constrói o seu map com MapOperation::from_list ({Pairs}, as chaves posteriores prevalecem). Os geradores de map obtêm MapOperation::entries (11, {Map}) uma vez: uma célula com as chaves e os valores nas palavras 1 + 2i e 2 + 2i na ordem canónica das chaves (o próprio map clássico, ou uma cópia em forma de tuplo das entradas de um map fora do heap que só o código gerado lê) e leem cada entrada inline; key_at/value_at ({Map, Position}) mantêm-se para outros chamadores. A entrada restante de um zip é mostrada com MapOperation::iterator, a cadeia {K, V, Next} do OTP que termina em none. MapOperation::update_owned (10) recebe os operandos de update para uma atualização de map cuja origem o compilador provou não ser lida depois: um map de origem fora do heap é atualizado no local e o seu stub fica consumido (vazio). Os geradores de bitstring usam a extração de padrões comum mais um segmento final binary/all para o resto.

try ... after A end acrescenta uma segunda proteção em torno do corpo e de todas as cláusulas of e catch. No caminho normal, A é executado depois de o valor selecionado ser enraizado, e o seu valor é descartado. O bloco de tratamento after obtém a exceção com CLAUSE_exception_v2, executa uma segunda cópia de A e volta a lançar com CLAUSE_reraise_v2; uma exceção ou falha dentro de A sai, em vez disso, pelo bloco de tratamento envolvente, substituindo a original. Os halts e as falhas de infraestrutura saltam A. Os slots de raiz pertencem ao frame da função, pelo que todos os caminhos os libertam na saída da função.

Rastreios de pilha

Cada frame de raiz nomeia a sua função gerada com um abi::v1::FrameDescriptor privado (descritor do módulo, slots dos átomos do nome do módulo e da função, aridade). Quando uma exceção Erlang é registada, o canal copia os 8 frames nomeados mais interiores (o backtrace_depth predefinido da BEAM); o termo [{Module, Function, Arity, []}, ...] só é construído quando um bloco de tratamento, um catch ou um relatório o pede. O frame de topo mostra a lista de argumentos de error/2,3 em vez da aridade quando esse argumento é uma lista.

erlang:raise(Class, Reason, Stack) (CLAUSE_reraise_v2) aceita as pilhas que a BEAM aceita: uma lista própria de {M, F, A} (completada com uma localização []) ou {M, F, A, Location} com M, F átomos e uma lista Location, cortada a 8 entradas; a pilha é então mantida tal como foi dada e os frames deixam de ser capturados. Uma classe ou pilha inválida não regista nada e a chamada avalia para badarg, como no OTP. erlang:get_stacktrace/0 é rejeitado com o texto de lint "removed" do OTP 29.

Diferenças em relação ao OTP, todas visíveis apenas no termo da pilha:

Frames e transferências

As funções geradas são executadas em frames explícitos (modelo de execução; serviços em frames.hpp). Cada função tem um descritor <symbol>.frame (FrameDescriptor: descritor do módulo, slots dos átomos do módulo e da função, aridade, código do corpo, número de slots, número de slots de termos; externo para funções exportadas) e um <symbol>.body interno do tipo abi::v1::Code, void(void *context, TermWord x0, ...) com abi::v1::register_arguments parâmetros de termo; com um runtime compilado com Clang em x86-64 e AArch64, os corpos e todas as transferências usam a convenção preserve_none, tal como abi::v1::Code nesses alvos: os corpos nunca regressam normalmente, pelo que não guardam registos preservados pelo chamado; o registo rejeita um módulo cuja code_convention (0 nenhuma, 1 C, 2 preserve_none) não seja a do runtime. Um <symbol> exportado mantém a assinatura TermWord(Context *, const TermWord *) como entrada do anfitrião que chama CLAUSE_invoke_v1(context, frame, arguments).

Serviços do runtime

O código gerado chama serviços C++ verificados: CLAUSE_exact_v1 (igualdade exata), CLAUSE_immediate_v1 (predicados/consultas sobre imediatos), CLAUSE_construct_v1, CLAUSE_inspect_v1 (para outros consumidores; o código gerado testa e lê células de tuplos e cons inline), CLAUSE_integer_v1, CLAUSE_float_v1, CLAUSE_map_v1, CLAUSE_bits_v1, CLAUSE_record_v1 (make/get/update/match/test de native records sob um RecordCheck; resultados bad_record, bad_field, no_match), CLAUSE_make_fun_v1 (constrói uma fun a partir de um FunDescriptor). Cada um devolve sucesso, erro semântico (badarg/badarith/...) ou falha de infraestrutura e só escreve a saída em caso de sucesso. CLAUSE_display_v1 (output.hpp) imprime uma linha de erlang:display/1 e produz true; não tem erro semântico. CLAUSE_halt_v1 nunca tem sucesso: regista um pedido de halt (CallError::halted com o código de saída) ou badarg, pelo que o chamador desenrola. As grafias para o ligador seguem o mangling C++ Itanium ou Microsoft do alvo.

Formas inline (codegen/lowering_inline.cpp): os testes de tipo (is_atom, is_integer, is_number, is_boolean, is_tuple, is_list, is_bitstring, is_float, is_map, is_pid, is_port, is_reference, is_function/1, is_record/1 nativo, is_binary/1 pela palavra do comprimento em bits) leem as tags e, depois de um teste de tag boxed, o tipo do cabeçalho (abi::v1::HeaderKind); nunca chamam um serviço. As comparações de dois imediatos (==, /=) ou de dois inteiros pequenos (<, =<, >, >=), element/2, tuple_size/1, bit_size/1 e byte_size/1 (a partir da palavra do comprimento em bits, enquanto o tamanho é um inteiro pequeno), hd/1, tl/1, os operadores booleanos, band, bor, bxor, bsr, bsl (enquanto o resultado se mantiver pequeno) e div/rem pequenos correm inline; self/0 lê FrameState::self. Correm inline quando os seus operandos o permitem e chamam CLAUSE_immediate_v1 caso contrário, que mantém todos os casos de erro e de bignum. setelement/3 de um índice pequeno dentro de um tuplo boxed copia o tuplo para as palavras livres do bloco de heap e substitui o slot inline; caso contrário, ou sem espaço, entra no builtin de ponte.

CLAUSE_builtin_frame_v1(context, builtin) (builtins.hpp) devolve o FrameDescriptor do builtin de produção com o índice builtin em abi::v1::bridge_builtins (apenas acrescentado); o código gerado entra nele como numa função, com os argumentos nos registos, e os erros e as falhas vão para o canal verificado (builtins). Um FrameDescriptor com corpo nulo é um builtin: entrar nele executa o builtin sobre os registos e regressa ao chamador, ou suspende o processo na continuação do builtin quando este fez trap (porções).

Um builtin direto (BuiltinCall::direct: conversões puras como integer_to_list/1, atom_to_list/1, list_to_tuple/1, setelement/3, phash2/1) nunca faz trap, nunca espera nem atua sobre outro processo. O código gerado chama-o com CLAUSE_builtin_call_v1(context, builtin, arguments), que executa o seu corpo de imediato e devolve o resultado, sem transferência de frame, sem ponto de retoma e sem safepoint de entrada; gasta uma redução, e uma falha vai para o canal verificado tal como a de um serviço. Um builtin rápido (BuiltinCall::quick: length/1, binary_to_list/1, list_to_binary/1, iolist_to_binary/1) é chamado primeiro da mesma forma; o seu caminho rápido limitado devolve 0 quando recusa (entradas mais longas, outras formas, todos os erros e sempre que as chamadas são rastreadas), e o código gerado entra então no frame do builtin como para qualquer builtin de ponte.

abi::v1::dispatch_builtin chama builtins registados pelo anfitrião através dos bytes do módulo/função, do array de argumentos e da aridade, devolvendo um Status; a saída só é escrita em caso de sucesso.

StatusValorSignificado
ok0Sucesso
not_implemented1Funcionalidade adiada conhecida, relatada uma vez
invalid_argument2Entrada inválida
diagnostic_failure3A entrega do relatório falhou
out_of_memory4A alocação falhou, estado revertido
busy5Contextos vivos ou despacho em execução
wrong_owner6O objeto pertence a outro runtime/contexto
resource_limit7Limite ou orçamento esgotado
stopped8O proprietário foi encerrado
abi_mismatch9A versão ou a largura do termo difere
internal_error10Falha inesperada contida
unknown_builtin11Assinatura não registada nem catalogada
erlang_error12Erro Erlang estruturado registado
output_failure13A saída padrão rejeitou uma escrita

Revisões

RevisãoAlteração
1Descritores e entradas iniciais
2Canal de falhas verificado
3Grafias e slots dos átomos nos descritores
4Âmbitos de raiz gerados obrigatórios
5Frames de processo e transferências explícitos
6Descritores de native records nos descritores de módulo, CLAUSE_record_v1
7Descritores de funs nos descritores de módulo, CLAUSE_make_fun_v1, CLAUSE_apply_v1
8Os descritores de exportação nomeiam o seu FrameDescriptor; serviços de chamada dinâmica CLAUSE_call_v1, CLAUSE_apply_list_v1, CLAUSE_call_list_v1, CLAUSE_make_external_fun_v1
9Cada contexto começa com abi::v1::FrameState; transferências inline
10Registos X iniciais passados como parâmetros do código
11Palavras fixas de false e true
12Campos de heap inline heap_top/heap_limit em FrameState
13undefined, ok, error fixos; ModuleDescriptor::atom_cache e FrameState::runtime
14Áreas de literais dos módulos: ModuleDescriptor::literals, literal_words
15FrameState::self, a palavra do pid do contexto
16Code usa preserve_none em x86-64 e AArch64 com um runtime compilado com Clang; ModuleDescriptor::code_convention
17Seis registos X como parâmetros de Code
18Células sub_binary (HeaderKind::sub_binary, sub_*_word) e extração inline de Rest
19Palavras de fim e de limite de acrescento nas células refc_binary (refc_append_*_word, 8 palavras de carga útil) e acrescentos inline
20MapOperation::entries e geradores de map inline
21ModuleDescriptor::fun_cache e closures inline
22Builtins diretos: BuiltinName::direct, CLAUSE_builtin_call_v1
23Builtins rápidos: BuiltinName::call (BuiltinCall), 0 de CLAUSE_builtin_call_v1 indica recusa
24RecordDescriptor::definition (a lista de campos como bytes do External Term Format, para records:get_definition/2) e ModuleDescriptor::features (slots de átomos das funcionalidades ativadas, para erl_features:used/1)
25Literais codificados do módulo e o heap de literais: ModuleDescriptor::literals contém bytes codificados (literal_bytes), literal_cache, literal_count; CLAUSE_literal_v1