Skip to content

Latest commit

 

History

History
758 lines (634 loc) · 39.3 KB

File metadata and controls

758 lines (634 loc) · 39.3 KB

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

← 문서 색인 · ← NeverC 프로젝트

NeverC 플러그인 ABI

NeverC 플러그인은 함수를 정확히 하나만 내보내고, 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 토큰, 매크로, pragma, include, 기능 질의, 39가지 이벤트
AST와 의미 분석 API 파서 확장, AST 변경, 이름 조회, 타입, 상수
IR API LLVM IR 읽기, 트랜잭션 기반 구성, 분석, 패스, 프로바이더
MIR API 머신 함수, 레지스터, 스택 프레임, MIR 패스와 분석
타깃, MC, 어셈블리, 오브젝트 타깃 등록, 호출 규약, MC 인코딩, 오브젝트 그래프
링크와 LTO API 링크 그래프, 심볼 결정, GC/ICF, 링커와 LTO 프로바이더
DynCode API 평평한 위치 독립 이미지, 임포트 로워링, 문자셋 인코딩
사용자 정의 호출 규약 데이터 주도 호출 규약 플러그인
페이즈 커버리지 근거 모든 안정 페이즈에 대한 테스트 매핑

실행 모델

호스트는 3중으로 중첩된 스코프를 통해 플러그인을 구동합니다. 각 스코프는 플러그인 이 직접 할당하고 소유하는 불투명 상태 포인터를 플러그인에 건네줍니다. 따라서 올바 르게 작성된 플러그인에는 전역 가변 상태가 필요 없습니다.

스코프 콜백 의미
Process ProcessBegin, Register, Destroy 컴파일러 프로세스 하나. 여기서 인터페이스를 질의하고 기능을 등록합니다.
Session SessionBegin, SessionEnd 드라이버 호출 한 번.
Task TaskBegin, TaskEnd 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;

실질적으로 필수인 것은 PluginIDRegister뿐이며, 모든 콜백 슬롯은 NULL로 남겨 두어도 됩니다. 태스크 종류는 NEVERC_TASK_INVOCATION, TRANSLATION_UNIT, LTO, LINK, CODEGEN, OBJECT, DYNCODE입니다.

호스트는 먼저 ProcessBegin을 호출하고, 이어서 Register를 정확히 한 번 호출합 니다. 옵션, 옵저버, 인터셉터, 프로바이더를 추가할 수 있는 곳은 등록 시점뿐이며, 그 뒤로 페이즈 그래프는 고정됩니다.

상태는 미리 붙잡아 두는 것이 아니라 콜백 안에서 가져옵니다:

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_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, dyncode.commit입니다. 관찰은 할 수 있지만 가로채기, 교체, 건너뛰기는 결코 할 수 없습니다.

옵저버는 페이즈가 선언한 시점에 전달됩니다: NEVERC_OBSERVER_BEFORE, NEVERC_OBSERVER_AFTER, NEVERC_OBSERVER_AFTER_COMMIT. 인터셉터는 NevercPhaseContinuation을 받으며, 콜백 스레드에서 InvokeNext많아야 한 번 호출한 뒤 NevercPhaseResult.ActionNEVERC_PHASE_CONTINUE, NEVERC_PHASE_REPLACE, NEVERC_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와 그것이 포함하는 생성물 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_* 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_* IR 테이블 6개 PluginIR.h
NEVERC_INTERFACE_TARGET_*, ..._TARGET_ABI_*, ..._CALLING_CONVENTION_* NevercTargetAPI, NevercTargetABIAPI, NevercCallingConventionAPI PluginTarget.h
NEVERC_INTERFACE_MIR_*, ..._MIR_ANALYSIS_*, ..._MIR_PASS_*, ..._MIR_PROVIDER_* MIR 테이블 4개 PluginMIR.h
NEVERC_INTERFACE_MC_*, ..._MC_EMISSION_*, ..._MC_PROVIDER_*, ..._ASSEMBLY_PROVIDER_* MC 테이블 4개 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_* NevercLTOAPI, NevercLTORegistrarAPI PluginLTO.h
NEVERC_INTERFACE_DYNCODE_*, ..._DYNCODE_REGISTRAR_*, ..._DYNCODE_PHASE_* dyncode 테이블 3개 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;

이들 호출은 모두 첫 번째 인자로 RegistrarContext를, 두 번째 인자로 0으로 초기화한 디스크립터를 받습니다. 어느 것을 호출하는지가 해당 페이즈에서 호스트가 여러분을 어떻게 대할지를 결정합니다:

호출 디스크립터 콜백 페이즈가 선언해야 하는 정책
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.RegisterPass, NevercTargetAPI.RegisterTarget, NevercObjectFormatAPI.RegisterFormat 등——는 모두 같은 RegistrarContext를 두 번째 인자로 받습니다. 호스트는 이를 통해 등록을 여러분의 플러그인에 귀속시킵니다.

옵저버

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)의 비트마스크이며 0이 아니어야 합니다. 어느 것이 발생했는지는 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);

컨티뉴에이션은 체인의 나머지 전부이고, 결과는 그것에 대해 무엇을 했는지 보고하는 수단입니다:

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;

세 가지 액션은 서로 바꿔 쓸 수 없습니다. 호스트는 결과를 실제 동작과 대조하여 어긋나면 NEVERC_STATUS_POLICY_VIOLATION으로 체인을 실패시킵니다:

Action InvokeNext Output Proof 추가 요건
NEVERC_PHASE_CONTINUE 한 번 호출 비어 있음 비어 있음
NEVERC_PHASE_REPLACE 호출하지 않음 설정 비어 있음 REPLACEABLE
NEVERC_PHASE_SKIP 호출하지 않음 설정 설정 SKIPPABLE_WITH_PROOF

InvokeNext는 많아야 한 번, 그리고 콜백 스레드에서만 호출할 수 있습니다. 두 번째 호출은 정책 위반이고, 다른 스레드에서의 호출은 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바이트이며 소문자, 숫자, ., _, -만 포함하고, 점으로 시작하거나 끝날 수 없으며 ..를 담을 수 없습니다. 대문자가 하나만 있어도 등록은 거부됩니다. 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;

Phase, InputArtifact, OutputArtifact는 모두 0이 아니어야 하고, Policy도 0이 아니면서 알려진 플래그만 담아야 합니다. NEVERC_PHASE_OBSERVABLE 없이 ObserverPoints를 선언하면 거부되며, NEVERC_PHASE_SEALED_HOST_GATEINTERCEPTABLE, REPLACEABLE, SKIPPABLE_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 등록에서는 세 필드를 모두 채워야 합니다. 빌드 ID가 비었거나, ABI 키가 비었거나, LLVM 메이저가 0이면 유효하지 않은 디스크립터로 거부됩니다.

빌드

통합 헤더 NevercPluginAPI.h 를 포함하거나, 사용하는 도메인만 포함하세요:

#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>: 한정자를 생략할 수 있는 것은 활성 플러그인이 정확히 하나일 때뿐입 니다. 플러그인이 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와 예약 영역을 초기화한다. 구조체를 0으로 채운 뒤 StructSize, Major, Minor, Flags를 설정한다.
  • 핸들과 빌린 뷰는 스코프가 있는 불투명 값으로 다룬다. 태스크 스코프 핸들을 콜백 이후까지 보관하지 말고, 다른 세션이나 태스크에서 쓰지 말며, 핸들 값을 지어내지 않는다.
  • 모든 콜백에서 NevercStatus를 반환한다. C++ 예외나 호스트 소유 포인터가 C 경계 를 넘지 않게 한다.
  • 사실에 부합하는 가장 좁은 NevercConcurrencyModel(SESSION_SERIAL, THREAD_SAFE, PROCESS_SERIAL)과 NevercReentrancyModel(NONE, ALLOWED) 을 선언한다.
  • 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) 안에서는 테이블의 안정된 앞부분 이 바뀌지 않습니다.

상태와 진단

NevercStatusCode, Flags, Detail 워드를 담습니다. 코드 전체 집합:

코드 의미
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과, 심각도(NOTE, REMARK, WARNING, ERROR, FATAL)·코드·플러그인 ID·페이즈 ID·메시지·노트·소스 위치·범 위·fix-it을 담은 NevercDiagnosticDescriptor를 사용하세요. 비용이 큰 작업 전에는 CheckCancelled를 호출하세요.

예제

전부 빌드하기:

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

모든 예제는 두 번 컴파일됩니다——한 번은 설정된 호스트 C 컴파일러로, 또 한 번은 방 금 빌드한 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 호출 처리량 마이크로벤치마크

하나 로드해 보기:

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 키에 대해 구조체 레이아웃을 단언할 수 있습니다.