Skip to content

Latest commit

 

History

History
771 lines (647 loc) · 42.4 KB

File metadata and controls

771 lines (647 loc) · 42.4 KB

言語: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية

← ドキュメント索引 · ← NeverC プロジェクト

NeverC プラグイン ABI

NeverC のプラグインは、関数をちょうど 1 つだけエクスポートし、128 ビットのインタ ーフェース ID でバージョン付きのケーパビリティテーブルをネゴシエートし、名前付き コンパイラフェーズの凍結されたグラフに自分自身を接続する共有モジュールです。イン ターフェース全体が純粋な C11 です。プラグインが LLVM ヘッダーをインクルードする ことも、コンパイラをリンクすることも、C++ の型を境界越しに渡すこともありません。

NEVERC_EXPORT NevercStatus NEVERC_CALL
neverc_plugin_entry(const NevercBootstrapAPI *Bootstrap,
                    NevercPluginDescriptor *OutPlugin);

PluginCore.h で宣言されるこのシグネチャが、リンケージ契約のすべてです。それ以外 のこと——IR を読む、オブジェクトグラフを書き換える、最適化パイプラインを差し替える ——はすべて、ID を指定してホストに要求するテーブル経由で到達します。

ガイド

ガイド 扱う範囲
ドライバー API コマンドラインツールチェーン選択アクショングラフジョブグラフ
ソースと I/O API VFS プロバイダーソース位置バッファー出力シンク依存関係
プリプロセッサー API トークンマクロpragmainclude機能クエリ39 種類のイベント
AST と意味解析 API パーサー拡張AST 変更名前探索定数
IR API LLVM IR の読み取りトランザクショナルな構築解析パスプロバイダー
MIR API マシン関数レジスタースタックフレームMIR パスと解析
ターゲット、MC、アセンブリ、オブジェクト ターゲット登録呼び出し規約MC エンコードオブジェクトグラフ
リンクと LTO API リンクグラフシンボル解決GC/ICFリンカーと LTO プロバイダー
DynCode API フラットな位置独立イメージインポートの低位化文字セットエンコード
カスタム呼び出し規約 データ駆動の呼び出し規約プラグイン
フェーズカバレッジの根拠 すべての安定フェーズに対するテストの対応付け

実行モデル

ホストは 3 層の入れ子スコープでプラグインを駆動します。各スコープは、プラグイン自 身が確保して所有する不透明な状態ポインターをプラグインに渡します。したがって、正 しく書かれたプラグインにグローバルな可変状態は必要ありません。

スコープ コールバック 意味
Process ProcessBeginRegisterDestroy コンパイラプロセス 1 つ。ここでインターフェースを問い合わせ、ケーパビリティを登録します。
Session SessionBeginSessionEnd ドライバー呼び出し 1 回。
Task TaskBeginTaskEnd NevercTaskKind で識別される作業単位 1 つ。
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;

実質的に必須なのは PluginIDRegister だけで、どのコールバックスロットも NULL のままで構いません。タスク種別は NEVERC_TASK_INVOCATIONTRANSLATION_UNITLTOLINKCODEGENOBJECTDYNCODE です。

ホストはまず ProcessBegin を呼び、続いて Register をちょうど 1 回呼びます。 オプション、オブザーバー、インターセプター、プロバイダーを追加できるのは登録時だ けで、その後フェーズグラフは凍結されます。

状態は事前に捕捉するのではなく、コールバックの中で取得します:

Core->GetSessionState(Core->Context, Frame->Session, PluginID, &SessionState);
Core->GetTaskState(Core->Context, Frame->Task, PluginID, &TaskState);

フェーズ

フェーズとは、入力アーティファクトから出力アーティファクトへの、名前付きでバージ ョン付きの遷移です。NeverC は 130 個の組み込みフェーズを提供し、さらにプラグ イン定義フェーズ用に 8 つの拡張 ID ファミリーを予約しています:

ドメイン フェーズ数 ドメイン フェーズ数
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

この 130 個はすべて ABI メジャー 1 において安定性ティア stable です。各フェーズ はポリシーを宣言し、プラグインはそのポリシーが許す方法でのみ接続できます:

ポリシーフラグ フェーズ数 プラグインができること
NEVERC_PHASE_OBSERVABLE 130 読み取り専用の通知を受けるオブザーバーを登録する。
NEVERC_PHASE_INTERCEPTABLE 105 フェーズをラップし、チェーンの残りを呼ぶかどうかを決める。
NEVERC_PHASE_REPLACEABLE 86 出力自体を供給するプロバイダーを登録する。
NEVERC_PHASE_SKIPPABLE_WITH_PROOF 13 証明ハンドルを提供したうえで遷移をスキップする。
NEVERC_PHASE_SEALED_HOST_GATE 14 何もできない。検証器とコミットはホストの専有物。

14 個の封印されたゲートは ir.final_verifymir.final_verifycodegen.product_verifyassembly.final_verifyassembly.commitobject.final_verifyobject.commitlink.image_verifylink.side_outputs_verifylink.commitdyncode.ir.final_verifydyncode.mir.final_verifydyncode.verifydyncode.commit です。観測はでき ますが、インターセプト・置換・スキップは決してできません。

オブザーバーは、フェーズが宣言した時点で配送されます: NEVERC_OBSERVER_BEFORENEVERC_OBSERVER_AFTERNEVERC_OBSERVER_AFTER_COMMIT。インターセプターは NevercPhaseContinuation を受け取り、コールバックスレッド上で InvokeNext高々 1 回呼んだうえで、NevercPhaseResult.ActionNEVERC_PHASE_CONTINUENEVERC_PHASE_REPLACENEVERC_PHASE_SKIP のいずれか を報告しなければなりません。

どのフェーズコールバックも同じフレームを受け取ります:

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 が、フェーズ ID・ポリシー・安定性ティア・検証器ゲートの 規範的な出典です。PluginPhaseSchema.h と、それが include する生成物 Schema/PluginPhaseSchema.inc は、それらをコンパイル時定数として公開します。 フェーズ 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 と、ドメインごとの NEVERC_BUILTIN_<DOMAIN>_PHASE_COUNT 定数を使えば、プラグインはビルド時に前提と したグラフをアサートできます。

完全な最小プラグイン

以下は pluginsdk/templates/minimal/Plugin.c そのままです。ロードされ、ABI をネ ゴシエートし、何も登録せず、きれいにアンロードされます。このディレクトリをコピー して、ここから育ててください。

#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 は呼び出し側が所有するバッファーです。入口ではその Header.StructSize が書き込み可能な容量を表します。プラグインはその範囲までしか 書き込まず、実際に生成したサイズを報告します。ディスクリプター自身の Header を 先に書き、それからコピーを切り詰めれば、この規則の両側を同時に満たせます。

インターフェースのネゴシエーション

ケーパビリティテーブルはシンボルではなく 128 ビットのインターフェース ID で取得し ます。ビルド時に前提としたメジャーバージョンと、動作可能な最小のマイナーバージョ ンを指定してください:

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);

呼び出す最後の関数のオフセットと TableSize を突き合わせること——これがこの ABI を拡張可能にしている規則です。新しいホストはフィールドを末尾に追加し、古いプラグ インは検証済みの前半部分より先を決して読まないので動き続けます。 NEVERC_ABI_FIELD_AVAILABLE(header, type, field) マクロは、受け取った構造体に同 じ検査を適用します。同じシグネチャの QueryInterfaceNevercCoreAPI にもある ので、エントリー時ではなく後からネゴシエートすることもできます。

公開インターフェースと、そのテーブル、ID マクロ:

インターフェースマクロの組 テーブル ヘッダー
NEVERC_INTERFACE_CORE_{HIGH,LOW} NevercCoreAPI PluginCore.h
NEVERC_INTERFACE_DRIVER_* NevercDriverAPI PluginDriver.h
NEVERC_INTERFACE_IO_*..._SOURCE_LOCATION_* NevercIOAPINevercSourceLocationAPI PluginSource.h
NEVERC_INTERFACE_PREP_* NevercPrepAPI PluginPrep.h
NEVERC_INTERFACE_AST_*..._PARSER_* NevercASTAPINevercParserAPI PluginAST.h
NEVERC_INTERFACE_SEMA_* NevercSemaAPI PluginSema.h
NEVERC_INTERFACE_IR_CORE_*..._IR_BUILDER_*..._IR_ANALYSIS_*..._IR_PASS_*..._IR_GEN_*..._IR_OPTIMIZATION_* 6 つの IR テーブル PluginIR.h
NEVERC_INTERFACE_TARGET_*..._TARGET_ABI_*..._CALLING_CONVENTION_* NevercTargetAPINevercTargetABIAPINevercCallingConventionAPI PluginTarget.h
NEVERC_INTERFACE_MIR_*..._MIR_ANALYSIS_*..._MIR_PASS_*..._MIR_PROVIDER_* 4 つの MIR テーブル PluginMIR.h
NEVERC_INTERFACE_MC_*..._MC_EMISSION_*..._MC_PROVIDER_*..._ASSEMBLY_PROVIDER_* 4 つの MC テーブル PluginMC.h
NEVERC_INTERFACE_OBJECT_*..._OBJECT_FORMAT_*..._OBJECT_PHASE_* 3 つのオブジェクトテーブル PluginObject.h
NEVERC_INTERFACE_LINK_*..._LINK_REGISTRAR_*..._LINK_PHASE_* 3 つのリンクテーブル PluginLink.h
NEVERC_INTERFACE_LTO_*..._LTO_REGISTRAR_* NevercLTOAPINevercLTORegistrarAPI PluginLTO.h
NEVERC_INTERFACE_DYNCODE_*..._DYNCODE_REGISTRAR_*..._DYNCODE_PHASE_* 3 つの dyncode テーブル PluginDynCode.h

各ヘッダーは、QueryInterface に渡すべき対応する NEVERC_<DOMAIN>_API_MAJOR_MINOR も定義しています。

インターフェースは NEVERC_INTERFACE_STABLE(新しいホストは追加のみ可能)か、 NEVERC_INTERFACE_LOCKSTEP(完全一致が必要なターゲット固有スキーマ)のいずれかで す。LOCKSTEP の値を利用する前にスキーマダイジェストを比較してください。

登録

RegisterNevercRegistrarAPI と不透明な RegistrarContext を受け取ります:

typedef struct NevercRegistrarAPI {
  NevercABITableHeader Header;
  NevercRegisterInterfaceFn RegisterInterface;
  NevercRegisterPhaseFn RegisterPhase;
  NevercRegisterObserverFn RegisterObserver;
  NevercRegisterInterceptorFn RegisterInterceptor;
  NevercRegisterProviderFn RegisterProvider;
  NevercRegisterOptionFn RegisterOption;
} NevercRegistrarAPI;

いずれの呼び出しも、第 1 引数に RegistrarContext を、第 2 引数にゼロ初期化した ディスクリプターを取ります。どれを呼ぶかが、そのフェーズでホストがあなたをどう扱 うかを決めます:

呼び出し ディスクリプター コールバック フェーズが宣言すべきポリシー
RegisterObserver NevercObserverDescriptor NevercPhaseObserverFn OBSERVABLE
RegisterInterceptor NevercInterceptorDescriptor NevercPhaseInterceptorFn INTERCEPTABLE
RegisterProvider NevercProviderDescriptor NevercPhaseProviderFn REPLACEABLE
RegisterPhase NevercPhaseDescriptor プラグイン定義の ID
RegisterOption NevercOptionDescriptor 任意の Validator
RegisterInterface 引数を直接渡す

構造検証に通らないディスクリプターは、その場で NEVERC_STATUS_INVALID_DESCRIPTOR として拒否されます。ポリシー検査が走るのは、ホストがその登録を適用するときです。未 知のフェーズや、その呼び出しが要求するポリシーを宣言していないフェーズは、そこで拒 否されます。sealed gate が受け付けるのはオブザーバーだけです。

ドメイン別の登録関数——NevercIRPassAPI.RegisterPassNevercTargetAPI.RegisterTargetNevercObjectFormatAPI.RegisterFormat など ——は、いずれも同じ RegistrarContext を第 2 引数に取ります。ホストはこれによっ て、登録をあなたのプラグインに帰属させます。

オブザーバー

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);

PointsNEVERC_OBSERVER_BEFORE(1)、NEVERC_OBSERVER_AFTER(2)、 NEVERC_OBSERVER_AFTER_COMMIT(4)のビットマスクで、非ゼロでなければなりません。 どれが発火したかは引数 Point が伝えます。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 はそのまま返されます。本節のどのディスクリプターにもある DestroyUserData を設定しておくと、登録が消えるときにホストがそのメモリを解放しま す。登録ごとに確保したメモリを 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);

継続(continuation)はチェーンの残り全体であり、結果はあなたがそれに対して何をした かを報告する手段です:

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;

3 つのアクションは互換ではありません。ホストは結果を実際の挙動と突き合わせ、食い違 えば NEVERC_STATUS_POLICY_VIOLATION でチェーンを失敗させます:

Action InvokeNext Output Proof 追加要件
NEVERC_PHASE_CONTINUE 1 回呼ぶ
NEVERC_PHASE_REPLACE 呼ばない 設定 REPLACEABLE
NEVERC_PHASE_SKIP 呼ばない 設定 設定 SKIPPABLE_WITH_PROOF

InvokeNext は多くとも 1 回、しかもコールバックスレッド上でのみ呼べます。2 回目の 呼び出しはポリシー違反であり、別スレッドからの呼び出しは NEVERC_STATUS_WRONG_SCOPE を返します。呼ばずに CONTINUE を返すインターセプター も同じくポリシー違反です。そのフェーズが何も生成しないまま黙って終わってしまうから です。

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);

プロバイダー

プロバイダーはフェーズを丸ごと置き換えるため、ビルドキャッシュが依拠する決定性の契 約もあわせて宣言します:

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 は正規名でなければなりません。最大 255 バイトで、小文字・数字・._- のみを含み、ドットで始まっても終わってもならず、.. を含んでもいけません。 大文字が 1 つあるだけで登録は拒否されます。Route.Header も他のテーブルヘッダーと同 様に初期化が必要です。

ここに継続はありません。コールバックそのものがフェーズです。Output を伴い Proof を空にした NEVERC_PHASE_REPLACE を報告しなければならず、それ以外はすべてポリシー 違反です。

FallbackSafe は、これらのフラグの中で唯一、記録以上の実行時効果を持ちます。これが NEVERC_TRUE で、かつプロバイダーが NEVERC_STATUS_FLAG_RECOVERABLE を立てた ステータスで失敗した場合、ホストは途中までの効果を破棄して組み込み実装を代わりに走 らせることがあります。中途半端な試行をロールバックできないなら NEVERC_FALSE のま まにしてください。

プラグイン定義のフェーズ

RegisterPhase は、ホストが知らない遷移を追加するためのものです。8 つの拡張 ID ファミリーは、まさにこのために予約されています:

typedef struct NevercPhaseDescriptor {
  NevercABITableHeader Header;
  NevercInterfaceID Phase;
  NevercStringView CanonicalName;
  NevercInterfaceID InputArtifact;
  NevercInterfaceID OutputArtifact;
  NevercPhasePolicy Policy;
  NevercObserverPoint ObserverPoints;
  uint32_t Reserved;
} NevercPhaseDescriptor;

PhaseInputArtifactOutputArtifact はいずれも非ゼロでなければならず、Policy も非ゼロかつ既知のフラグだけを含む必要があります。NEVERC_PHASE_OBSERVABLE なしに ObserverPoints を宣言することは拒否され、NEVERC_PHASE_SEALED_HOST_GATEINTERCEPTABLEREPLACEABLESKIPPABLE_WITH_PROOF のいずれかと組み合わせること も拒否されます——組み込みのフェーズグラフが検査されるのと同じ不変条件です。将来の組 み込みフェーズと衝突しないよう、ID は自分のドメインのファミリーから取ってください:

#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};

各ファミリーは _NAMESPACE_ID_HIGH_ID_LOW_MIN_ID_LOW_MAX を公開してお り、下位 64 ビットはその範囲内で自由に割り当てられます。

他のプラグインへインターフェースを公開する

RegisterInterface は、ディスクリプターを取らない唯一の呼び出しです。自分のテーブル をホストに預けることで、別のプラグインが組み込みインターフェースと同じ QueryInterface 経由でそれに到達できるようになります:

Registrar->RegisterInterface(RegistrarContext, MyInterfaceID,
                             NEVERC_INTERFACE_STABLE, &MyTable,
                             /* Compatibility = */ NULL);

バージョンのずれに耐えられないターゲット固有のスキーマ値をテーブルが運ぶ場合は、代 わりに NEVERC_INTERFACE_LOCKSTEP を渡します。lockstep インターフェースは NevercCompatibilityKey を必ず提供しなければならず、これが利用側を特定の生成側ビル ドに固定します:

typedef struct NevercCompatibilityKey {
  NevercABITableHeader Header;
  NevercStringView ProducerBuildID;   /* compare against Bootstrap->HostBuildID */
  NevercStringView TargetABIKey;
  uint32_t LLVMMajor;                 /* compare against Bootstrap->LLVMMajor   */
  uint32_t Reserved;
} NevercCompatibilityKey;

lockstep 登録では 3 つのフィールドすべてを埋める必要があります。ビルド ID が空、ABI キーが空、あるいは LLVM メジャーが 0 の場合は、無効なディスクリプターとして拒否され ます。

ビルド

集約ヘッダー NevercPluginAPI.h を include するか、使うドメインだけを include します:

#include "neverc/Plugin/NevercPluginAPI.h"   /* everything */
#include "neverc/Plugin/PluginIR.h"          /* or one domain */

NeverC 自身で共有モジュールをビルドする:

neverc --target=arm64-apple-macosx -shared \
  -I/path/to/pluginsdk/include \
  -o MyPlugin.dylib MyPlugin.c

インストール済み SDK に対して CMake でビルドする:

find_package(NevercPluginSDK REQUIRED)
add_library(my_plugin MODULE my_plugin.c)
target_link_libraries(my_plugin PRIVATE NevercPluginSDK::headers)

pkg-config を使う:

cc -shared $(pkg-config --cflags neverc-plugin) -o my_plugin.so my_plugin.c

ホストに応じて .so.dylib.dll を使い分けてください。この SDK は LLVM も NeverC ランタイムもリンクしません。NevercPluginSDK::headers はヘッダーオンリー です。

ロードと設定

neverc -fplugin=./MyPlugin.dylib -c input.c -o input.o
オプション 形式 目的
-fplugin=<path> 繰り返し可 フルツールチェーンのプラグイン共有モジュールをロードする。
-fplugin-arg=<plugin-id>:<key>=<value> 繰り返し可 登録済みプラグインオプションに名前空間付きの値を渡す。
-fplugin-provider=<phase>:<plugin-id> 繰り返し可 置換可能フェーズをどのプラグインが提供するかを選ぶ。
-fplugin-pass=<dsopath> 繰り返し可 C-ABI のアウトオブツリーなパスプラグインをロードする。
-fplugin-pass-arg=<key>=<value> 繰り返し可 C-ABI パスプラグインに引数を渡す。

<plugin-id>: 修飾子を省略できるのは、有効なプラグインがちょうど 1 つのときだけ です。プラグインが RegisterOption で登録したオプションは、宣言した綴りで直接 ——flag 形式、joined 形式、separate 形式、複数引数形式で——受け付けられます。対応す る -fplugin= のないプラグイン引数やプロバイダー選択は、黙って無視されるのではな くハードエラーになります。

登録済みのオプションは、いつでも 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);

ABI ルール

  • ケーパビリティテーブルは QueryInterface で取得し、メジャーの一致を要求し、フ ィールドに触れる前に StructSize を確認する。
  • すべての公開構造体の Header と予約領域を初期化する。構造体をゼロ埋めしてから StructSizeMajorMinorFlags を設定する。
  • ハンドルと借用ビューはスコープ付きの不透明値として扱う。タスクスコープのハンド ルをコールバックの外まで保持しない、別のセッションやタスクで使わない、ハンドル 値を自作しない。
  • すべてのコールバックから NevercStatus を返す。C++ 例外やホスト所有のポインタ ーを C 境界越しに漏らさない。
  • 真実であるうちで最も狭い NevercConcurrencyModelSESSION_SERIALTHREAD_SAFEPROCESS_SERIAL)と NevercReentrancyModelNONEALLOWED)を宣言する。
  • IR、MIR、AST、グラフ、アーティファクトの変更は、トランザクショナルなホスト API を通じて行う。変更を開始し、変更をステージし、コミットまたはアボートする。コミ ットは検証と公開をアトミックに行い、失敗したコミットは以前の状態をそのまま残す。
  • ホストにメモリーを計上させたい場合は NevercCoreAPI.Allocate / Reallocate / Deallocate で確保する。
  • 可変状態はホストが提供する process/session/task 状態に置く。グローバルな可変状 態は utils/plugin-api/check-global-state.py が検査する。

公開構造体はすべて NEVERC_ABI_PACK_BEGIN(8 バイトパッキング)の下に配置され、 固定幅の型のみを使います。新しい関数は、独立にバージョン管理されるケーパビリティ テーブルの末尾に追加されます。最初の ABI メジャー(NEVERC_PLUGIN_ABI_MAJOR = 1)の範囲内では、テーブルの安定した前半部分は変わりません。

ステータスと診断

NevercStatusCodeFlagsDetail ワードを持ちます。コードの全体像:

コード 意味
NEVERC_STATUS_OK 成功。
NEVERC_STATUS_INVALID_ARGUMENT 必須のポインターまたは値が欠けているか不正。
NEVERC_STATUS_ABI_MISMATCH ネゴシエートされたテーブルが小さすぎるか、メジャーが異なる。
NEVERC_STATUS_MISSING_INTERFACE ホストが要求されたインターフェースを公開していない。
NEVERC_STATUS_VERSION_MISMATCH 要求されたメジャー/マイナーを満たせない。
NEVERC_STATUS_INVALID_DESCRIPTOR ディスクリプターが構造検証に失敗した。
NEVERC_STATUS_DUPLICATE_ID その ID はすでに登録済み。
NEVERC_STATUS_DEPENDENCY_MISSING 宣言された依存関係が存在しない。
NEVERC_STATUS_DEPENDENCY_CYCLE 登録順序を満たせない。
NEVERC_STATUS_BUSY リソースが他所で保持されている。
NEVERC_STATUS_CANCELLED 協調的キャンセルが要求された。
NEVERC_STATUS_RESOURCE_EXHAUSTED 予算または上限に達した。
NEVERC_STATUS_STALE_HANDLE ハンドルが指す対象より長く生き残った。
NEVERC_STATUS_WRONG_SESSION ハンドルが別のセッションで使われた。
NEVERC_STATUS_WRONG_SCOPE ハンドルがスコープ外で使われた。
NEVERC_STATUS_WRONG_TYPE ハンドルが別種のエンティティを指していた。
NEVERC_STATUS_INVALID_STATE 現在の状態でその操作は不正。
NEVERC_STATUS_POLICY_VIOLATION フェーズポリシーがその操作を禁じている。
NEVERC_STATUS_VERIFICATION_FAILED 封印されたホスト検証器が成果物を拒否した。
NEVERC_STATUS_CAPABILITY_UNAVAILABLE ホストはここでその機能を提供できない。
NEVERC_STATUS_PLUGIN_FAILURE プラグインが一般的な失敗を報告した。
NEVERC_STATUS_PLUGIN_EXCEPTION プラグインのコールバックから例外が漏れた。
NEVERC_STATUS_OUTPUT_PARTIAL 出力が部分的にしか書かれなかった。
NEVERC_STATUS_REENTRANCY_DENIED 再入呼び出しが拒否された。
NEVERC_STATUS_NOT_FOUND 指定されたエンティティが存在しない。

フラグビットは出力に何が起きたかを表します。これはビルドシステムがリトライの安全 性を判断するために必要な情報です: NEVERC_STATUS_FLAG_RECOVERABLE_OUTPUT_ALREADY_COMMITTED_OUTPUT_MAY_BE_PARTIAL_OUTPUT_RECOVERY_REQUIRED_DURABILITY_UNCONFIRMED

問題の報告には NevercCoreAPI.EmitDiagnostic と、重大度(NOTEREMARKWARNINGERRORFATAL)・コード・プラグイン ID・フェーズ ID・メッセージ・ 注記・ソース位置・範囲・fix-it を持つ NevercDiagnosticDescriptor を使います。 高コストな処理の前には CheckCancelled を呼んでください。

サンプル

すべてビルドする:

cmake --build build-neverc --target neverc-pluginsdk-examples

各サンプルは 2 回コンパイルされます——1 回は設定されたホスト C コンパイラーで、も う 1 回はビルドしたばかりの NeverC で——したがって ABI が両側から証明されます。モ ジュールは build-neverc/neverc/pluginsdk/examples/host/ に生成されます。

サンプル CMake ターゲット 示す内容
DriverTracePlugin.c neverc-plugin-example-driver-trace オプション登録、フェーズ観測、ジョブのインターセプト
VirtualHeaderPlugin.c neverc-plugin-example-virtual-header メモリー内ヘッダーを提供する VFS プロバイダー
ASTRewritePlugin.c neverc-plugin-example-ast-rewrite パーサーのインターセプトとアトミックな AST 変更
ExamplePlugin.c neverc-plugin-example-ir-overview 値カーソルで関数リストを歩くモジュールレベル IR パス
FunctionPass.c neverc-plugin-example-function-pass 安定した IR 関数パス
MachinePass.c neverc-plugin-example-machine-pass pre-emit フックに置く安定した MIR パス
MCObserverPlugin.c neverc-plugin-example-mc-observer 読み取り専用の MC 出力イベント
ObjectRewritePlugin.c neverc-plugin-example-object-rewrite トランザクショナルな ObjectGraph 書き換え
CustomCallConvPlugin.c neverc-plugin-example-custom-callconv データ駆動の呼び出し規約
DynCodeTracePlugin.c neverc-plugin-example-dyncode-trace dyncode パイプラインの観測
DynCodeEncoderPlugin.c neverc-plugin-example-dyncode-encoder dyncode の文字セットエンコードのインターセプト
CrtShimPlugin.c neverc-plugin-example-crt-shim CRT 依存ゼロのプラグイン
BenchPlugin.c neverc-plugin-example-abi-bench ABI 呼び出しスループットのマイクロベンチマーク

1 つロードしてみる:

neverc -fplugin=build-neverc/neverc/pluginsdk/examples/host/FunctionPass.so \
  -O2 -c input.c -o input.o

規範的な出典

ファイル 保証する内容
neverc/include/neverc/Plugin/Schema/PhaseSchema.json フェーズ ID、ポリシー、安定性、検証器ゲート
pluginsdk/manifest/plugin.json ABI バージョン、インターフェース ID/バージョン/安定性、スキーマダイジェスト、対応ターゲット
pluginsdk/abi/plugin.json ホスト ABI キーごとの、全公開構造体の実測サイズ・アライメント・フィールドオフセット
docs/plugin-api/coverage.json 各安定フェーズを、肯定・否定・置換・オブザーバー・封印ゲートのテストに対応付ける

したがって SDK はホストに対して機械的に検証でき、プラグインのビルドは、それがロー ドされる先の ABI キーに対して自身の構造体レイアウトをアサートできます。