Skip to content

Latest commit

 

History

History
452 lines (362 loc) · 21.6 KB

File metadata and controls

452 lines (362 loc) · 21.6 KB

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

← واجهة NeverC الثنائية للإضافات

واجهة التمثيل الوسيط لإضافات NeverC

تكشف PluginIR.h تمثيلَ LLVM الوسيط عبر ستة جداول قدرات ومخطَّطٍ مولَّد. تستطيع الإضافة أن تقرأ التمثيل الوسيط وتعيد كتابته، وأن تسجّل تمريرات في خمس نقاط مستقرة من خط الأنابيب، وأن تعرّف تحليلاتها الخاصة، أو أن تستبدل توليدَ التمثيل الوسيط وخط أنابيب التحسين بالكامل — دون أن تضمّ ملفًا ترويسيًا واحدًا من LLVM.

رموز العمليات وأنواع الأنماط وخصائص التعليمات كلها معرّفات مخطَّط مستقرة، لا قيم تعدادية من LLVM. وهذه الوساطة هي ما يجعل إضافةً تُترجم اليوم تظل عاملةً حين ينتقل المضيف إلى إصدار LLVM جديد.

الواجهات

#include "neverc/Plugin/PluginIR.h"
الواجهة الجدول الفتحات الغرض
NEVERC_INTERFACE_IR_CORE_{HIGH,LOW} NevercIRCoreAPI 99 قراءة وتحرير الوحدات والقيم والأنواع والثوابت والبيانات الوصفية والسمات
NEVERC_INTERFACE_IR_BUILDER_{HIGH,LOW} NevercIRBuilderAPI 29 البناء المعامَلاتي
NEVERC_INTERFACE_IR_ANALYSIS_{HIGH,LOW} NevercIRAnalysisAPI 13 التحليلات المدمجة وتحليلات الإضافات
NEVERC_INTERFACE_IR_PASS_{HIGH,LOW} NevercIRPassAPI 1 RegisterPass
NEVERC_INTERFACE_IR_GEN_{HIGH,LOW} NevercIRGenAPI 5 استبدال الخفض من SemanticUnit إلى التمثيل الوسيط
NEVERC_INTERFACE_IR_OPTIMIZATION_{HIGH,LOW} NevercIROptimizationAPI 7 استبدال خط أنابيب التحسين بأسره

كلٌّ منها NEVERC_INTERFACE_STABLE عند الرقم الرئيسي 1. تفاوض باستخدام NEVERC_IR_*_API_MAJOR / _MINOR المقابلة، وتحقّق أن TableSize يبلغ آخر فتحة تستدعيها، تمامًا كما يفعل pluginsdk/examples/FunctionPass.c:

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,
    &StructSize);
if (!Table ||
    StructSize < offsetof(NevercIRPassAPI, RegisterPass) +
                     sizeof(((NevercIRPassAPI *)0)->RegisterPass))
  return fail(NEVERC_STATUS_ABI_MISMATCH);

المراحل

ثماني مراحل للتمثيل الوسيط:

المرحلة السياسة
neverc.ir.generate OBSERVABLE، INTERCEPTABLE، REPLACEABLE
neverc.ir.optimize OBSERVABLE، INTERCEPTABLE، REPLACEABLE
neverc.ir.pass.pre_opt OBSERVABLE، INTERCEPTABLE
neverc.ir.pass.pipeline_start OBSERVABLE، INTERCEPTABLE
neverc.ir.pass.optimizer_last OBSERVABLE، INTERCEPTABLE
neverc.ir.pass.post_opt OBSERVABLE، INTERCEPTABLE
neverc.ir.pass.pre_codegen OBSERVABLE، INTERCEPTABLE
neverc.ir.final_verify OBSERVABLE، بوابة مضيف مختومة

مراحل pass.* الخمس هي ما يشير إليه NevercIRPassDescriptor.Phase. وتشغّل neverc.ir.final_verify مدقّقَ LLVM، ولا يستطيع أي شيء اعتراضها أو استبدالها أو تخطّيها — بما في ذلك مزوّد التحسين.

المخطَّط

Schema/PluginIRSchema.inc مولَّد ويضمّه PluginIR.h. وهو ينشر بصمةً ومجموعات الثوابت التالية:

#define NEVERC_IR_SCHEMA_CAPABILITY_MAJOR   UINT16_C(1)
#define NEVERC_IR_SCHEMA_DIGEST             "4302919d…"
#define NEVERC_IR_TYPE_KIND_COUNT           UINT32_C(22)
#define NEVERC_IR_VALUE_KIND_COUNT          UINT32_C(29)
#define NEVERC_IR_OPCODE_COUNT              UINT32_C(67)
#define NEVERC_IR_PREDICATE_COUNT           UINT32_C(26)
#define NEVERC_IR_LINKAGE_COUNT             UINT32_C(11)
#define NEVERC_IR_CALLING_CONVENTION_COUNT  UINT32_C(21)
#define NEVERC_IR_PROPERTY_COUNT            UINT32_C(23)

تُوسَم المعرّفات بمجالها في البايت الأعلى — 0x41…… للأنواع، و0x42…… لأنواع القيم، و0x43…… لرموز العمليات، و0x49…… للخصائص — فالقيمة المستعملة في غير موضعها تُرفض بدل أن تُقرأ خطأً.

المقابض والملكية

مقابض التمثيل الوسيط أزواج {Owner, Value} معتِمة نطاقها مهمة واحدة، وكل ما وراءها مملوك للمضيف.

  • لا تحتفظ بمقبض بعد انتهاء ردّ ندائه أو مهمته.
  • لا تستعمل مقبضًا في جلسة أو مهمة أخرى.
  • الاستبدال المودَع يُبطل مقابض الأشياء التي استُبدلت.
  • التعديل المُجهَض يجعل المقابض التي أنشأها بائدة.
  • الأخطاء هي NEVERC_STATUS_STALE_HANDLE أو WRONG_SCOPE أو WRONG_TYPE — ولن يكون الناتج أبدًا مؤشر LLVM خامًا.

السلاسل ورؤى البايتات العائدة من استعلامٍ ما مستعارة طوال ردّ النداء. والاستثناء الوحيد هو ExportModule، فهو يعيد NevercIRSerializedBufferHandle يجب أن تعيده إلى ReleaseSerializedBuffer.

اجتياز وحدة

تُقرأ المجموعات عبر مؤشر يحمل جيلَه الخاص، فيُكتشف أي تعديل يقع في منتصف الاجتياز بدل أن تُتخطّى المدخلات صمتًا:

NevercIRValueCursor Cursor = {0};
Cursor.Header = (NevercABITableHeader){sizeof(Cursor),
                                       NEVERC_IR_CORE_API_MAJOR,
                                       NEVERC_IR_CORE_API_MINOR, 0};
Core->BeginValueCursor(Core->Context, Task, Module,
                       NEVERC_IR_COLLECTION_MODULE_FUNCTIONS, &Cursor);

NevercIRValueHandle Batch[32];
uint64_t Count = 0;
for (;;) {
  Core->CollectValueCursor(Core->Context, Task, &Cursor, Batch, 32, &Count);
  if (Count == 0)
    break;
  for (uint64_t I = 0; I != Count; ++I) {
    NevercStringView Name;
    Core->GetValueName(Core->Context, Task, Batch[I], &Name);
  }
}

كرّر حتى يعود Count صفرًا. والمجموعات السبع هي MODULE_FUNCTIONS وMODULE_GLOBALS وMODULE_ALIASES وMODULE_I_FUNCS وFUNCTION_ARGUMENTS وFUNCTION_BLOCKS وBLOCK_INSTRUCTIONS.

وما عدا ذلك استعلامٌ مباشر: GetValueKind وGetValueType وGetOperandCount / GetOperand / SetOperand وGetValueUseCount / GetValueUse وGetTerminator وGetPredecessor* وGetSuccessor* وGetPHIIncoming*، وعلى مستوى الوحدة GetModuleIdentifier وGetModuleTargetTriple وGetModuleDataLayout وGetModuleInlineAssembly مع ضوابطها.

الأنواع والثوابت

الأنواع مُدمَجة داخليًا، فالسؤال مرتين يعطي المقبض نفسه:

NevercIRTypeHandle I32, Ptr, Fn;
Core->GetIntegerType(Core->Context, Task, 32, &I32);
Core->GetPointerType(Core->Context, Task, /*AddressSpace=*/0, &Ptr);

NevercIRTypeHandle Params[] = {I32, Ptr};
Core->GetFunctionType(Core->Context, Task, I32, Params, 2,
                      /*Variadic=*/0, &Fn);

تأخذ GetPrimitiveType نوعَ مخطَّط مثل NEVERC_IR_TYPE_VOID أو _FLOAT أو _DOUBLE أو _TOKEN، بينما تغطي البقيةَ GetArrayType وGetVectorType (مع راية Scalable) وGetStructType (مسمّاة أو حرفية، محزومة أو لا).

تُبنى الثوابت الصحيحة والعائمة من كلمات 64 بتًا صغيرة النهاية، فلا يحتاج i128 مسارًا خاصًا:

uint64_t Words[2] = {0xFFFFFFFFFFFFFFFFULL, 0x1ULL};
NevercIRValueHandle C;
Core->CreateIntegerConstant(Core->Context, Task, I128, Words, 2, &C);

وتغطي GetNullConstant وGetPoisonConstant وGetUndefConstant وCreateAggregateConstant وGetGlobalAddressConstant الحالاتِ البسيطة، بينما تبني CreateConstantBinaryExpression وCreateConstantCastExpression وCreateConstantCompareExpression وCreateConstantGEPExpression التعابيرَ الثابتة.

خصائص التعليمات

بدل موصِّل لكل راية، تمرّ تفاصيل التعليمة عبر قيمة خاصية موسومة مفتاحُها معرّف المخطَّط:

typedef struct NevercIRPropertyValue {
  NevercABITableHeader Header;
  NevercIRPropertyValueKind Kind;   /* BOOL, UINT, ENUM, FLAGS, STRING, TYPE */
  uint32_t Reserved;
  uint64_t UnsignedValue;
  NevercIRTypeHandle TypeValue;
  NevercStringView StringValue;
} NevercIRPropertyValue;

NevercIRPropertyValue Value = {0};
Value.Header = /* … */;
Core->GetInstructionProperty(Core->Context, Task, Instruction,
                             NEVERC_IR_PROPERTY_ALIGNMENT, &Value);

والخصائص الـ23 هي NAME وFAST_MATH_FLAGS وNUW وNSW وEXACT وDISJOINT وVOLATILE وALIGNMENT وATOMIC_ORDERING وSYNC_SCOPE وPREDICATE وCALLING_CONVENTION وTAIL_CALL_KIND وINDICES وWEAK وSUCCESS_ORDERING وFAILURE_ORDERING وINBOUNDS وSOURCE_ELEMENT_TYPE وALLOCATED_TYPE وATTRIBUTES وCLEANUP وNUSW. وتمتد الترتيبات الذرّية من NOT_ATOMIC إلى SEQUENTIALLY_CONSISTENT، وأنواع نداء الذيل هي NONE وTAIL وMUST_TAIL وNO_TAIL، ورايات fast-math هي البتات السبع المعتادة من ALLOW_REASSOC إلى APPROX_FUNC.

السمات

السمات قيمٌ تنشئها ثم تُرفقها، وهذا ما يجعل الأنواع الأربعة (ENUM وINTEGER وSTRING وTYPE) متسقة:

NevercIRAttributeHandle NoInline;
Core->CreateEnumAttribute(Core->Context, Task, SV("noinline"), &NoInline);
Core->AddFunctionAttribute(Core->Context, Task, Function,
                           NEVERC_IR_ATTRIBUTE_LOCATION_FUNCTION,
                           /*ParameterIndex=*/0, NoInline);

NevercBool Present = NEVERC_FALSE;
Core->HasFunctionAttribute(Core->Context, Task, Function, SV("noinline"),
                           &Present);

ويستعمل pluginsdk/examples/CustomCallConvPlugin.c هذا مع GetFunctionStringAttribute لقيادة اصطلاح نداءٍ معرَّف بالبيانات.

التعديل المعامَلاتي

يمرّ كل تغيير بنيوي عبر NevercIRBuilderAPI. التعديل هو المعامَلة، والباني مؤشر داخلها.

NevercIRMutationHandle Mutation;
NevercIRBuilderHandle Builder;

Builders->BeginMutation(Builders->Context, Task,
                        NEVERC_IR_MUTATION_SCOPE_FUNCTION, Function,
                        &Mutation);
Builders->CreateBuilder(Builders->Context, Task, Mutation, &Builder);
Builders->SetInsertBefore(Builders->Context, Task, Builder, Terminator);

NevercIRValueHandle Sum;
Builders->BuildBinary(Builders->Context, Task, Builder,
                      NEVERC_IR_OPCODE_ADD, Left, Right, SV("sum"), &Sum);

Status = Builders->CommitMutation(Builders->Context, Task, Mutation);
if (Status.Code != NEVERC_STATUS_OK)
  Builders->AbortMutation(Builders->Context, Task, Mutation);

Builders->DestroyBuilder(Builders->Context, Task, Builder);
Builders->DestroyMutation(Builders->Context, Task, Mutation);

النطاقات هي NEVERC_IR_MUTATION_SCOPE_MODULE و_FUNCTION و_LOOP، ويسمّي ScopeRoot الدالةَ أو ترويسةَ الحلقة. والإيداع يتحقق من المرشَّح وينشر ذريًّا — فإذا أخفق المدقّق تراجع المضيفُ وبقيت الوحدة السابقة دون مساس.

ونداءات البناء هي BuildBinary وBuildUnary وBuildCompare وBuildCast وBuildSelect وBuildAlloca وBuildLoad وBuildStore وBuildGetElementPtr وBuildCall وBuildPhi وBuildBranch وBuildConditionalBranch وBuildUnreachable وBuildReturn وBuildReturnVoid. وتنطبق SetDebugLocation وSetFastMathFlags على كل ما يصدره الباني بعدهما.

لاحظ عدم التناظر: تأخذ AddPhiIncoming وCreateFunction وCreateBasicBlock التعديلَ لا الباني، لأنها غير مرتبطة بموضع إدراج.

وDestroyMutation منفصل عن الإيداع والإجهاض. فكل BeginMutation يحتاج DestroyMutation واحدًا بالضبط، أيًّا كانت النهاية التي بلغتها المعامَلة.

التمريرات

NevercIRPassDescriptor Pass = {0};
Pass.Header = (NevercABITableHeader){sizeof(Pass), NEVERC_IR_PASS_API_MAJOR,
                                     NEVERC_IR_PASS_API_MINOR, 0};
Pass.PassID        = SV("example.function-pass");
Pass.Phase         = (NevercInterfaceID){
                         NEVERC_PHASE_IR_PASS_PIPELINE_START_HIGH,
                         NEVERC_PHASE_IR_PASS_PIPELINE_START_LOW};
Pass.Level         = NEVERC_IR_PASS_LEVEL_FUNCTION;
Pass.Deterministic = NEVERC_TRUE;
Pass.Cacheable     = NEVERC_TRUE;
Pass.Run           = run_function;
PassAPI->RegisterPass(PassAPI->Context, RegistrarContext, &Pass);

المستويات هي MODULE وCGSCC وFUNCTION وLOOP. ولا يحمل النداء إلا المقابض الصالحة لمستواه:

typedef struct NevercIRPassInvocation {
  NevercABITableHeader Header;
  NevercTaskHandle Task;
  NevercInterfaceID Phase;
  NevercStringView PassID;
  NevercIRPassLevel Level;
  NevercIROptimizationLevel OptimizationLevel;  /* O0…O3, Os, Oz */
  NevercIRModuleHandle Module;
  NevercIRValueHandle Function;                 /* FUNCTION و LOOP       */
  NevercIRValueHandle LoopHeader;               /* LOOP فقط              */
  const NevercIRValueHandle *SCCFunctions;      /* CGSCC فقط             */
  uint64_t SCCFunctionCount;
  const NevercIRCoreAPI *Core;
  const NevercIRBuilderAPI *Builder;
  const NevercIRAnalysisAPI *Analyses;
  uint64_t Reserved[2];
} NevercIRPassInvocation;

تأتي مؤشرات الواجهات الثلاثة مع النداء، فلا يحتاج جسمُ التمريرة جدولًا مخزّنًا.

وأبلغ عمّا نجا عبر OutPreserved:

OutPreserved->Flags = NEVERC_IR_PRESERVE_ALL;   /* أو _NONE، أو _CFG */

NEVERC_IR_PRESERVE_CFG يعني أن مخطط تدفق التحكم سليم وإن تغيّرت التعليمات. وتُحفظ التحليلات المخصّصة بإدراجها في CustomAnalyses. ولا تدّعِ PRESERVE_ALL بعد تغيير التمثيل الوسيط — فالمهايئ يقارن جيلَ الوحدة ويرفض الادّعاء الكاذب.

وقد تعمل تمريرات الدوال والحلقات على التوازي، فيجب أن توافق حالةُ الإضافة القابلة للتغيير نموذجَ NevercConcurrencyModel الذي أعلنته الإضافة.

التحليلات

سبعة تحليلات مدمجة قابلة للاستعلام بالمعرّف: DOMINATOR_TREE وPOST_DOMINATOR_TREE وLOOP_INFO وSCALAR_EVOLUTION وMEMORY_SSA وCALL_GRAPH وALIAS.

NevercIRAnalysisResultHandle Loops;
Analyses->QueryBuiltin(Analyses->Context, Task,
                       NEVERC_IR_ANALYSIS_LOOP_INFO, Function, &Loops);

uint64_t LoopCount = 0;
Analyses->GetLoopCount(Analyses->Context, Task, Loops, &LoopCount);
for (uint64_t I = 0; I != LoopCount; ++I) {
  NevercIRValueHandle Header;
  Analyses->GetLoopHeader(Analyses->Context, Task, Loops, I, &Header);
}

ولكلٍّ منها موصِّلات مُنمّطة بدل كتلة معتِمة: DominatorTreeDominates وGetLoopCount / GetLoopHeader / GetLoopForBlock وGetScalarEvolutionConstantTripCount وGetMemoryAccessKind (NONE وUSE وDEF وPHI وLIVE_ON_ENTRY) وGetDirectCalleeCount / GetDirectCallee وAlias (NO وMAY وPARTIAL وMUST).

ويُسجَّل تحليل الإضافة بدورة حياته الخاصة:

NevercIRAnalysisDescriptor Analysis = {0};
Analysis.Header          = /* … */;
Analysis.AnalysisID      = MyAnalysisID;
Analysis.Name            = SV("example.my-analysis");
Analysis.Level           = NEVERC_IR_PASS_LEVEL_FUNCTION;
Analysis.Dependencies    = Deps;
Analysis.DependencyCount = DepCount;
Analysis.Compute         = compute;
Analysis.Query           = query;
Analysis.Invalidate      = invalidate;
Analysis.Destroy         = destroy;
Analyses->RegisterAnalysis(Analyses->Context, RegistrarContext, &Analysis);

ويُخبَر Invalidate بالسبب — INVALIDATED_BY_PASS أو INVALIDATED_BY_PLAN_DESTROY. وتُخزَّن النتائج مؤقتًا لكل نداء وتُسقَط بحسب ما حفظته التمريرةُ الجارية. وتُرفض حلقاتُ التبعية عند التسجيل، كما يُرفض تعديل التمثيل الوسيط من داخل ردّ نداء تحليل.

استبدال التوليد والتحسين

تستبدل NevercIRGenAPI المرحلةَ neverc.ir.generate:

NevercIRGeneratePhaseInput In = {0};
In.Header = /* … */;
Gen->GetGeneratePhaseInput(Gen->Context, Frame, Frame->Input, &In);
/* In.SemanticUnit و.TargetTriple و.DataLayout و.SourceIdentity و
   .SourceDigest */

const NevercIRCoreAPI *Core;
const NevercIRBuilderAPI *Builders;
Gen->CreateModule(Gen->Context, Frame, SV("my.module"), &Core, &Builders);
/* … ابنِ الوحدة … */

NevercIRModuleArtifactDescriptor Descriptor = {0};
Descriptor.Header           = /* … */;
Descriptor.Product          = MyProductID;
Descriptor.DependencyDigest = Digest;
Gen->PublishModule(Gen->Context, Frame, &Descriptor, &Output);

وتبدأ ImportModule من bitcode أو تمثيل وسيط نصّي بدل وحدة فارغة. ولـ NevercIROptimizationAPI الشكلُ نفسه للمرحلة neverc.ir.optimize، مضافًا إليه GetInputModule للوصول إلى الوحدة الداخلة، وRunBuiltinPipeline لتفويض خط الأنابيب المدمج ثم معالجة نتيجته لاحقًا.

كلا المسارين ينشران عبر المضيف بدل إعادة مؤشر، ويتحققان من توافق الهدف، ويحفظان الوحدةَ القديمة ذريًّا إن أخفق النشر. وتظل neverc.ir.final_verify تعمل بعد ذلك.

أمثلة

الملف ما يعرضه
pluginsdk/examples/FunctionPass.c تمريرة دالة للقراءة فقط، مع مفاوضة ABI
pluginsdk/examples/ExamplePlugin.c تمريرة على مستوى الوحدة تجتاز الدوال بمؤشر قيم
pluginsdk/examples/CustomCallConvPlugin.c السمات وخصائص موضع النداء
cmake --build build-neverc --target neverc-plugin-example-function-pass
build-neverc/bin/neverc \
  -fplugin=build-neverc/neverc/pluginsdk/examples/host/FunctionPass.so \
  -O2 -c input.c -o input.o

استعمل لاحقة الوحدة التي أنتجها CMake لمنصّتك.

القواعد

  • أعِد NevercStatus من كل ردّ نداء. فإخفاق الإضافة يصير تشخيصًا مُهيكلًا، ولا تدع استثناءً يعبر حدّ لغة C أبدًا.
  • صفّر كل بنية خرج واضبط Header فيها قبل النداء الذي يملؤها.
  • لا تكتب أعدادًا حرفية لرموز العمليات أو الأنواع أو الخصائص. استعمل أسماء PluginIRSchema.inc كي تصير مراجعةُ المخطَّط خطأ ترجمة.
  • كل BeginMutation يبلغ DestroyMutation واحدًا بالضبط، وكل CreateBuilder يبلغ DestroyBuilder واحدًا بالضبط، على مسارات الخطأ أيضًا.
  • حرّر ما تسلّمه من ExportModule بـReleaseSerializedBuffer.
  • لا تدّعِ NEVERC_IR_PRESERVE_ALL بعد تعديل التمثيل الوسيط.
  • افترض أن تمريرات الدوال والحلقات تعمل على التوازي، ما لم تعلن الإضافة NEVERC_CONCURRENCY_SESSION_SERIAL.
  • neverc.ir.final_verify مختومة. ولا شيء تفعله إضافةٌ يمكنه تخطّيها.

انظر PluginIR.h وSchema/IRSchema.json وSchema/PluginIRSchema.inc وSchema/PhaseSchema.json وcoverage.json للتصريحات المعيارية وللمخطَّط نفسه ولثوابته المولَّدة وسياسات المراحل وأدلة الاختبارات.