مقالة تقنية

نقل inline asm الخاص بـ _ftol في Delphi إلى بناء 64 بت

· برمجة Delphi

كان كود Delphi القديم يحتوي أحيانا على مساعد صغير باسم _ftol لتحويل قيمة فاصلة عائمة إلى عدد صحيح. في مشاريع 32 بت كان من الشائع كتابة هذا النوع من المساعد باستخدام inline assembler، خصوصا في كود الرسومات وPDF المضبوط لمترجمات أقدم.

1
2
3
4
5
6
7
8
function _ftol( f: double) : Integer; cdecl;
begin
  asm
    lea   eax, f
    fstp  qword ptr [eax]
  end;
  result := Trunc(f);
end;

ينهار هذا الأسلوب عندما تترجم الوحدة نفسها لهدف 64 بت. يعرض مترجم Delphi ذي 64 بت الخطأ E1025 Unsupported language feature: 'ASM' لأن DCC64 لا يدعم خلط Pascal وinline assembly داخل روتينات Pascal العادية.

لماذا يرفضه مترجم 64 بت

يستطيع DCC64 تجميع روتينات assembly ذات 64 بت، لكن يجب كتابة الروتين كروتين assembler كامل مع مراعاة اتفاقية الاستدعاء ذات 64 بت. لا يمكنك نسخ inline assembler من 32 بت إلى جسم دالة Pascal وتوقع أن يترجم المترجم استخدام السجلات أو تخطيط stack أو سلوك x87.

يمكن إنشاء نسخة assembler كاملة، والكود التالي يوضح نوع معالجة control-word الذي قد يلزم عندما تكون القيمة موجودة بالفعل على FPU stack.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function _ftol: Integer; cdecl;
// Assumes double value is in FPU stack on entry
// Make a truncation to integer and put it into function result
var
  TmpVal: Int64;
  SaveCW, ScratchCW: word;
 
asm
  .NOFRAME
  fnstcw word ptr [SaveCW]
  fnstcw word ptr [ScratchCW]
  or word ptr [ScratchCW], 0F00h  ;// trunc toward zero, full precision
  fldcw word ptr [ScratchCW]
  fistp qword ptr [TmpVal]
  fldcw word ptr [SaveCW]
  mov rax, TmpVal
end;

فضل Pascal عندما يكون الهدف هو القطع فقط

في معظم كود التطبيقات، لا حاجة إلى روتين assembler. إذا كان المتطلب الحقيقي هو قطع Double نحو الصفر وإرجاع قيمة عدد صحيح، فالنسخة الواضحة والقابلة للنقل هي ببساطة:

1
2
3
4
function _ftol(f: double): integer; cdecl;
begin
  Result := Trunc(f);
end;

Trunc يعبر عن القصد مباشرة، ويترجم لأهداف 32 بت و64 بت، ويتجنب تكلفة صيانة assembler خاص بالمعمارية. كما أنه أسهل على الصيانة المستقبلية عند التفكير في فحص النطاق وسلوك overflow.

إرشادات الترحيل

  • استخدم تنفيذ Pascal عندما تتحكم في موضع الاستدعاء وتحتاج فقط إلى تحويل رقمي.
  • احتفظ بتصدير assembler فقط عندما تتطلب التوافقية الثنائية رمزا واتفاقية استدعاء محددين.
  • لا تنسخ كود سجلات 32 بت إلى روتين 64 بت دون مراجعة اتفاقية الاستدعاء وعرض البيانات.
  • اختبر حالات الحافة مثل القيم السالبة، والقيم الكبيرة، والقيم خارج نطاق العدد الصحيح الهدف.

اعتبارات النطاق والسلوك

يجب اعتبار استبدال assembler بـ Trunc تغييرا في السلوك يستحق الاختبارات. أكد كيف يجب أن يتصرف الكود مع الأرقام السالبة، والكسور القريبة من حد عدد صحيح، والقيم خارج نطاق Integer. ربما اعتمد assembler القديم على حالة FPU control-word، بينما تتبع نسخة Pascal تنفيذ RTL للمترجم الهدف.

إذا كان المساعد داخل مكون PDF أو رسومات، فاختبر المخرجات التي تستخدم نصا مدورا، وإحداثيات مكبرة، وأوامر رسم محولة. هذه المناطق أقدر على كشف فروق تحويل float-to-integer من الأمثلة البسيطة.

مسار الترحيل الموصى به

أولا، استبدل مساعدات التحويل الخاصة بكود Pascal واضح حيثما أمكن. ثانيا، احتفظ بالتنفيذ الأصلي 32 بت فقط خلف شرط 32 بت إذا كان لا يزال مطلوبا. ثالثا، أضف فرعا منفصلا 64 بت فقط عندما يبرر ذلك قياس أداء أو متطلب توافقية ثنائية. هذا يبقي المسار القابل للنقل بسيطا ويجعل المسار الخاص بالمعمارية استثناء لا قاعدة.