Idiomas: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
← ABI de complementos de NeverC
El driver convierte una línea de órdenes en un conjunto de trabajos ejecutados.
PluginDriver.h expone esa canalización como seis fases y una tabla de
capacidades, NevercDriverAPI, de modo que un plugin puede reescribir
argumentos, elegir una cadena de herramientas, reestructurar el grafo de
acciones, añadir o sustituir trabajos e incluso ejecutar un trabajo en el propio
proceso en lugar de lanzar uno nuevo.
#include "neverc/Plugin/PluginDriver.h"
Bootstrap->QueryInterface(
Bootstrap->Context,
(NevercInterfaceID){NEVERC_INTERFACE_DRIVER_HIGH,
NEVERC_INTERFACE_DRIVER_LOW},
NEVERC_DRIVER_API_MAJOR, NEVERC_DRIVER_API_MINOR,
&Table, &Minor, &TableSize);NevercDriverAPI es una única tabla plana de 67 ranuras de función agrupadas en
cinco áreas: argumentos en bruto, opciones analizadas, selección de cadena de
herramientas, grafo de acciones y grafo de trabajos. Valide TableSize frente
al desplazamiento de la última ranura que use: la cola actual es
GetJobResult.
| Fase | Política | Entrada → salida |
|---|---|---|
neverc.driver.raw_arguments |
OBSERVABLE, INTERCEPTABLE | argv → argv |
neverc.driver.parsed_arguments |
OBSERVABLE, INTERCEPTABLE | lista de opciones analizadas → lista de opciones analizadas |
neverc.driver.select_toolchain |
además REPLACEABLE | petición de cadena → selección de cadena |
neverc.driver.build_actions |
además REPLACEABLE | petición → grafo de acciones |
neverc.driver.build_jobs |
además REPLACEABLE | grafo de acciones → grafo de trabajos |
neverc.driver.execute_job |
además REPLACEABLE | petición de ejecución → resultado del trabajo |
Sus macros siguen el patrón habitual:
NEVERC_PHASE_DRIVER_RAW_ARGUMENTS_{NAME,HIGH,LOW,POLICY,…}.
Las opciones se declaran una sola vez, durante Register, y a partir de ahí el
driver las acepta en la línea de órdenes exactamente como si fueran
incorporadas.
typedef struct NevercOptionDescriptor {
NevercABITableHeader Header;
NevercStringView Spelling;
NevercStringList Aliases;
NevercOptionForm Form; /* FLAG, JOINED, SEPARATE, MULTI_ARG */
NevercOptionValueType ValueType; /* BOOL, INT, UINT, STRING, ENUM, PATH */
NevercOptionMultiplicity Multiplicity; /* SINGLE, LAST_WINS, APPEND */
uint32_t ArgumentCount;
NevercBool Required;
NevercBool Hidden;
NevercStringView Help;
NevercStringView Metavar;
NevercStructArrayView EnumValues; /* NevercOptionEnumValue[] */
NevercStringList Conflicts;
NevercStringList Requires;
NevercStringView TargetPredicate;
NevercOptionValidatorFn Validator;
void *UserData;
NevercDestroyUserDataFn DestroyUserData;
} NevercOptionDescriptor;De pluginsdk/examples/DriverTracePlugin.c:
NevercOptionDescriptor Option = {0};
Option.Header = (NevercABITableHeader){sizeof(Option), NEVERC_DRIVER_API_MAJOR,
NEVERC_DRIVER_API_MINOR, 0};
Option.Spelling = SV("--driver-trace");
Option.Form = NEVERC_OPTION_FLAG;
Option.ValueType = NEVERC_OPTION_BOOL;
Option.Multiplicity = NEVERC_OPTION_SINGLE;
Option.Help = SV("enable the driver trace example plugin");
Status = Registrar->RegisterOption(RegistrarContext, &Option);Validator se invoca en cada aparición con un
NevercOptionValidationContext que lleva el identificador del plugin, la
grafía, el triple objetivo y el índice de aparición, de forma que un valor se
puede rechazar con un diagnóstico real en vez de fallar más tarde.
TargetPredicate restringe una opción a los triples coincidentes. Los valores
se releen con NevercCoreAPI.GetPluginOptionValueCount y
GetPluginOptionValue.
En neverc.driver.raw_arguments el artefacto es el vector argv. La lectura se
hace por índice y cada entrada informa de su procedencia:
Driver->GetArgumentCount(Driver->Context, Frame, Frame->Input, &Count);
NevercStringView Value, Source;
NevercArgumentOrigin Origin; /* COMMAND_LINE, CONFIGURATION, PLUGIN */
uint64_t Position;
Driver->GetArgument(Driver->Context, Frame, Frame->Input, Index,
&Value, &Origin, &Source, &Position);La edición es transaccional y solo es legal desde un interceptor, porque la mutación queda ligada a la continuación:
NevercArgumentMutationHandle Mutation;
Driver->BeginArgumentMutation(Driver->Context, Frame, Continuation,
Frame->Input, &Mutation);
Driver->InsertArgument(Driver->Context, Mutation, Index, SV("-O2"));
Driver->ReplaceArgument(Driver->Context, Mutation, Index, SV("-O3"));
Driver->EraseArgument(Driver->Context, Mutation, Index);
Driver->CommitArgumentMutation(Driver->Context, Mutation); /* o Abort */neverc.driver.parsed_arguments trabaja sobre apariciones de opciones en vez de
cadenas, que es justo lo que se necesita al añadir un indicador que no debe
volver a analizarse léxicamente:
typedef struct NevercOptionOccurrence {
NevercABITableHeader Header;
uint64_t Occurrence;
NevercStringView Spelling;
NevercStringList Values;
NevercArgumentOrigin Origin;
uint32_t Reserved;
} NevercOptionOccurrence;GetOptionOccurrenceCount y GetOptionOccurrence leen; después
BeginParsedArgumentMutation, AddOptionOccurrence,
RemoveOptionOccurrence, ReplaceOptionOccurrence y
CommitParsedArgumentMutation / AbortParsedArgumentMutation editan.
La petición describe qué se pidió y qué calculó el driver:
typedef struct NevercToolChainRequest {
NevercABITableHeader Header;
NevercStringView RequestedTriple;
NevercStringView ComputedTriple;
NevercStringView SysRoot;
NevercStringView ResourceDir;
NevercStringView CPU;
NevercStringList Features;
NevercExecutionLevel ExecutionLevel; /* UNSPECIFIED, USER, KERNEL */
NevercBool DynamicCodeProfile;
uint32_t Reserved;
} NevercToolChainRequest;Un interceptor puede ajustar la petición con BeginToolChainMutation,
SetToolChainTriple, SetToolChainCPU, SetToolChainFeatures y
CommitToolChainMutation. Un proveedor, en cambio, responde a la fase por
completo con CreateToolChainSelection, nombrando uno de los identificadores de
cadena incorporados o el suyo propio:
NEVERC_TOOLCHAIN_ID_DARWIN /* "neverc.builtin.darwin" */
NEVERC_TOOLCHAIN_ID_LINUX /* "neverc.builtin.linux" */
NEVERC_TOOLCHAIN_ID_MSVC /* "neverc.builtin.msvc" */
NEVERC_TOOLCHAIN_ID_GENERIC_ELF /* "neverc.builtin.generic-elf" */
NEVERC_TOOLCHAIN_ID_MACHO /* "neverc.builtin.macho" */
NEVERC_TOOLCHAIN_ID_GENERIC_GCC /* "neverc.builtin.generic-gcc" */GetToolChainSelection relee el resultado e informa de BuiltinProviderUsed,
así que un observador puede saber si un plugin ganó la fase.
Un nodo de acción es un paso de compilación tipado. Los nodos referencian entradas del driver y otros nodos:
typedef struct NevercActionNode {
NevercABITableHeader Header;
NevercActionNodeID Node;
NevercActionKind Kind;
NevercDriverType OutputType;
uint64_t InputCount;
NevercDriverInputID DriverInput;
NevercStringView BindArch;
uint64_t Reserved;
} NevercActionNode;NevercActionKind |
NevercDriverType |
||
|---|---|---|---|
INPUT |
BIND_ARCH |
PP_C, C, C_HEADER |
PP_ASM, ASM |
PREPROCESS |
COMPILE |
LLVM_IR, LLVM_BC |
LTO_IR, LTO_BC |
BACKEND |
ASSEMBLE |
OBJECT, IMAGE |
DSYM |
LINK, LIPO |
DSYMUTIL |
DEPENDENCIES |
NOTHING |
STATIC_LIB |
DYNCODE |
Lectura con GetDriverInputCount / GetDriverInput, GetActionNodeCount /
GetActionNode / GetActionNodeInput, y GetActionRootCount /
GetActionRoot.
Construir un grafo de reemplazo pasa por un constructor y una única publicación:
NevercActionGraphBuilderHandle Builder;
Driver->CreateActionGraphBuilder(Driver->Context, Frame, Request, &Builder);
NevercActionNodeDescriptor Node = {0};
Node.Header = (NevercABITableHeader){sizeof(Node), NEVERC_DRIVER_API_MAJOR,
NEVERC_DRIVER_API_MINOR, 0};
Node.Kind = NEVERC_ACTION_COMPILE;
Node.OutputType = NEVERC_DRIVER_TYPE_OBJECT;
Node.Inputs = /* NevercActionNodeIDList */;
NevercActionNodeID Created;
Driver->AddActionNode(Driver->Context, Builder, &Node, &Created);
Driver->SetActionRoots(Driver->Context, Builder, Roots);
Driver->PublishActionGraph(Driver->Context, Frame, Builder, &OutGraph);RemoveActionNode, ReplaceActionNodeInputs, SetActionNodeOutputType y
SetActionNodeBindArch editan un constructor en curso. Para ajustar el grafo
existente del anfitrión en lugar de reconstruirlo, use
BeginActionGraphMutation y CommitActionGraphMutation;
AbortActionGraphEdit descarta cualquiera de las dos formas.
Un trabajo es una orden que ejecutar. NevercJobDescriptor describe uno:
typedef struct NevercJobDescriptor {
NevercABITableHeader Header;
NevercJobKind Kind; /* COMMAND, FRONTEND, LINKER,
ARCHIVE, PLUGIN, DYNCODE */
NevercResponseFileKind ResponseFileKind; /* NONE, FULL, LIST */
NevercResponseFileEncoding ResponseFileEncoding;/* UTF8, CURRENT_CODE_PAGE,
UTF16 */
NevercBool InProcess;
NevercActionNodeID SourceAction;
NevercLinkerFlavor LinkerFlavor; /* NONE, GNU, WIN_LINK, DARWIN */
uint32_t Reserved;
NevercStringView Executable;
NevercStringList Arguments;
NevercStringList Environment;
NevercJobFileList Inputs;
NevercJobFileList Outputs;
NevercJobIDList Dependencies;
NevercStringView CallbackID;
NevercPluginJobCallbackFn Callback;
void *UserData;
} NevercJobDescriptor;Ponga Kind en NEVERC_JOB_PLUGIN con un Callback y el driver ejecutará su
función donde de otro modo lanzaría un proceso:
static NevercStatus NEVERC_CALL run_job(const NevercPluginJobContext *Context,
int32_t *OutExitCode, void *UserData) {
/* Context->Arguments, ->Environment, ->Inputs, ->Outputs son prestados. */
*OutExitCode = 0;
return neverc_status_ok();
}La lectura del grafo refleja la del grafo de acciones: GetJobCount / GetJob,
GetJobDependency, GetJobArgument / GetJobEnvironment, GetJobInput /
GetJobOutput. Tenga en cuenta que NevercJob solo informa de recuentos:
obtenga cada cadena o archivo por índice en lugar de esperar un arreglo en
línea.
La edición usa CreateJobGraphBuilder o BeginJobGraphMutation y después
AddJob, RemoveJob, MoveJobBefore, ReplaceJob, SetJobArgument,
SetJobEnvironment, SetJobInput, SetJobOutput y
ReplaceJobDependencies. Publique con PublishJobGraph o
CommitJobGraphMutation; descarte con AbortJobGraphEdit.
En neverc.driver.execute_job el artefacto de entrada es un
NevercJobExecutionRequest: el trabajo más sus listas de argumentos, entorno,
entradas, salidas y dependencias totalmente materializadas. Un proveedor ejecuta
el trabajo e informa del resultado:
typedef struct NevercJobResultDescriptor {
NevercABITableHeader Header;
int32_t ExitCode;
NevercBool ExecutionFailed;
NevercBool HasProcessStatistics;
uint32_t Reserved;
NevercStringView ErrorMessage;
NevercOutputSealList OutputSeals;
uint64_t TotalTimeMicroseconds;
uint64_t UserTimeMicroseconds;
uint64_t PeakMemoryKiB;
} NevercJobResultDescriptor;OutputSeals transporta los NevercOutputSealHandle producidos a través de la
API de E/S (véase Source y E/S), que es como
el anfitrión confirma que los archivos que un trabajo afirmó escribir existen
realmente con los resúmenes informados. GetJobResult lee un resultado
confirmado y, igual que la selección de cadena, informa de
BuiltinProviderUsed.
Condensado de pluginsdk/examples/DriverTracePlugin.c. El plugin no guarda
variables globales: el estado de proceso conserva las tablas negociadas y los
contadores por sesión y por tarea se piden al anfitrión dentro de cada callback.
static NevercStatus NEVERC_CALL
observe_arguments(const NevercPhaseFrame *Frame, NevercObserverPoint Point,
void *UserData) {
DriverTraceProcessState *Process = (DriverTraceProcessState *)UserData;
DriverTraceSessionState *Session = NULL;
uint64_t ArgumentCount = 0;
NevercStatus Status;
if (Frame == NULL || Process == NULL)
return status_code(NEVERC_STATUS_INVALID_ARGUMENT);
Status = Process->Core->GetSessionState(Process->Core->Context,
Frame->Session, plugin_id(),
(void **)&Session);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
Status = Process->Driver->GetArgumentCount(Process->Driver->Context, Frame,
Frame->Input, &ArgumentCount);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
++Session->ArgumentCallbacks;
if (Point == NEVERC_OBSERVER_BEFORE && !Session->Announced) {
Session->Announced = NEVERC_TRUE;
return emit_trace_remark(Process, Frame, "driver argument phase observed",
30, 1001);
}
return neverc_status_ok();
}
static NevercStatus NEVERC_CALL
intercept_job(const NevercPhaseFrame *Frame,
NevercPhaseContinuation *Continuation,
NevercPhaseResult *OutResult, void *UserData) {
DriverTraceProcessState *Process = (DriverTraceProcessState *)UserData;
NevercJobExecutionRequest Request = {0};
NevercPhaseResult Downstream = {0};
NevercStatus Status;
if (Frame == NULL || Continuation == NULL || OutResult == NULL || !Process)
return status_code(NEVERC_STATUS_INVALID_ARGUMENT);
Request.Header = (NevercABITableHeader){
sizeof(Request), NEVERC_DRIVER_API_MAJOR, NEVERC_DRIVER_API_MINOR, 0};
Status = Process->Driver->GetJobExecutionRequest(
Process->Driver->Context, Frame, Frame->Input, &Request);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
Downstream.Header = (NevercABITableHeader){
sizeof(Downstream), NEVERC_PLUGIN_ABI_MAJOR, NEVERC_PLUGIN_ABI_MINOR, 0};
Status = Continuation->InvokeNext(Continuation, Frame, &Downstream);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
*OutResult = (NevercPhaseResult){0};
OutResult->Header = (NevercABITableHeader){
sizeof(*OutResult), NEVERC_PLUGIN_ABI_MAJOR, NEVERC_PLUGIN_ABI_MINOR, 0};
OutResult->Action = NEVERC_PHASE_CONTINUE;
return neverc_status_ok();
}El registro conecta ambos con sus fases:
Observer.Phase = phase_id(NEVERC_PHASE_DRIVER_RAW_ARGUMENTS_HIGH,
NEVERC_PHASE_DRIVER_RAW_ARGUMENTS_LOW);
Observer.Points = NEVERC_OBSERVER_BEFORE | NEVERC_OBSERVER_AFTER;
Observer.Callback = observe_arguments;
Observer.UserData = Process;
Registrar->RegisterObserver(RegistrarContext, &Observer);
Interceptor.Phase = phase_id(NEVERC_PHASE_DRIVER_EXECUTE_JOB_HIGH,
NEVERC_PHASE_DRIVER_EXECUTE_JOB_LOW);
Interceptor.Callback = intercept_job;
Interceptor.UserData = Process;
Registrar->RegisterInterceptor(RegistrarContext, &Interceptor);Compílelo y ejecútelo:
cmake --build build-neverc --target neverc-plugin-example-driver-trace
build-neverc/bin/neverc \
-fplugin=build-neverc/neverc/pluginsdk/examples/host/DriverTracePlugin.so \
--driver-trace -c input.c -o input.o- Las mutaciones de argumentos, argumentos analizados, cadena de herramientas,
grafo de acciones y grafo de trabajos requieren todas la
NevercPhaseContinuationdel interceptor; fuera de él se rechazan conNEVERC_STATUS_WRONG_SCOPE. - Llame a
InvokeNextcomo máximo una vez y solo en el hilo del callback. - Todo handle de mutación debe alcanzar exactamente un
Commit*o unAbort*. - Las vistas devueltas por una llamada
Get*están prestadas mientras dure el callback. Copie lo que necesite conservar. - Un callback
NEVERC_JOB_PLUGINno debe lanzar el proceso que el anfitrión habría lanzado y además informar del éxito de la ruta incorporada; declareREPLACEy asuma el resultado. - Informe de un trabajo fallido mediante
NevercJobResultDescriptor.ExecutionFailedyErrorMessageen lugar de devolver un estado distinto de OK para un trabajo que se ejecutó y falló legítimamente.
Consulte PluginDriver.h para las declaraciones normativas,
PhaseSchema.json para las políticas de las fases del driver y
coverage.json para las pruebas de cobertura.