Clause
← Toda la documentación

Traducido del original en inglés · 06042fa · 2026-10-09 · Leer en inglés

ABI del código generado (revisión 4)

Contrato privado entre la salida del compilador y el runtime. Es C++23 interno del proyecto, no es compatible con BEAM ni es una FFI de propósito general. Los objetos y el runtime deben proceder de la misma compilación; las revisiones anteriores de los descriptores se rechazan antes de usarse.

Cabeceras: v1.hpp (tipos de término, contexto y función), term.hpp (códec de enteros inmediatos), status.hpp, builtins.hpp, startup.hpp (arranque del programa).

Términos

Un término es una palabra sin signo del ancho de puntero del objetivo (32 o 64 bits). Las disposiciones se derivan del objetivo LLVM configurado, por lo que el código entre objetivos distintos usa los anchos del objetivo.

Funciones y símbolos

Firma de entrada (convención de llamada nativa de C, no requiere enlace C):

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

Los argumentos son un array prestado y alineado a palabra, en el orden del código fuente (nulo con aridad 0). El contexto está vivo y se pasa sin cambios a través de las llamadas. Una palabra devuelta solo es utilizable después de comprobar el canal de fallos.

Símbolos: clausev1_<hex module>_<hex function>_<arity>, hexadecimal en minúsculas de los bytes UTF-8, aridad decimal canónica; reversibles e independientes del host. Las entradas exportadas son externas y las demás, internas.

Registro de módulos

Cada módulo emite clausev1_<hex module>__0.descriptor y .register. El descriptor contiene la versión de la ABI, el ancho de término, la tabla de exportaciones (nombre, aridad, entrada del host y el FrameDescriptor en el que entran las llamadas dinámicas), las grafías de los átomos (pares de puntero/tamaño UTF-8), los descriptores de native record (slots de átomos de módulo, nombre y campos, flag de exportación; native records) en una tabla externa <prefix>.records a la que hacen referencia otros módulos de un lote, y los descriptores de funs en una tabla privada <prefix>.funs (funs). El registro llama a CLAUSE_register_module_v4(Runtime*), que valida la versión/ancho y todas las exportaciones, interna los átomos, construye un registro congelado y lo publica junto con la imagen de código en una sola transacción. Los módulos duplicados nunca reemplazan código; cualquier fallo no publica nada (los átomos ya internados permanecen en la tabla acotada).

Canal de fallos (revisión 2)

Los errores no se codifican en los bits del término. Después de cada llamada generada que no es de cola, el llamador comprueba CLAUSE_call_failed_v2(context) antes de usar el resultado o de evaluar el siguiente argumento. En caso de fallo, el llamado devuelve una palabra cero inválida.

ResultadoTransporte
Patrón que no coincide, rechazo de guardContinuación al siguiente candidato; canal intacto
Cláusulas agotadaserror:function_clause; error:{case_clause, Value} con carga útil propia para un case; error:if_clause; error:{try_clause, Value} para las cláusulas of de un try; error:{else_clause, Value} para las cláusulas else de un maybe
Fallo de coincidencia en el cuerpoerror:{badmatch, Value} con carga útil propia
Comprehensionserror:{bad_generator, Tail}, error:{bad_filter, Value}, error:{bad_generators, Inputs} (ErrorReason 16-18); el rechazo de un generador estricto es {badmatch, Element}
Acceso a records, argumentos incorrectos, aritmética, mapsbadrecord, badarg, badarith, badmap/badkey
Campo ausente en un native recordErrorReason::badfield (20), carga útil {{Module, Name}, Field}
Llamada a un valor (F(Args))ErrorReason::badfun (22, la carga útil es el valor), badarity (23, carga útil {Fun, Args}), undef (24), registrados por CLAUSE_apply_v1 (funs)
Llamadas dinámicas (M:F(Args), apply/2,3, fun M:F/A con variables)badarg para un módulo o función que no es un átomo, una lista de argumentos impropia o una aridad inválida; undef cuando ningún módulo del programa exporta la función; badfun/badarity como arriba (funs)
Construcción nativa externa sin un valorErrorReason::novalue (21), carga útil {{Module, Name}, Field}
Resultado entero que supera el límite de tamañoResultado de servicio ValueOutcome::system_limit (3): un guard rechaza, un cuerpo lanza error:system_limit (ErrorReason 19)
Operando izquierdo perezoso inválido{badarg, Value}
Infraestructura (OOM, límites, propiedad, interno)CallError::runtime_failure con el Status exacto
erlang:error/1,2,3, exit/1, throw/1, erlang:raise/3raised_error/raised_exit/raised_throw: la clase viene del ID, la carga útil propia es el motivo completo
erlang:halt/0,1CallError::halted con halt_status (y eslogan)

Los motivos son IDs tipados registrados por CLAUSE_raise_v2; los tres IDs raised_* seleccionan la clase exit o throw (en otro caso error) y llevan cualquier término como motivo. error/2,3 lanzan a través de CLAUSE_error_v1(context, reason, args), que además conserva una lista args para el marco superior de la pila. Gana el primer fallo; las invocaciones anidadas comparten el canal. GeneratedInvocation es el ámbito del host: comprueba los fallos pendientes antes de la entrada y después del retorno, copia el resultado o el error y limpia solo en la salida más externa (también ante excepciones de C++). Ninguna excepción atraviesa las entradas generadas. Quienes llamen a entradas en bruto deben abrir un GeneratedInvocation; los hosts normales usan ResolvedFunction::call.

catch Expr redirige toda comprobación de fallo y todo lanzamiento dentro de Expr a un bloque manejador que llama a CLAUSE_catch_v1(context, slot). Para una excepción de Erlang pendiente, escribe el valor del catch en el slot raíz y limpia el canal: el término lanzado, {'EXIT', Reason} para un exit, o {'EXIT', {Reason, []}} para un error (los motivos tipados se convierten en sus términos de OTP, como {badmatch, V}; la pila se describe más abajo). Los halts y los fallos de infraestructura siguen pendientes, y la propia comprobación del manejador continúa hacia el manejador envolvente o la salida de la función. Las vinculaciones hechas dentro de Expr no son seguras después, por lo que la unión solo combina el valor.

try Body of ... catch ... end protege solo Body de la misma manera. Su manejador llama a CLAUSE_exception_v2(context, class_slot, reason_slot, stack_slot), que escribe el átomo de clase (error, exit o throw), el motivo y el término de la traza de pila en slots raíz y limpia el canal (los halts y los fallos de infraestructura siguen pendientes, como con catch). Las cláusulas catch hacen coincidir entonces Class:Reason con patrones y guards ordinarios; una clase omitida coincide con throw, y una variable de pila con nombre se vincula al término de pila. Cuando ninguna coincide, CLAUSE_reraise_v2(context, class, reason, stack) registra de nuevo la excepción con un motivo raised_* y la misma pila, que se notifica y se captura exactamente igual que la original. Las cláusulas of seleccionan sobre el valor del cuerpo y lanzan {try_clause, Value} (ErrorReason::try_clause = 14); las excepciones dentro de las cláusulas of y de los manejadores van al manejador envolvente.

maybe no necesita ningún servicio: cada ?= es una coincidencia ordinaria cuya arista de no coincidencia sale del cuerpo hacia la salida del maybe con el valor no coincidente (ya enraizado). Sin else, ese valor es el resultado; en otro caso, las cláusulas else seleccionan sobre él como las cláusulas de case y lanzan {else_clause, Value} (ErrorReason::else_clause = 15) cuando ninguna coincide.

Las comprehensions usan los servicios existentes con unas pocas operaciones: los elementos acumulados se invierten mediante la construcción reverse (ContainerConstruction::reverse = 2, valores {List, Tail}); una comprehension de binary une sus piezas con BitOperation::concat ({List}) y una comprehension de map construye su map con MapOperation::from_list ({Pairs}, las claves posteriores ganan). Los generadores de map leen MapOperation::key_at/value_at ({Map, Position} en el orden canónico de claves) y muestran la entrada restante de un zip con MapOperation::iterator, la cadena {K, V, Next} de OTP que termina en none. Los generadores de bitstring usan la extracción de patrones ordinaria más un segmento final binary/all para el resto.

try ... after A end añade una segunda protección alrededor del cuerpo y de todas las cláusulas of y catch. En el camino normal, A se ejecuta después de enraizar el valor seleccionado y su valor se descarta. El manejador de after toma la excepción con CLAUSE_exception_v2, ejecuta una segunda copia de A y vuelve a lanzar con CLAUSE_reraise_v2; una excepción o un fallo dentro de A sale en cambio por el manejador envolvente, sustituyendo a la original. Los halts y los fallos de infraestructura omiten A. Los slots raíz pertenecen al marco de la función, por lo que todos los caminos los liberan en la salida de la función.

Trazas de pila

Cada marco raíz nombra su función generada con un abi::v1::FrameDescriptor privado (descriptor de módulo, slots de átomos de nombre de módulo y de función, aridad). Cuando se registra una excepción de Erlang, el canal copia los 8 marcos con nombre más internos (el backtrace_depth predeterminado de BEAM); el término [{Module, Function, Arity, []}, ...] solo se construye cuando un manejador, un catch o un informe lo solicita. El marco superior muestra la lista de argumentos de error/2,3 en lugar de la aridad cuando ese argumento es una lista.

erlang:raise(Class, Reason, Stack) (CLAUSE_reraise_v2) acepta las pilas que acepta BEAM: una lista propia de {M, F, A} (completados con una ubicación []) o {M, F, A, Location} con M, F átomos y una lista Location, recortada a 8 entradas; la pila se conserva entonces tal como se da y ya no se capturan marcos. Una clase o pila inválida no registra nada y la llamada se evalúa como badarg, igual que en OTP. erlang:get_stacktrace/0 se rechaza con el texto de lint "removed" de OTP 29.

Diferencias respecto a OTP, todas visibles solo en el término de pila:

Marcos y transferencias

Las funciones generadas se ejecutan sobre marcos explícitos (modelo de ejecución, step 19; servicios en frames.hpp). Cada función tiene un descriptor <symbol>.frame (FrameDescriptor: descriptor de módulo, slots de átomos de módulo y de función, aridad, código del cuerpo, número de slots, número de slots de términos; externo para las funciones exportadas) y un <symbol>.body interno de tipo void(void *context). Un <symbol> exportado conserva la firma TermWord(Context *, const TermWord *) como entrada del host que llama a CLAUSE_invoke_v1(context, frame, arguments).

Servicios del runtime

El código generado llama a servicios C++ comprobados: CLAUSE_exact_v1 (igualdad exacta), CLAUSE_immediate_v1 (predicados/consultas de inmediatos), CLAUSE_construct_v1, CLAUSE_inspect_v1, CLAUSE_integer_v1, CLAUSE_float_v1, CLAUSE_map_v1, CLAUSE_bits_v1, CLAUSE_record_v1 (make/get/update/match/test de native records bajo un RecordCheck; resultados bad_record, bad_field, no_match), CLAUSE_make_fun_v1 (construye un fun a partir de un FunDescriptor). Cada uno devuelve éxito, error semántico (badarg/badarith/...) o fallo de infraestructura y escribe la salida solo en caso de éxito. CLAUSE_display_v1 (output.hpp) imprime una línea de erlang:display/1 y produce true; no tiene error semántico. CLAUSE_halt_v1 nunca tiene éxito: registra una solicitud de halt (CallError::halted con el código de salida) o badarg, de modo que el llamador deshace la pila. Las grafías del enlazador siguen el mangling de C++ Itanium o Microsoft del objetivo.

CLAUSE_builtin_frame_v1(context, builtin) (builtins.hpp) devuelve el FrameDescriptor del builtin de producción con índice builtin en abi::v1::bridge_builtins (solo se añade al final); el código generado entra en él como en una función con los argumentos en los registros, y los errores y fallos van al canal comprobado (builtins). Un FrameDescriptor con cuerpo nulo es un builtin: entrar en él ejecuta el builtin sobre los registros y vuelve al llamador, o suspende el proceso en la continuación del builtin cuando este hizo un trap (porciones).

abi::v1::dispatch_builtin llama a builtins registrados por el host mediante los bytes de módulo/función, el array de argumentos y la aridad, y devuelve un Status; la salida se escribe solo en caso de éxito.

StatusValorSignificado
ok0Éxito
not_implemented1Característica diferida conocida, notificada una vez
invalid_argument2Entrada inválida
diagnostic_failure3Falló la entrega del informe
out_of_memory4Falló la asignación, estado revertido
busy5Contextos vivos o despacho en ejecución
wrong_owner6El objeto pertenece a otro runtime/contexto
resource_limit7Límite o presupuesto agotado
stopped8El propietario se ha cerrado
abi_mismatch9La versión o el ancho de término difieren
internal_error10Fallo inesperado contenido
unknown_builtin11Firma no registrada ni catalogada
erlang_error12Error estructurado de Erlang registrado
output_failure13La salida estándar rechazó una escritura

Revisiones

RevisiónCambio
1Descriptores y entradas iniciales
2Canal de fallos comprobado
3Grafías y slots de átomos en los descriptores
4Ámbitos de raíces generados obligatorios
5Marcos de proceso explícitos y transferencias
6Descriptores de native record en los descriptores de módulo, CLAUSE_record_v1
7Descriptores de funs en los descriptores de módulo, CLAUSE_make_fun_v1, CLAUSE_apply_v1
8Los descriptores de exportación nombran su FrameDescriptor; servicios de llamada dinámica CLAUSE_call_v1, CLAUSE_apply_list_v1, CLAUSE_call_list_v1, CLAUSE_make_external_fun_v1