Working with LLVM
هذا ليس بديلاً عن وثائق LLVM، بل هو مجموعة من النصائح للعمل على LLVM لجوليا.
Overview of Julia to LLVM Interface
تقوم جوليا بربط ديناميكي مع LLVM بشكل افتراضي. قم بالبناء باستخدام USE_LLVM_SHLIB=0
للربط الثابت.
الكود الخاص بتحويل شجرة التركيب (AST) في جوليا إلى LLVM IR أو تفسيرها مباشرة موجود في الدليل src/
.
File | Description |
---|---|
aotcompile.cpp | Compiler C-interface entry and object file emission |
builtins.c | Builtin functions |
ccall.cpp | Lowering ccall |
cgutils.cpp | Lowering utilities, notably for array and tuple accesses |
codegen.cpp | Top-level of code generation, pass list, lowering builtins |
debuginfo.cpp | Tracks debug information for JIT code |
disasm.cpp | Handles native object file and JIT code diassembly |
gf.c | Generic functions |
intrinsics.cpp | Lowering intrinsics |
jitlayers.cpp | JIT-specific code, ORC compilation layers/utilities |
llvm-alloc-helpers.cpp | Julia-specific escape analysis |
llvm-alloc-opt.cpp | Custom LLVM pass to demote heap allocations to the stack |
llvm-cpufeatures.cpp | Custom LLVM pass to lower CPU-based functions (e.g. haveFMA) |
llvm-demote-float16.cpp | Custom LLVM pass to lower 16b float ops to 32b float ops |
llvm-final-gc-lowering.cpp | Custom LLVM pass to lower GC calls to their final form |
llvm-gc-invariant-verifier.cpp | Custom LLVM pass to verify Julia GC invariants |
llvm-julia-licm.cpp | Custom LLVM pass to hoist/sink Julia-specific intrinsics |
llvm-late-gc-lowering.cpp | Custom LLVM pass to root GC-tracked values |
llvm-lower-handlers.cpp | Custom LLVM pass to lower try-catch blocks |
llvm-muladd.cpp | Custom LLVM pass for fast-match FMA |
llvm-multiversioning.cpp | Custom LLVM pass to generate sysimg code on multiple architectures |
llvm-propagate-addrspaces.cpp | Custom LLVM pass to canonicalize addrspaces |
llvm-ptls.cpp | Custom LLVM pass to lower TLS operations |
llvm-remove-addrspaces.cpp | Custom LLVM pass to remove Julia addrspaces |
llvm-remove-ni.cpp | Custom LLVM pass to remove Julia non-integral addrspaces |
llvm-simdloop.cpp | Custom LLVM pass for @simd |
pipeline.cpp | New pass manager pipeline, pass pipeline parsing |
sys.c | I/O and operating system utility functions |
بعض ملفات .cpp
تشكل مجموعة تتجمع لتكوين كائن واحد.
الفرق بين الدالة الداخلية (intrinsic) والدالة المدمجة (builtin) هو أن الدالة المدمجة هي دالة من الدرجة الأولى يمكن استخدامها مثل أي دالة أخرى في جوليا. بينما يمكن للدالة الداخلية أن تعمل فقط على البيانات غير المعبأة (unboxed)، وبالتالي يجب أن تكون وسائطها ذات نوع ثابت.
Alias Analysis
جوليا تستخدم حاليًا LLVM's Type Based Alias Analysis. للعثور على التعليقات التي توثق علاقات الإدراج، ابحث عن static MDNode*
في src/codegen.cpp
.
خيار -O
يمكّن LLVM
من Basic Alias Analysis.
Building Julia with a different version of LLVM
الإصدار الافتراضي من LLVM محدد في deps/llvm.version
. يمكنك تجاوز ذلك عن طريق إنشاء ملف يسمى Make.user
في الدليل العلوي وإضافة سطر إليه مثل:
LLVM_VER = 13.0.0
بجانب أرقام إصدار LLVM، يمكنك أيضًا استخدام DEPS_GIT = llvm
بالاشتراك مع USE_BINARYBUILDER_LLVM = 0
للبناء ضد أحدث إصدار تطويري من LLVM.
يمكنك أيضًا تحديد بناء نسخة تصحيحية من LLVM، عن طريق تعيين إما LLVM_DEBUG = 1
أو LLVM_DEBUG = Release
في ملف Make.user
الخاص بك. الخيار الأول سيكون بناءً غير مُحسّن بالكامل من LLVM والخيار الأخير سينتج بناءً مُحسّنًا من LLVM. اعتمادًا على احتياجاتك، سيكون الخيار الأخير كافيًا وهو أسرع بكثير. إذا كنت تستخدم LLVM_DEBUG = Release
، سترغب أيضًا في تعيين LLVM_ASSERTIONS = 1
لتمكين التشخيصات لتمريرات مختلفة. فقط LLVM_DEBUG = 1
يعني أن هذا الخيار مفعل بشكل افتراضي.
Passing options to LLVM
يمكنك تمرير الخيارات إلى LLVM عبر متغير البيئة JULIA_LLVM_ARGS
. إليك إعدادات مثال باستخدام صيغة bash
:
export JULIA_LLVM_ARGS=-print-after-all
يقوم بتفريغ IR بعد كل تمريرة.export JULIA_LLVM_ARGS=-debug-only=loop-vectorize
يقوم بتفريغ تشخيصات LLVMDEBUG(...)
لمتجه الحلقة. إذا تلقيت تحذيرات حول "حجة سطر الأوامر غير المعروفة"، قم بإعادة بناء LLVM معLLVM_ASSERTIONS = 1
.export JULIA_LLVM_ARGS=-help
يظهر قائمة بالخيارات المتاحة.export JULIA_LLVM_ARGS=-help-hidden
يظهر المزيد.export JULIA_LLVM_ARGS="-fatal-warnings -print-options"
هو مثال على كيفية استخدام خيارات متعددة.
Useful JULIA_LLVM_ARGS
parameters
-print-after=PASS
: يطبع الـ IR بعد أي تنفيذ لـPASS
، مفيد للتحقق من التغييرات التي تم إجراؤها بواسطة تمريرة.-print-before=PASS
: يطبع الـ IR قبل أي تنفيذ لـPASS
، مفيد للتحقق من المدخلات إلى تمريرة.-print-changed
: يطبع الـ IR كلما قام تمرير بتغيير الـ IR، مفيد لتضييق نطاق التمريرات التي تسبب مشاكل.-print-(before|after)=MARKER-PASS
: تحتوي أنبوبية جوليا على عدد من تمريرات العلامات في الأنبوب، والتي يمكن استخدامها لتحديد مكان حدوث المشكلات أو التحسينات. يتم تعريف تمريرة العلامة على أنها تمريرة تظهر مرة واحدة في الأنبوب ولا تقوم بأي تحويلات على IR، وهي مفيدة فقط لاستهداف الطباعة قبل/بعد. حاليًا، توجد تمريرات العلامات التالية في الأنبوب:- قبل التحسين
- قبل التبسيط المبكر
- بعد التبسيط المبكر
- قبل تحسين الأداء المبكر
- بعد تحسين مبكر
- قبل تحسين الحلقة
- قبلLICM
- بعدLICM
- قبل تبسيط الحلقة
- بعد تبسيط الحلقة
- بعد تحسين الحلقة
- قبل تحسين المقياس
- بعد تحسين المقياس
- قبل التمثيل العددي
- بعد التمثيل العددي
- قبل التخفيض الداخلي
- بعد التخفيض الداخلي
- قبل التنظيف
- بعد التنظيف
- بعد التحسين
-time-passes
: يطبع الوقت المستغرق في كل تمريرة، مفيد لتحديد التمريرات التي تستغرق وقتًا طويلاً.-print-module-scope
: يُستخدم بالاقتران مع-print-(before|after)
، يحصل على الوحدة الكاملة بدلاً من وحدة IR المستلمة بواسطة التمريرة-debug
: يطبع الكثير من معلومات التصحيح عبر LLVM-debug-only=NAME
، يطبع بيانات تصحيح الأخطاء من الملفات التي تم تعريفDEBUG_TYPE
فيها إلىNAME
، مفيد للحصول على سياق إضافي حول مشكلة ما
Debugging LLVM transformations in isolation
في بعض الأحيان، يمكن أن يكون من المفيد تصحيح تحويلات LLVM بشكل معزول عن بقية نظام جوليا، على سبيل المثال، لأن إعادة إنتاج المشكلة داخل julia
قد تستغرق وقتًا طويلاً، أو لأن المرء يريد الاستفادة من أدوات LLVM (مثل bugpoint).
للبدء، يمكنك تثبيت أدوات المطورين للعمل مع LLVM عبر:
make -C deps install-llvm-tools
للحصول على IR غير المحسن لصورة النظام بالكامل، قم بتمرير الخيار --output-unopt-bc unopt.bc
إلى عملية بناء صورة النظام، والتي ستخرج IR غير المحسن إلى ملف unopt.bc
. يمكن بعد ذلك تمرير هذا الملف إلى أدوات LLVM كالمعتاد. يمكن أن تعمل libjulia
كإضافة تمرير LLVM ويمكن تحميلها في أدوات LLVM، لجعل التمريرات الخاصة بـ julia متاحة في هذا البيئة. بالإضافة إلى ذلك، فإنه يكشف عن الميتا-تمريرة -julia
، التي تشغل كامل خط تمريرات Julia على IR. كمثال، لتوليد صورة نظام مع مدير التمريرات القديم، يمكن للمرء القيام بما يلي:
llc -o sys.o opt.bc
cc -shared -o sys.so sys.o
لإنشاء صورة نظام مع مدير المرور الجديد، يمكن القيام بما يلي:
opt -load-pass-plugin=libjulia-codegen.so --passes='julia' -o opt.bc unopt.bc
llc -o sys.o opt.bc
cc -shared -o sys.so sys.o
يمكن بعد ذلك تحميل صورة النظام بواسطة julia
كالمعتاد.
من الممكن أيضًا تفريغ وحدة LLVM IR لدالة جوليا واحدة فقط، باستخدام:
fun, T = +, Tuple{Int,Int} # Substitute your function of interest here
optimize = false
open("plus.ll", "w") do file
println(file, InteractiveUtils._dump_function(fun, T, false, false, false, true, :att, optimize, :default, false))
end
يمكن معالجة هذه الملفات بنفس الطريقة التي تم بها عرض IR sysimg غير المحسن أعلاه.
Running the LLVM test suite
لتشغيل اختبارات llvm محليًا، تحتاج أولاً إلى تثبيت الأدوات، ثم بناء جوليا، ثم يمكنك تشغيل الاختبارات:
make -C deps install-llvm-tools
make -j julia-src-release
make -C test/llvmpasses
إذا كنت ترغب في تشغيل ملفات الاختبار الفردية مباشرة، عبر الأوامر في أعلى كل ملف اختبار، فإن الخطوة الأولى هنا ستقوم بتثبيت الأدوات في ./usr/tools/opt
. ثم سترغب في استبدال %s
يدويًا باسم ملف الاختبار.
Improving LLVM optimizations for Julia
تحسين توليد كود LLVM عادة ما ينطوي على إما تغيير خفض جوليا ليكون أكثر ملاءمة لتمريرات LLVM، أو تحسين تمريرة.
إذا كنت تخطط لتحسين تمريرة، تأكد من قراءة LLVM developer policy. أفضل استراتيجية هي إنشاء مثال كود في شكل يمكنك من استخدام أداة opt
الخاصة بـ LLVM لدراسته والتمرير المعني بشكل معزول.
- Sure! Please provide the Markdown content or text you'd like me to translate into Arabic.
- استخدم
JULIA_LLVM_ARGS=-print-after-all
لتفريغ IR. - اختر IR في النقطة التي تسبق مباشرة تمريرة الاهتمام.
- قم بإزالة بيانات التصحيح وإصلاح بيانات TBAA يدويًا.
الخطوة الأخيرة تتطلب جهدًا كبيرًا. ستكون الاقتراحات حول طريقة أفضل موضع تقدير.
The jlcall calling convention
تتمتع جوليا باتفاقية استدعاء عامة للشفرة غير المحسّنة، والتي تبدو كما يلي:
jl_value_t *any_unoptimized_call(jl_value_t *, jl_value_t **, int);
حيث الحجة الأولى هي كائن الدالة المعبأة، والحجة الثانية هي مصفوفة من الحجج على المكدس، والثالث هو عدد الحجج. الآن، يمكننا إجراء تخفيض مباشر وإصدار alloca لمصفوفة الحجج. ومع ذلك، فإن هذا سيخون طبيعة SSA للاستخدامات في موقع الاستدعاء، مما يجعل التحسينات (بما في ذلك وضع جذر GC) أكثر صعوبة بشكل كبير. بدلاً من ذلك، نقوم بإصداره على النحو التالي:
call %jl_value_t *@julia.call(jl_value_t *(*)(...) @any_unoptimized_call, %jl_value_t *%arg1, %jl_value_t *%arg2)
هذا يسمح لنا بالاحتفاظ بخصائص SSA في الاستخدامات طوال المحسّن. ستقوم عملية وضع جذر GC لاحقًا بتخفيض هذه المكالمة إلى واجهة برمجة التطبيقات الأصلية C.
GC root placement
يتم وضع جذر GC بواسطة تمريرة LLVM في وقت متأخر من خط أنابيب التمريرات. يتيح القيام بوضع جذر GC في وقت متأخر لـ LLVM إجراء تحسينات أكثر عدوانية حول الشيفرة التي تتطلب جذور GC، بالإضافة إلى السماح لنا بتقليل عدد جذور GC المطلوبة وعمليات تخزين جذر GC (نظرًا لأن LLVM لا تفهم GC الخاص بنا، فلن تعرف بخلاف ذلك ما هو مسموح به وما هو غير مسموح به مع القيم المخزنة في إطار GC، لذا ستقوم بحذر بعمل القليل جدًا). كمثال، اعتبر مسار خطأ
if some_condition()
#= Use some variables maybe =#
error("An error occurred")
end
أثناء عملية الطي الثابت، قد يكتشف LLVM أن الشرط دائمًا غير صحيح، ويمكنه إزالة الكتلة الأساسية. ومع ذلك، إذا تم إجراء خفض جذر GC مبكرًا، فإن فتحات جذر GC المستخدمة في الكتلة المحذوفة، بالإضافة إلى أي قيم تم الاحتفاظ بها حية في تلك الفتحات فقط لأنها كانت مستخدمة في مسار الخطأ، ستظل محتفظة بها من قبل LLVM. من خلال إجراء خفض جذر GC في وقت متأخر، نمنح LLVM الترخيص للقيام بأي من تحسيناته المعتادة (الطي الثابت، وإزالة الشيفرة الميتة، إلخ)، دون الحاجة إلى القلق (الكثير) بشأن القيم التي قد تكون أو لا تكون متعقبة بواسطة GC.
ومع ذلك، من أجل أن نكون قادرين على تنفيذ وضع جذر GC المتأخر، نحتاج إلى أن نكون قادرين على تحديد أ) أي المؤشرات يتم تتبعها بواسطة GC و ب) جميع استخدامات هذه المؤشرات. وبالتالي، فإن هدف تمريرة وضع GC هو بسيط:
قلل عدد الجذور/التخزينات المطلوبة لجمع القمامة مع الالتزام بالشرط أنه في كل نقطة أمان، يجب أن يكون أي مؤشر متتبع لجمع القمامة (أي الذي يوجد له مسار بعد هذه النقطة يحتوي على استخدام لهذا المؤشر) في بعض فتحات جمع القمامة.
Representation
الصعوبة الرئيسية هي اختيار تمثيل IR الذي يسمح لنا بتحديد المؤشرات التي تتبعها GC واستخداماتها، حتى بعد أن يتم تشغيل البرنامج من خلال المحسّن. تصميمنا يستخدم ثلاث ميزات من LLVM لتحقيق ذلك:
- مساحات العناوين المخصصة
- حزم العمليات
- مؤشرات غير صحيحة
تسمح مساحات العناوين المخصصة لنا بتوسيم كل نقطة برقم صحيح يجب الحفاظ عليه من خلال التحسينات. قد لا يقوم المترجم بإدراج تحويلات بين مساحات العناوين التي لم تكن موجودة في البرنامج الأصلي ويجب ألا يغير أبدًا مساحة العنوان لمؤشر في عملية تحميل/تخزين/إلخ. هذا يسمح لنا بتعليق أي المؤشرات تتبعها إدارة الذاكرة بطريقة مقاومة للتحسين. لاحظ أن البيانات الوصفية لن تتمكن من تحقيق نفس الغرض. من المفترض أن تكون البيانات الوصفية قابلة للإهمال دائمًا دون تغيير دلالات البرنامج. ومع ذلك، فإن الفشل في تحديد مؤشر تتبعه إدارة الذاكرة يغير سلوك البرنامج الناتج بشكل كبير - من المحتمل أن يتعطل أو يعيد نتائج خاطئة. نحن نستخدم حاليًا ثلاث مساحات عناوين مختلفة (أرقامها محددة في src/codegen_shared.cpp
):
- GC Tracked Pointers (حاليًا 10): هذه مؤشرات إلى قيم مغلفة قد يتم وضعها في إطار GC. إنه يعادل بشكل فضفاض مؤشر
jl_value_t*
على جانب C. ملاحظة: من غير القانوني أن يكون لديك أبدًا مؤشر في هذه المساحة العنوانية قد لا يتم تخزينه في فتحة GC. - المؤشرات المشتقة (حالياً 11): هذه هي المؤشرات التي يتم اشتقاقها من بعض المؤشرات التي تتعقبها إدارة الذاكرة. استخدامات هذه المؤشرات تولد استخدامات للمؤشر الأصلي. ومع ذلك، لا تحتاج بالضرورة إلى أن تكون معروفة لإدارة الذاكرة. يجب على مرحلة وضع الجذر في إدارة الذاكرة دائماً العثور على المؤشر المتعقب من قبل إدارة الذاكرة الذي تم اشتقاق هذه المؤشر منه واستخدامه كمؤشر للجذر.
- مؤشرات الجذر المستندة إلى المتصل (حالياً 12): هذه مساحة عنوانية مفيدة للتعبير عن مفهوم قيمة مستندة إلى المتصل. يجب أن تكون جميع قيم هذه المساحة العنوانية قابلة للتخزين في جذر GC (على الرغم من أنه من الممكن تخفيف هذا الشرط في المستقبل)، ولكن على عكس المؤشرات الأخرى، لا تحتاج إلى أن تكون مستندة إذا تم تمريرها إلى استدعاء (لا تزال بحاجة إلى أن تكون مستندة إذا كانت حية عبر نقطة أمان أخرى بين التعريف والاستدعاء).
- المؤشرات المحملة من الكائن المتعقب (حالياً 13): يتم استخدام ذلك بواسطة المصفوفات، التي تحتوي بدورها على مؤشر إلى البيانات المدارة. هذه المنطقة من البيانات مملوكة للمصفوفة، لكنها ليست كائناً متعقباً بواسطة جامع القمامة بحد ذاتها. يضمن المترجم أنه طالما أن هذا المؤشر نشط، سيظل الكائن الذي تم تحميل هذا المؤشر منه نشطاً أيضاً.
Invariants
تستخدم عملية وضع جذر GC عدة ثوابت، والتي يجب أن يلتزم بها الواجهة الأمامية ويتم الحفاظ عليها بواسطة المحسّن.
أولاً، يُسمح فقط بتحويلات مساحة العنوان التالية:
- 0->{Tracked,Derived,CalleeRooted}: يُسمح بتقليل مؤشر غير متعقب إلى أي من المؤشرات الأخرى. ومع ذلك، يجب ملاحظة أن المحسّن لديه ترخيص واسع لعدم تثبيت مثل هذه القيمة. من غير الآمن أبدًا أن تحتوي مساحة العنوان 0 على أي جزء من البرنامج إذا كانت (أو مشتقة من) قيمة تتطلب جذر GC.
- تم تتبعها->مشتقة: هذا هو مسار التحلل القياسي للقيم الداخلية. ستبحث عملية الترتيب عن هذه لتحديد المؤشر الأساسي لأي استخدام.
- Tracked->CalleeRooted: Addrspace CalleeRooted يعمل فقط كإشارة على أن جذر GC غير مطلوب. ومع ذلك، يجب ملاحظة أن تدهور Derived->CalleeRooted محظور، حيث يجب عمومًا أن تكون المؤشرات قابلة للتخزين في فتحة GC، حتى في هذه المساحة العنوانية.
الآن دعنا نعتبر ما يشكل استخدامًا:
- الأحمال التي تكون قيمها المحملة في واحدة من مساحات العناوين
- تخزين قيمة في واحدة من مساحات العناوين إلى موقع
- يخزن في مؤشر في واحدة من مساحات العناوين
- استدعاءات يكون فيها قيمة في واحدة من مساحات العناوين هي عامل تشغيل
- استدعاءات في ABI jlcall، حيث يحتوي مصفوفة المعاملات على قيمة
- Please provide the Markdown content or text that you would like me to translate into Arabic.
نحن نسمح صراحةً بعمليات التحميل/التخزين والمكالمات البسيطة في مساحات العناوين Tracked/Derived. يجب أن تكون عناصر مصفوفات وسيطة jlcall دائمًا في مساحة العنوان Tracked (هذا مطلوب من قبل ABI بحيث تكون مؤشرات jl_value_t*
صالحة). نفس الشيء ينطبق على تعليمات الإرجاع (على الرغم من ملاحظة أن وسائط إرجاع الهيكل مسموح لها بأن تحتوي على أي من مساحات العناوين). الاستخدام الوحيد المسموح به لمؤشر CalleeRooted في مساحة العنوان هو تمريره إلى مكالمة (يجب أن تحتوي على عملية ذات نوع مناسب).
علاوة على ذلك، نحن نمنع getelementptr
في addrspace Tracked. وذلك لأنه ما لم تكن العملية لا تفعل شيئًا، فإن المؤشر الناتج لن يكون قابلًا للتخزين بشكل صحيح في فتحة GC وقد لا يكون بالتالي في هذه المساحة العنوانية. إذا كان مثل هذا المؤشر مطلوبًا، يجب أن يتم تقليله إلى addrspace Derived أولاً.
أخيرًا، نحن نمنع تعليمات inttoptr
/ptrtoint
في هذه المساحات العنوانية. وجود هذه التعليمات يعني أن بعض قيم i64
تتعقبها إدارة الذاكرة. هذا أمر إشكالي، لأنه يكسر المتطلب المذكور بأننا قادرون على تحديد المؤشرات ذات الصلة بإدارة الذاكرة. يتم تحقيق هذه الثباتة باستخدام ميزة "المؤشرات غير العددية" في LLVM، والتي هي جديدة في LLVM 5.0. إنها تمنع المحسّن من إجراء تحسينات قد تُدخل هذه العمليات. لاحظ أنه لا يزال بإمكاننا إدراج ثوابت ثابتة في وقت JIT باستخدام inttoptr
في مساحة العنوان 0 ثم التحلل إلى مساحة العنوان المناسبة بعد ذلك.
Supporting ccall
جانب مهم مفقود من المناقشة حتى الآن هو التعامل مع ccall
. 4d61726b646f776e2e436f64652822222c20226363616c6c2229_40726566
له ميزة غريبة تتمثل في أن الموقع ونطاق الاستخدام لا يتطابقان. كمثال، اعتبر:
A = randn(1024)
ccall(:foo, Cvoid, (Ptr{Float64},), A)
في عملية التخفيض، سيقوم المترجم بإدراج تحويل من المصفوفة إلى المؤشر الذي يسقط الإشارة إلى قيمة المصفوفة. ومع ذلك، نحتاج بالطبع إلى التأكد من أن المصفوفة تبقى حية بينما نقوم بـ ccall
. لفهم كيف يتم ذلك، دعونا نلقي نظرة على تخفيض تقريبي افتراضي ممكن للكود أعلاه:
return $(Expr(:foreigncall, :(:foo), Cvoid, svec(Ptr{Float64}), 0, :(:ccall), Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), 0, :(:ccall), :(A)), :(A)))
الـ :(A)
الأخير هو قائمة إضافية من المعاملات تم إدراجها أثناء التخفيض والتي تُعلم مولد الشيفرة بالقيم على مستوى جوليا التي تحتاج إلى البقاء حية طوال مدة هذا ccall
. ثم نأخذ هذه المعلومات ونمثلها في "حزمة معامل" على مستوى IR. حزمة المعامل هي في الأساس استخدام مزيف يتم إرفاقه بموقع الاستدعاء. على مستوى IR، يبدو هذا كما يلي:
call void inttoptr (i64 ... to void (double*)*)(double* %5) [ "jl_roots"(%jl_value_t addrspace(10)* %A) ]
ستتعامل مرحلة وضع جذر GC مع حزمة المعامل jl_roots
كما لو كانت معاملًا عاديًا. ومع ذلك، كخطوة نهائية، بعد إدخال جذور GC، ستقوم بإسقاط حزمة المعامل لتجنب إرباك اختيار التعليمات.
Supporting pointer_from_objref
pointer_from_objref
خاص لأنه يتطلب من المستخدم اتخاذ السيطرة الصريحة على جذر GC. وفقًا لثوابتنا أعلاه، فإن هذه الدالة غير قانونية، لأنها تقوم بإجراء تحويل لمساحة العنوان من 10 إلى 0. ومع ذلك، يمكن أن تكون مفيدة، في حالات معينة، لذا نقدم دالة داخلية خاصة:
declared %jl_value_t *julia.pointer_from_objref(%jl_value_t addrspace(10)*)
الذي يتم تخفيضه إلى مساحة العنوان المقابلة بعد تخفيض جذر GC. يجب ملاحظة أنه باستخدام هذه الدالة، يتحمل المتصل جميع المسؤوليات للتأكد من أن القيمة المعنية متجذرة. علاوة على ذلك، لا تعتبر هذه الدالة استخدامًا، لذا لن يوفر تمرير وضع جذر GC جذرًا لـ الوظيفة. ونتيجة لذلك، يجب ترتيب التجذير الخارجي بينما لا تزال القيمة متعقبة بواسطة النظام. أي أنه ليس من الصحيح محاولة استخدام نتيجة هذه العملية لإنشاء جذر عالمي - قد يكون المحسّن قد أسقط القيمة بالفعل.
Keeping values alive in the absence of uses
في بعض الحالات، من الضروري الحفاظ على كائن حي، حتى لو لم يكن هناك استخدام مرئي للكائن من قبل المترجم. قد يكون هذا هو الحال في التعليمات البرمجية منخفضة المستوى التي تعمل مباشرة على تمثيل الذاكرة لكائن ما أو التعليمات البرمجية التي تحتاج إلى التفاعل مع كود C. للسماح بذلك، نقدم الدوال الداخلية التالية على مستوى LLVM:
token @llvm.julia.gc_preserve_begin(...)
void @llvm.julia.gc_preserve_end(token)
(يتطلب وجود llvm.
في الاسم من أجل القدرة على استخدام نوع token
). دلالات هذه الدوال الداخلية هي كما يلي: في أي نقطة أمان يتم السيطرة عليها بواسطة استدعاء gc_preserve_begin
، ولكنها ليست مهيمنة على استدعاء gc_preserve_end
المقابل (أي استدعاء تكون حُجته هي الرمز الذي تم إرجاعه بواسطة استدعاء gc_preserve_begin
)، ستظل القيم الممررة كحجج إلى gc_preserve_begin
حية. لاحظ أن gc_preserve_begin
لا يزال يُعتبر استخدامًا عاديًا لتلك القيم، لذا ستضمن دلالات مدة الحياة القياسية أن القيم ستظل حية قبل دخول منطقة الحفظ.