Lingue: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
← Indice documentazione · ← Progetto NeverC
Un plugin NeverC è un modulo condiviso che esporta esattamente una funzione, negozia tabelle di capacità versionate tramite un identificatore di interfaccia a 128 bit e si aggancia a un grafo congelato di fasi del compilatore dotate di nome. L'intera interfaccia è C11 puro. Un plugin non include mai un header LLVM, non collega mai il compilatore e non fa mai attraversare il confine a un tipo C++.
NEVERC_EXPORT NevercStatus NEVERC_CALL
neverc_plugin_entry(const NevercBootstrapAPI *Bootstrap,
NevercPluginDescriptor *OutPlugin);Questa firma, dichiarata in PluginCore.h, è l'intero contratto di
collegamento. Tutto il resto — leggere l'IR, riscrivere un grafo oggetto,
sostituire la pipeline di ottimizzazione — si raggiunge tramite tabelle che si
chiedono all'host per identificatore.
L'host guida un plugin attraverso tre ambiti annidati. Ogni ambito consegna al plugin un puntatore di stato opaco che il plugin stesso alloca e possiede, così un plugin scritto correttamente non ha bisogno di alcuno stato globale mutabile.
| Ambito | Callback | Significato |
|---|---|---|
| Process | ProcessBegin, Register, Destroy |
Un processo del compilatore. Qui si interrogano le interfacce e si registrano le capacità. |
| Session | SessionBegin, SessionEnd |
Una invocazione del driver. |
| Task | TaskBegin, TaskEnd |
Una unità di lavoro, identificata da NevercTaskKind. |
typedef struct NevercPluginDescriptor {
NevercABITableHeader Header;
NevercStringView PluginID;
NevercStringView DisplayName;
NevercSemanticVersion Version;
NevercConcurrencyModel Concurrency;
NevercReentrancyModel Reentrancy;
NevercStructArrayView RequiredInterfaces; /* NevercInterfaceRequirement[] */
NevercStructArrayView OptionalInterfaces; /* NevercInterfaceRequirement[] */
NevercStructArrayView Dependencies; /* NevercPluginDependency[] */
NevercProcessBeginFn ProcessBegin;
NevercRegisterPluginFn Register;
NevercSessionBeginFn SessionBegin;
NevercSessionEndFn SessionEnd;
NevercTaskBeginFn TaskBegin;
NevercTaskEndFn TaskEnd;
NevercPluginDestroyFn Destroy;
} NevercPluginDescriptor;In pratica solo PluginID e Register sono obbligatori; qualsiasi slot di
callback può restare NULL. I tipi di task sono NEVERC_TASK_INVOCATION,
TRANSLATION_UNIT, LTO, LINK, CODEGEN, OBJECT e DYNCODE.
L'host chiama prima ProcessBegin, poi Register esattamente una volta. La
registrazione è l'unico punto in cui si possono aggiungere opzioni,
osservatori, intercettori e provider; dopodiché il grafo delle fasi è
congelato.
Lo stato si recupera dentro una callback, non lo si cattura in anticipo:
Core->GetSessionState(Core->Context, Frame->Session, PluginID, &SessionState);
Core->GetTaskState(Core->Context, Frame->Task, PluginID, &TaskState);Una fase è una transizione con nome e versione da un artefatto di ingresso a un artefatto di uscita. NeverC include 130 fasi predefinite, più 8 famiglie di identificatori di estensione riservate alle fasi definite dai plugin:
| Dominio | Fasi | Dominio | Fasi |
|---|---|---|---|
driver |
6 | mir |
10 |
source |
3 | codegen |
4 |
prep |
6 | mc |
13 |
syntax |
7 | assembly |
4 |
sema |
7 | object |
8 |
ir |
8 | link |
20 |
dyncode |
34 |
Tutte e 130 hanno livello di stabilità stable nella major 1 dell'ABI. Ogni
fase dichiara una policy, e un plugin può agganciarsi solo nei modi che quella
policy consente:
| Flag di policy | Fasi | Cosa può fare un plugin |
|---|---|---|
NEVERC_PHASE_OBSERVABLE |
130 | Registrare un osservatore per una notifica in sola lettura. |
NEVERC_PHASE_INTERCEPTABLE |
105 | Avvolgere la fase e decidere se chiamare il resto della catena. |
NEVERC_PHASE_REPLACEABLE |
86 | Registrare un provider che fornisce l'output da sé. |
NEVERC_PHASE_SKIPPABLE_WITH_PROOF |
13 | Saltare la transizione fornendo un handle di prova. |
NEVERC_PHASE_SEALED_HOST_GATE |
14 | Nulla. Verificatori e commit appartengono all'host. |
I 14 gate sigillati sono ir.final_verify, mir.final_verify,
codegen.product_verify, assembly.final_verify, assembly.commit,
object.final_verify, object.commit, link.image_verify,
link.side_outputs_verify, link.commit, dyncode.ir.final_verify,
dyncode.mir.final_verify, dyncode.verify e dyncode.commit. Si possono
osservare, ma mai intercettare, sostituire o saltare.
Gli osservatori vengono notificati nei punti che la fase dichiara:
NEVERC_OBSERVER_BEFORE, NEVERC_OBSERVER_AFTER e
NEVERC_OBSERVER_AFTER_COMMIT. Un intercettore riceve una
NevercPhaseContinuation e deve chiamare InvokeNext al massimo una
volta, sul thread della callback, quindi riportare
NEVERC_PHASE_CONTINUE, NEVERC_PHASE_REPLACE o NEVERC_PHASE_SKIP in
NevercPhaseResult.Action.
Ogni callback di fase riceve lo stesso frame:
typedef struct NevercPhaseFrame {
NevercABITableHeader Header;
NevercSessionHandle Session;
NevercTaskHandle Task;
NevercInterfaceID Phase;
NevercPhaseRoute Route; /* triple, CPU, features, object format */
NevercArtifactHandle Input;
NevercArtifactHandle CurrentOutput;
NevercHandle Cancellation;
} NevercPhaseFrame;Schema/PhaseSchema.json è la fonte normativa per identificatori di fase,
policy, livelli di stabilità e gate di verifica. PluginPhaseSchema.h e il
file generato Schema/PluginPhaseSchema.inc che include espongono ciascuno
di essi come costante di compilazione — per la fase
neverc.ir.pass.pipeline_start:
NEVERC_PHASE_IR_PASS_PIPELINE_START_NAME /* "neverc.ir.pass.pipeline_start" */
NEVERC_PHASE_IR_PASS_PIPELINE_START_HIGH /* UINT64_C(0x4e43504849520001) */
NEVERC_PHASE_IR_PASS_PIPELINE_START_LOW /* UINT64_C(0x0000000000000004) */
NEVERC_PHASE_IR_PASS_PIPELINE_START_POLICY /* OBSERVABLE | INTERCEPTABLE */
NEVERC_PHASE_IR_PASS_PIPELINE_START_STABILITY
NEVERC_PHASE_IR_PASS_PIPELINE_START_INPUT_HIGH /* and _INPUT_LOW, _OUTPUT_* */NEVERC_BUILTIN_PHASE_COUNT e le costanti per dominio
NEVERC_BUILTIN_<DOMAIN>_PHASE_COUNT permettono a un plugin di asserire il
grafo contro cui è stato compilato.
Questo è pluginsdk/templates/minimal/Plugin.c alla lettera. Si carica,
negozia l'ABI, non registra nulla e si scarica in modo pulito: copiate la
directory e fatela crescere da qui.
#include "neverc/Plugin/NevercPluginAPI.h"
#define MINIMAL_PLUGIN_ID "com.example.minimal"
#define STRING_VIEW_LITERAL(Text) \
{ (Text), (uint64_t)(sizeof(Text) - 1) }
static NevercStatus status_code(NevercStatusCode Code) {
NevercStatus Status = neverc_status_ok();
Status.Code = Code;
return Status;
}
static void copy_bytes(void *Destination, const void *Source, uint64_t Count) {
uint64_t Index;
unsigned char *Out = (unsigned char *)Destination;
const unsigned char *In = (const unsigned char *)Source;
for (Index = 0; Index != Count; ++Index)
Out[Index] = In[Index];
}
static NevercStatus NEVERC_CALL
process_begin(const NevercCoreAPI *Core, void **OutProcessState) {
if (Core == NULL || OutProcessState == NULL)
return status_code(NEVERC_STATUS_INVALID_ARGUMENT);
*OutProcessState = NULL;
return neverc_status_ok();
}
static NevercStatus NEVERC_CALL
register_plugin(const NevercCoreAPI *Core, const NevercRegistrarAPI *Registrar,
void *RegistrarContext, void *ProcessState) {
(void)Core;
(void)RegistrarContext;
(void)ProcessState;
if (Registrar == NULL)
return status_code(NEVERC_STATUS_INVALID_ARGUMENT);
/* Register options, observers, interceptors, or providers here. */
return neverc_status_ok();
}
NEVERC_EXPORT NevercStatus NEVERC_CALL
neverc_plugin_entry(const NevercBootstrapAPI *Bootstrap,
NevercPluginDescriptor *OutPlugin) {
NevercPluginDescriptor Descriptor = {0};
uint32_t Capacity;
uint64_t BytesToWrite;
if (Bootstrap == NULL || OutPlugin == NULL ||
OutPlugin->Header.StructSize < sizeof(uint32_t))
return status_code(NEVERC_STATUS_INVALID_ARGUMENT);
Capacity = OutPlugin->Header.StructSize;
Descriptor.Header = (NevercABITableHeader){
sizeof(Descriptor), NEVERC_PLUGIN_ABI_MAJOR, NEVERC_PLUGIN_ABI_MINOR, 0};
Descriptor.PluginID = (NevercStringView)STRING_VIEW_LITERAL(MINIMAL_PLUGIN_ID);
Descriptor.DisplayName =
(NevercStringView)STRING_VIEW_LITERAL("Minimal Plugin");
Descriptor.Version = (NevercSemanticVersion){1, 0, 0, 0};
Descriptor.Concurrency = NEVERC_CONCURRENCY_SESSION_SERIAL;
Descriptor.Reentrancy = NEVERC_REENTRANCY_ALLOWED;
Descriptor.ProcessBegin = process_begin;
Descriptor.Register = register_plugin;
BytesToWrite = Capacity < sizeof(Descriptor) ? Capacity : sizeof(Descriptor);
copy_bytes(OutPlugin, &Descriptor, BytesToWrite);
return neverc_status_ok();
}OutPlugin è un buffer di proprietà del chiamante. All'ingresso il suo
Header.StructSize indica la capacità scrivibile; il plugin scrive al massimo
quel numero di byte e riporta la dimensione che ha effettivamente prodotto.
Scrivere per primo l'Header del descrittore stesso e poi troncare la copia
soddisfa entrambe le metà di quella regola.
Le tabelle di capacità si ottengono tramite identificatore di interfaccia a 128 bit, non tramite simbolo. Chiedete la versione major contro cui avete compilato e la minor più bassa con cui riuscite a lavorare:
const void *Table = NULL;
uint16_t Minor = 0;
uint64_t TableSize = 0;
NevercStatus Status = Bootstrap->QueryInterface(
Bootstrap->Context,
(NevercInterfaceID){NEVERC_INTERFACE_IR_PASS_HIGH,
NEVERC_INTERFACE_IR_PASS_LOW},
NEVERC_IR_PASS_API_MAJOR, NEVERC_IR_PASS_API_MINOR, &Table, &Minor,
&TableSize);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
if (!Table || TableSize < offsetof(NevercIRPassAPI, RegisterPass) +
sizeof(((NevercIRPassAPI *)0)->RegisterPass))
return fail(NEVERC_STATUS_ABI_MISMATCH);Confrontare TableSize con l'offset dell'ultima funzione che chiamate è la
regola che rende estensibile questo ABI: un host più recente aggiunge campi in
coda e un plugin più vecchio continua a funzionare perché non legge mai oltre
il prefisso che ha verificato. La macro
NEVERC_ABI_FIELD_AVAILABLE(header, type, field) applica lo stesso controllo a
una struttura che avete ricevuto. La stessa firma di QueryInterface è
presente anche su NevercCoreAPI, così potete negoziare in un secondo momento
invece che all'ingresso.
Le interfacce pubbliche, le loro tabelle e le loro macro identificative:
| Coppia di macro di interfaccia | Tabella | Header |
|---|---|---|
NEVERC_INTERFACE_CORE_{HIGH,LOW} |
NevercCoreAPI |
PluginCore.h |
NEVERC_INTERFACE_DRIVER_* |
NevercDriverAPI |
PluginDriver.h |
NEVERC_INTERFACE_IO_*, ..._SOURCE_LOCATION_* |
NevercIOAPI, NevercSourceLocationAPI |
PluginSource.h |
NEVERC_INTERFACE_PREP_* |
NevercPrepAPI |
PluginPrep.h |
NEVERC_INTERFACE_AST_*, ..._PARSER_* |
NevercASTAPI, NevercParserAPI |
PluginAST.h |
NEVERC_INTERFACE_SEMA_* |
NevercSemaAPI |
PluginSema.h |
NEVERC_INTERFACE_IR_CORE_*, ..._IR_BUILDER_*, ..._IR_ANALYSIS_*, ..._IR_PASS_*, ..._IR_GEN_*, ..._IR_OPTIMIZATION_* |
sei tabelle IR | PluginIR.h |
NEVERC_INTERFACE_TARGET_*, ..._TARGET_ABI_*, ..._CALLING_CONVENTION_* |
NevercTargetAPI, NevercTargetABIAPI, NevercCallingConventionAPI |
PluginTarget.h |
NEVERC_INTERFACE_MIR_*, ..._MIR_ANALYSIS_*, ..._MIR_PASS_*, ..._MIR_PROVIDER_* |
quattro tabelle MIR | PluginMIR.h |
NEVERC_INTERFACE_MC_*, ..._MC_EMISSION_*, ..._MC_PROVIDER_*, ..._ASSEMBLY_PROVIDER_* |
quattro tabelle MC | PluginMC.h |
NEVERC_INTERFACE_OBJECT_*, ..._OBJECT_FORMAT_*, ..._OBJECT_PHASE_* |
tre tabelle oggetto | PluginObject.h |
NEVERC_INTERFACE_LINK_*, ..._LINK_REGISTRAR_*, ..._LINK_PHASE_* |
tre tabelle di collegamento | PluginLink.h |
NEVERC_INTERFACE_LTO_*, ..._LTO_REGISTRAR_* |
NevercLTOAPI, NevercLTORegistrarAPI |
PluginLTO.h |
NEVERC_INTERFACE_DYNCODE_*, ..._DYNCODE_REGISTRAR_*, ..._DYNCODE_PHASE_* |
tre tabelle dyncode | PluginDynCode.h |
Ogni header definisce anche i corrispondenti NEVERC_<DOMAIN>_API_MAJOR e
_MINOR da passare a QueryInterface.
Un'interfaccia è o NEVERC_INTERFACE_STABLE (un host più recente può solo
aggiungere) o NEVERC_INTERFACE_LOCKSTEP (schemi specifici del target che
devono corrispondere esattamente). Confrontate il digest dello schema prima di
consumare valori LOCKSTEP.
Register riceve una NevercRegistrarAPI e un RegistrarContext opaco:
typedef struct NevercRegistrarAPI {
NevercABITableHeader Header;
NevercRegisterInterfaceFn RegisterInterface;
NevercRegisterPhaseFn RegisterPhase;
NevercRegisterObserverFn RegisterObserver;
NevercRegisterInterceptorFn RegisterInterceptor;
NevercRegisterProviderFn RegisterProvider;
NevercRegisterOptionFn RegisterOption;
} NevercRegistrarAPI;Ognuna di queste chiamate prende RegistrarContext come primo argomento e un
descrittore azzerato come secondo. Quale chiamata scegli è ciò che decide come
l'host ti tratta alla fase:
| Chiamata | Descrittore | Callback | La fase deve dichiarare |
|---|---|---|---|
RegisterObserver |
NevercObserverDescriptor |
NevercPhaseObserverFn |
OBSERVABLE |
RegisterInterceptor |
NevercInterceptorDescriptor |
NevercPhaseInterceptorFn |
INTERCEPTABLE |
RegisterProvider |
NevercProviderDescriptor |
NevercPhaseProviderFn |
REPLACEABLE |
RegisterPhase |
NevercPhaseDescriptor |
— | un ID definito dal plugin |
RegisterOption |
NevercOptionDescriptor |
Validator facoltativo |
— |
RegisterInterface |
argomenti semplici | — | — |
Un descrittore che non supera la validazione strutturale viene respinto sul posto con
NEVERC_STATUS_INVALID_DESCRIPTOR. Il controllo della policy avviene quando l'host
applica la registrazione: una fase sconosciuta, o una che non dichiara la policy
richiesta dalla tua chiamata, viene rifiutata lì. Un gate sigillato accetta soltanto
osservatori.
I registrar di dominio — NevercIRPassAPI.RegisterPass,
NevercTargetAPI.RegisterTarget, NevercObjectFormatAPI.RegisterFormat e gli
altri — prendono quello stesso RegistrarContext come secondo argomento: è
così che l'host attribuisce una registrazione al vostro plugin.
typedef struct NevercObserverDescriptor {
NevercABITableHeader Header;
NevercInterfaceID Phase;
NevercObserverPoint Points;
uint32_t Reserved;
NevercPhaseObserverFn Callback;
void *UserData;
NevercDestroyUserDataFn DestroyUserData;
} NevercObserverDescriptor;
typedef NevercStatus(NEVERC_CALL *NevercPhaseObserverFn)(
const NevercPhaseFrame *Frame, NevercObserverPoint Point, void *UserData);Points è una maschera di bit fra NEVERC_OBSERVER_BEFORE (1),
NEVERC_OBSERVER_AFTER (2) e NEVERC_OBSERVER_AFTER_COMMIT (4); deve essere diversa
da zero, e l'argomento Point dice al callback quale si è attivato. Da
pluginsdk/examples/DriverTracePlugin.c:
NevercObserverDescriptor Observer = {0};
Observer.Header = (NevercABITableHeader){
sizeof(Observer), NEVERC_PLUGIN_ABI_MAJOR, NEVERC_PLUGIN_ABI_MINOR, 0};
Observer.Phase = (NevercInterfaceID){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;
Status = Registrar->RegisterObserver(RegistrarContext, &Observer);UserData ti viene restituito intatto. Impostare DestroyUserData — presente su
ogni descrittore di questa sezione — fa sì che l'host liberi quella memoria quando la
registrazione scompare, così un'allocazione per registrazione non deve essere seguita
in Destroy.
typedef struct NevercInterceptorDescriptor {
NevercABITableHeader Header;
NevercInterfaceID Phase;
NevercPhaseInterceptorFn Callback;
void *UserData;
NevercDestroyUserDataFn DestroyUserData;
} NevercInterceptorDescriptor;
typedef NevercStatus(NEVERC_CALL *NevercPhaseInterceptorFn)(
const NevercPhaseFrame *Frame, NevercPhaseContinuation *Continuation,
NevercPhaseResult *OutResult, void *UserData);La continuazione è tutto il resto della catena, e il risultato è il modo in cui riferisci che cosa ne hai fatto:
typedef struct NevercPhaseContinuation {
NevercABITableHeader Header;
NevercInvokeNextFn InvokeNext;
void *Context;
uint64_t Generation;
} NevercPhaseContinuation;
typedef struct NevercPhaseResult {
NevercABITableHeader Header;
NevercPhaseAction Action;
uint32_t Reserved;
NevercArtifactHandle Output;
NevercProofHandle Proof;
} NevercPhaseResult;Le tre azioni non sono intercambiabili. L'host confronta il risultato con quello che
hai fatto davvero e fa fallire la catena con NEVERC_STATUS_POLICY_VIOLATION a ogni
discordanza:
Action |
InvokeNext |
Output |
Proof |
Richiede inoltre |
|---|---|---|---|---|
NEVERC_PHASE_CONTINUE |
chiamato una volta | vuoto | vuoto | — |
NEVERC_PHASE_REPLACE |
non chiamato | impostato | vuoto | REPLACEABLE |
NEVERC_PHASE_SKIP |
non chiamato | impostato | impostato | SKIPPABLE_WITH_PROOF |
InvokeNext può essere chiamato al più una volta e solo sul thread del callback: una
seconda chiamata è una violazione di policy, e una chiamata da un altro thread
segnala NEVERC_STATUS_WRONG_SCOPE. Anche un intercettore che restituisce CONTINUE
senza averlo chiamato viola la policy, perché la fase non produrrebbe nulla in
silenzio.
NevercInterceptorDescriptor Interceptor = {0};
Interceptor.Header = (NevercABITableHeader){
sizeof(Interceptor), NEVERC_PLUGIN_ABI_MAJOR, NEVERC_PLUGIN_ABI_MINOR, 0};
Interceptor.Phase = (NevercInterfaceID){NEVERC_PHASE_DRIVER_EXECUTE_JOB_HIGH,
NEVERC_PHASE_DRIVER_EXECUTE_JOB_LOW};
Interceptor.Callback = intercept_job;
Interceptor.UserData = Process;
Status = Registrar->RegisterInterceptor(RegistrarContext, &Interceptor);Un provider sostituisce la fase per intero, perciò dichiara anche il contratto di determinismo su cui fa affidamento la cache di build:
typedef struct NevercProviderDescriptor {
NevercABITableHeader Header;
NevercInterfaceID Phase;
NevercStringView ProviderID;
NevercPhaseRoute Route;
NevercBool Deterministic;
NevercBool Cacheable;
NevercBool FallbackSafe;
uint32_t Reserved;
NevercPhaseProviderFn Callback;
void *UserData;
NevercDestroyUserDataFn DestroyUserData;
} NevercProviderDescriptor;
typedef NevercStatus(NEVERC_CALL *NevercPhaseProviderFn)(
const NevercPhaseFrame *Frame, NevercPhaseResult *OutResult,
void *UserData);Provider.ProviderID = SV("com.example.my-lowering");
Provider.Route = /* triple / CPU / features / object format */;
Provider.Deterministic = NEVERC_TRUE;
Provider.Cacheable = NEVERC_TRUE;
Provider.FallbackSafe = NEVERC_FALSE; /* built-in cannot silently take over */ProviderID deve essere un nome canonico: al massimo 255 byte fra lettere minuscole,
cifre, ., _ e -, che non inizi né finisca con un punto e non contenga mai ...
Basta una maiuscola perché la registrazione venga rifiutata. Route.Header va
inizializzato come qualunque altra intestazione di tabella.
Qui non c'è continuazione: il callback è la fase. Deve riportare
NEVERC_PHASE_REPLACE con un Output e un Proof vuoto — qualsiasi altra cosa è una
violazione di policy.
FallbackSafe è l'unico di questi flag con un effetto a runtime che va oltre la
contabilità. Quando vale NEVERC_TRUE e il provider fallisce con uno stato
contrassegnato NEVERC_STATUS_FLAG_RECOVERABLE, l'host può scartare gli effetti
parziali ed eseguire invece l'implementazione integrata. Lascialo a NEVERC_FALSE
quando un tentativo lasciato a metà non può essere annullato.
RegisterPhase aggiunge una transizione che l'host non conosce, ed è esattamente
quello per cui sono riservate le 8 famiglie di ID di estensione:
typedef struct NevercPhaseDescriptor {
NevercABITableHeader Header;
NevercInterfaceID Phase;
NevercStringView CanonicalName;
NevercInterfaceID InputArtifact;
NevercInterfaceID OutputArtifact;
NevercPhasePolicy Policy;
NevercObserverPoint ObserverPoints;
uint32_t Reserved;
} NevercPhaseDescriptor;Phase, InputArtifact e OutputArtifact devono essere tutti diversi da zero, e
Policy deve essere diversa da zero e contenere solo flag noti. Dichiarare
ObserverPoints senza NEVERC_PHASE_OBSERVABLE viene rifiutato, così come combinare
NEVERC_PHASE_SEALED_HOST_GATE con INTERCEPTABLE, REPLACEABLE o
SKIPPABLE_WITH_PROOF: sono gli stessi invarianti rispetto ai quali viene verificato
il grafo integrato. Prendi l'ID dalla famiglia del tuo dominio, così non potrà
collidere con una futura fase integrata:
#include "neverc/Plugin/Schema/PluginPhaseSchema.inc"
/* NEVERC_EXTENSION_FAMILY_COUNT is 8; family 1 is "neverc.ir.extension". */
NevercInterfaceID MyPhase = {NEVERC_EXTENSION_FAMILY_1_ID_HIGH,
NEVERC_EXTENSION_FAMILY_1_ID_LOW_MIN};Ogni famiglia pubblica _NAMESPACE, _ID_HIGH, _ID_LOW_MIN e _ID_LOW_MAX, e la
metà bassa è tua da assegnare all'interno di quell'intervallo.
RegisterInterface è l'unica chiamata che non prende un descrittore. Consegna
all'host una tabella tua, così che un altro plugin possa raggiungerla attraverso lo
stesso QueryInterface usato per le interfacce integrate:
Registrar->RegisterInterface(RegistrarContext, MyInterfaceID,
NEVERC_INTERFACE_STABLE, &MyTable,
/* Compatibility = */ NULL);Passa invece NEVERC_INTERFACE_LOCKSTEP quando la tabella trasporta valori di schema
specifici del target che non sopravviverebbero a uno sfasamento di versione.
Un'interfaccia lockstep deve fornire una NevercCompatibilityKey, che vincola il
consumatore a una singola build del produttore:
typedef struct NevercCompatibilityKey {
NevercABITableHeader Header;
NevercStringView ProducerBuildID; /* compare against Bootstrap->HostBuildID */
NevercStringView TargetABIKey;
uint32_t LLVMMajor; /* compare against Bootstrap->LLVMMajor */
uint32_t Reserved;
} NevercCompatibilityKey;Per una registrazione lockstep vanno riempiti tutti e tre i campi; un ID di build vuoto, una chiave ABI vuota o un major LLVM pari a zero vengono rifiutati come descrittore non valido.
Includete l'header aggregato NevercPluginAPI.h oppure solo i domini che
usate:
#include "neverc/Plugin/NevercPluginAPI.h" /* everything */
#include "neverc/Plugin/PluginIR.h" /* or one domain */Costruire un modulo condiviso con NeverC stesso:
neverc --target=arm64-apple-macosx -shared \
-I/path/to/pluginsdk/include \
-o MyPlugin.dylib MyPlugin.cOppure contro un SDK installato con CMake:
find_package(NevercPluginSDK REQUIRED)
add_library(my_plugin MODULE my_plugin.c)
target_link_libraries(my_plugin PRIVATE NevercPluginSDK::headers)Oppure con pkg-config:
cc -shared $(pkg-config --cflags neverc-plugin) -o my_plugin.so my_plugin.cUsate .so, .dylib o .dll a seconda dell'host. L'SDK non collega alcun
LLVM né alcun runtime NeverC: NevercPluginSDK::headers è di soli header.
neverc -fplugin=./MyPlugin.dylib -c input.c -o input.o| Opzione | Forma | Scopo |
|---|---|---|
-fplugin=<path> |
ripetibile | Caricare un modulo condiviso di plugin per l'intera toolchain. |
-fplugin-arg=<plugin-id>:<key>=<value> |
ripetibile | Passare un valore con spazio dei nomi a un'opzione di plugin registrata. |
-fplugin-provider=<phase>:<plugin-id> |
ripetibile | Scegliere quale plugin fornisce una fase sostituibile. |
-fplugin-pass=<dsopath> |
ripetibile | Caricare un plugin di pass out-of-tree con ABI C. |
-fplugin-pass-arg=<key>=<value> |
ripetibile | Passare un argomento ai plugin di pass con ABI C. |
Il qualificatore <plugin-id>: può essere omesso solo quando è attivo
esattamente un plugin. Le opzioni che un plugin registra con RegisterOption
sono accettate anche direttamente con la grafia dichiarata, in forma di flag,
unita, separata o a più argomenti. Argomenti di plugin e selezioni di provider
privi di un -fplugin= corrispondente sono un errore netto, non un'operazione
ignorata in silenzio.
Un'opzione registrata si può rileggere in qualsiasi momento tramite la tabella core:
uint64_t Count = 0;
Core->GetPluginOptionValueCount(Core->Context, Session, PluginID,
SV("--driver-trace"), &Count);
NevercStringView Value;
Core->GetPluginOptionValue(Core->Context, Session, PluginID,
SV("--driver-trace"), 0, &Value);- Interrogate le tabelle di capacità tramite
QueryInterface; pretendete la major corrispondente e controllateStructSizeprima di toccare un campo. - Inizializzate l'
Headere lo spazio riservato di ogni struttura pubblica. Azzerate la struttura, poi impostateStructSize,Major,MinoreFlags. - Trattate handle e viste prese in prestito come valori opachi con ambito. Non conservate mai un handle di ambito task oltre la sua callback, non usatelo mai in un'altra sessione o task e non fabbricate mai un valore di handle.
- Restituite
NevercStatusda ogni callback. Non lasciate che un'eccezione C++ o un puntatore di proprietà dell'host attraversino il confine C. - Dichiarate il
NevercConcurrencyModelpiù stretto che sia veritiero (SESSION_SERIAL,THREAD_SAFE,PROCESS_SERIAL) e ilNevercReentrancyModel(NONE,ALLOWED). - Eseguite le modifiche a IR, MIR, AST, grafi e artefatti tramite le API transazionali dell'host: aprite una mutazione, preparate le modifiche, poi fate commit o abort. Il commit verifica e pubblica in modo atomico; un commit fallito lascia intatto lo stato precedente.
- Allocate tramite
NevercCoreAPI.Allocate/Reallocate/Deallocatequando la memoria deve essere contabilizzata dall'host. - Tenete lo stato mutabile nello stato process/session/task fornito dall'host.
Lo stato globale mutabile è controllato da
utils/plugin-api/check-global-state.py.
Tutte le strutture pubbliche sono disposte sotto NEVERC_ABI_PACK_BEGIN
(packing a 8 byte) e usano solo tipi a larghezza fissa. Le nuove funzioni
vengono aggiunte in coda a tabelle di capacità versionate in modo
indipendente; il prefisso stabile di una tabella non cambia entro la prima
major dell'ABI (NEVERC_PLUGIN_ABI_MAJOR = 1).
NevercStatus porta un Code, dei Flags e una parola Detail. L'insieme
completo dei codici:
| Codice | Significato |
|---|---|
NEVERC_STATUS_OK |
Successo. |
NEVERC_STATUS_INVALID_ARGUMENT |
Un puntatore o valore richiesto mancava o era malformato. |
NEVERC_STATUS_ABI_MISMATCH |
La tabella negoziata è troppo piccola o la major è diversa. |
NEVERC_STATUS_MISSING_INTERFACE |
L'host non pubblica l'interfaccia richiesta. |
NEVERC_STATUS_VERSION_MISMATCH |
La major/minor richiesta non può essere soddisfatta. |
NEVERC_STATUS_INVALID_DESCRIPTOR |
Un descrittore non ha superato la validazione strutturale. |
NEVERC_STATUS_DUPLICATE_ID |
Un identificatore era già registrato. |
NEVERC_STATUS_DEPENDENCY_MISSING |
Una dipendenza dichiarata è assente. |
NEVERC_STATUS_DEPENDENCY_CYCLE |
L'ordine di registrazione non può essere soddisfatto. |
NEVERC_STATUS_BUSY |
Una risorsa è trattenuta altrove. |
NEVERC_STATUS_CANCELLED |
È stata richiesta una cancellazione cooperativa. |
NEVERC_STATUS_RESOURCE_EXHAUSTED |
È stato raggiunto un budget o un limite. |
NEVERC_STATUS_STALE_HANDLE |
Un handle è sopravvissuto all'oggetto che nominava. |
NEVERC_STATUS_WRONG_SESSION |
Un handle è stato usato in un'altra sessione. |
NEVERC_STATUS_WRONG_SCOPE |
Un handle è stato usato fuori dal suo ambito. |
NEVERC_STATUS_WRONG_TYPE |
Un handle nominava un'entità di tipo diverso. |
NEVERC_STATUS_INVALID_STATE |
L'operazione non è lecita nello stato attuale. |
NEVERC_STATUS_POLICY_VIOLATION |
La policy della fase vieta l'operazione. |
NEVERC_STATUS_VERIFICATION_FAILED |
Un verificatore sigillato dell'host ha respinto il prodotto. |
NEVERC_STATUS_CAPABILITY_UNAVAILABLE |
L'host non può offrire qui quella capacità. |
NEVERC_STATUS_PLUGIN_FAILURE |
Il plugin ha segnalato un fallimento generico. |
NEVERC_STATUS_PLUGIN_EXCEPTION |
Un'eccezione è sfuggita da una callback del plugin. |
NEVERC_STATUS_OUTPUT_PARTIAL |
L'output è stato scritto solo in parte. |
NEVERC_STATUS_REENTRANCY_DENIED |
Una chiamata rientrante è stata rifiutata. |
NEVERC_STATUS_NOT_FOUND |
L'entità indicata non esiste. |
I bit di flag descrivono che cosa è successo all'output, ed è ciò di cui un
sistema di build ha bisogno per decidere se un nuovo tentativo è sicuro:
NEVERC_STATUS_FLAG_RECOVERABLE, _OUTPUT_ALREADY_COMMITTED,
_OUTPUT_MAY_BE_PARTIAL, _OUTPUT_RECOVERY_REQUIRED e
_DURABILITY_UNCONFIRMED.
Segnalate i problemi con NevercCoreAPI.EmitDiagnostic e un
NevercDiagnosticDescriptor che porta gravità (NOTE, REMARK, WARNING,
ERROR, FATAL), codice, identificatore del plugin, identificatore della
fase, messaggio, note, posizione sorgente, intervalli e fix-it. Chiamate
CheckCancelled prima di lavori costosi.
Compilarli tutti:
cmake --build build-neverc --target neverc-pluginsdk-examplesOgni esempio viene compilato due volte — una con il compilatore C host
configurato e una con il NeverC appena costruito — così l'ABI è dimostrato da
entrambi i lati. I moduli finiscono in
build-neverc/neverc/pluginsdk/examples/host/.
| Esempio | Target CMake | Mostra |
|---|---|---|
DriverTracePlugin.c |
neverc-plugin-example-driver-trace |
Registrazione di opzioni, osservazione di fasi, intercettazione di job |
VirtualHeaderPlugin.c |
neverc-plugin-example-virtual-header |
Un provider VFS che serve un header in memoria |
ASTRewritePlugin.c |
neverc-plugin-example-ast-rewrite |
Intercettazione del parser e mutazione atomica dell'AST |
ExamplePlugin.c |
neverc-plugin-example-ir-overview |
Un pass IR a livello di modulo che percorre l'elenco delle funzioni con un cursore di valori |
FunctionPass.c |
neverc-plugin-example-function-pass |
Un pass IR di funzione stabile |
MachinePass.c |
neverc-plugin-example-machine-pass |
Un pass MIR stabile all'hook pre-emit |
MCObserverPlugin.c |
neverc-plugin-example-mc-observer |
Eventi di emissione MC in sola lettura |
ObjectRewritePlugin.c |
neverc-plugin-example-object-rewrite |
Riscrittura transazionale di ObjectGraph |
CustomCallConvPlugin.c |
neverc-plugin-example-custom-callconv |
Convenzioni di chiamata guidate dai dati |
DynCodeTracePlugin.c |
neverc-plugin-example-dyncode-trace |
Osservazione della pipeline dyncode |
DynCodeEncoderPlugin.c |
neverc-plugin-example-dyncode-encoder |
Intercettazione della codifica del set di caratteri dyncode |
CrtShimPlugin.c |
neverc-plugin-example-crt-shim |
Un plugin con zero dipendenze dalla CRT |
BenchPlugin.c |
neverc-plugin-example-abi-bench |
Microbenchmark del throughput di chiamata dell'ABI |
Caricarne uno:
neverc -fplugin=build-neverc/neverc/pluginsdk/examples/host/FunctionPass.so \
-O2 -c input.c -o input.o| File | Garanzie |
|---|---|
neverc/include/neverc/Plugin/Schema/PhaseSchema.json |
Identificatori di fase, policy, stabilità, gate di verifica |
pluginsdk/manifest/plugin.json |
Versione dell'ABI, identificatori/versioni/stabilità delle interfacce, digest degli schemi, target supportati |
pluginsdk/abi/plugin.json |
Dimensione, allineamento e offset dei campi misurati di ogni struttura pubblica, per chiave di ABI dell'host |
docs/plugin-api/coverage.json |
Associa ogni fase stabile a test positivi, negativi, di sostituzione, di osservatore e di gate sigillato |
Un SDK può quindi essere validato meccanicamente contro un host, e una build di plugin può asserire il layout delle proprie strutture contro la chiave di ABI in cui verrà caricata.