تصحيح أخطاء WebAssembly أسرع

Philip Pfaffe
Kim-Anh Tran
Kim-Anh Tran
Eric Leese
Sam Clegg

خلال مؤتمر Chrome Dev Summit لعام 2020، قدَّمنا عرضًا توضيحيًا حول إتاحة تصحيح أخطاء Chrome في تطبيقات WebAssembly على الويب. ومنذ ذلك الحين، استثمر الفريق الكثير من الطاقة في توسيع نطاق تجربة المطوّرين لتشمل التطبيقات الكبيرة وحتى الضخمة. في هذه المشاركة، سنعرض لك المقابض التي أضفناها (أو أجريناها) في الأدوات المختلفة وكيفية استخدامها.

تصحيح الأخطاء القابل للتطور

لنتابع من حيث توقفنا في المشاركة التي نشرناها في العام 2020. في ما يلي المثال الذي كنا ننظر إليه في ذلك الوقت:

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

ما زال مثالاً بسيطًا إلى حد ما، ولن ترى على الأرجح أيًا من المشكلات الحقيقية التي قد تواجهها في تطبيق كبير، ولكن لا يزال بإمكاننا توضيح الميزات الجديدة لك. يمكنك إعداد التجربة بنفسك وبسرعة وسهولة.

ناقشنا في المشاركة الأخيرة كيفية تجميع هذا المثال وتصحيح الأخطاء فيه. لنراجع ذلك مجددًا، ولكن لنلقِ أيضًا نظرة سريعة على //performance//:

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH

ينتج هذا الأمر ملف ثنائي Wasm بحجم 3 ميغابايت. والجزء الأكبر من ذلك، كما هو متوقع، هو معلومات تصحيح الأخطاء. يمكنك التحقّق من ذلك باستخدام أداة llvm-objdump [1]، على سبيل المثال:

$ llvm-objdump -h mandelbrot.wasm

mandelbrot.wasm:        file format wasm

Sections:
Idx Name          Size     VMA      Type
  0 TYPE          0000026f 00000000
  1 IMPORT        00001f03 00000000
  2 FUNCTION      0000043e 00000000
  3 TABLE         00000007 00000000
  4 MEMORY        00000007 00000000
  5 GLOBAL        00000021 00000000
  6 EXPORT        0000014a 00000000
  7 ELEM          00000457 00000000
  8 CODE          0009308a 00000000 TEXT
  9 DATA          0000e4cc 00000000 DATA
 10 name          00007e58 00000000
 11 .debug_info   000bb1c9 00000000
 12 .debug_loc    0009b407 00000000
 13 .debug_ranges 0000ad90 00000000
 14 .debug_abbrev 000136e8 00000000
 15 .debug_line   000bb3ab 00000000
 16 .debug_str    000209bd 00000000

يعرض لنا هذا الناتج جميع الأقسام في ملف Wasm الذي تم إنشاؤه، ومعظمها عبارة عن أقسام WebAssembly عادية، ولكن هناك أيضًا العديد من الأقسام المخصّصة التي يبدأ اسمها بـ .debug_. وهنا يحتوي البرنامج الثنائي على معلومات تصحيح الأخطاء. إذا أضفنا جميع الأحجام، يتضح لنا أنّ معلومات تصحيح الأخطاء تشكّل 2.3 ميغابايت تقريبًا من ملف 3 ميغابايت. في حال time أيضًا الأمر emcc، سنلاحظ أنّ تشغيله على الجهاز يستغرق 1.5 ثانية تقريبًا. هذه الأرقام تجعل خط الأساس صغيرًا ولطيفًا، لكنها صغيرة جدًا على الأرجح لن يراقبها أحد. مع ذلك، في التطبيقات الحقيقية يمكن أن يصل حجم البرنامج الثنائي لتصحيح الأخطاء إلى غيغابايت ويستغرق بضع دقائق لإنشاءه.

تخطي Binaryen

عند إنشاء تطبيق Wasm باستخدام Emscripten، تتمثل إحدى خطوات التصميم النهائية له في تشغيل أداة تحسين Binaryen. Binaryen هي مجموعة أدوات تحويل تعمل على تحسين وإضفاء الطابع القانوني على برامج ثنائية WebAssembly (مثل). يعد تشغيل Binaryen كجزء من الإصدار مكلفًا إلى حد ما، ولكنه مطلوب فقط في ظل ظروف معينة. بالنسبة إلى إصدارات تصحيح الأخطاء، يمكننا تسريع وقت الإصدار بشكل كبير إذا تجنبنا الحاجة إلى بطاقات Binaryen. يعتبر ممر Binaryen المطلوب الأكثر شيوعًا هو المصادقة على توقيعات الدوال التي تتضمن قيم أعداد صحيحة 64 بت. يمكننا تجنُّب ذلك من خلال الموافقة على دمج WebAssembly BigInt باستخدام -sWASM_BIGINT.

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

لقد وضعنا علامة -sERROR_ON_WASM_CHANGES_AFTER_LINK على وجه التحديد. يساعد في اكتشاف وقت تشغيل Binaryen وإعادة كتابة البرنامج الثنائي بشكل غير متوقع. وبهذه الطريقة، يمكننا التأكد من أننا نبقى على المسار السريع.

على الرغم من أن المثال الذي ذكرناه صغير إلى حد ما، لا يزال بإمكاننا رؤية تأثير تخطي Binaryen! وفقًا لـ time، يتم تشغيل هذا الأمر لمدة أقل من ثانية واحدة بقليل، أي أسرع بنصف ثانية من ذي قبل.

التعديلات المتقدمة

تخطّي فحص ملف الإدخال

عند ربط مشروع Emscripten، عادةً ما يفحص emcc كل ملفات ومكتبات عناصر الإدخال. وهو يفعل ذلك لتنفيذ اعتماديات دقيقة بين دوال مكتبة JavaScript والرموز الأصلية في برنامجك. وبالنسبة إلى المشاريع الأكبر حجمًا، يمكن أن يؤدي هذا الفحص الإضافي لملفات الإدخال (باستخدام llvm-nm) إلى زيادة مدة الربط بشكل كبير.

ويمكن تشغيله بدلاً من ذلك باستخدام -sREVERSE_DEPS=all الذي يشير إلى emcc لتضمين جميع الاعتماديات الأصلية المحتملة لدوال JavaScript. هذا الرمز البرمجي صغير الحجم، ولكنّه يمكن أن يزيد من مدّة الربط، ويمكن أن يكون مفيدًا في إصدارات تصحيح الأخطاء.

بالنسبة لمشروع صغير مثل المثال الذي ذكرناه، لا يحدث فرق حقيقي ولكن إذا كان لديك المئات أو حتى الآلاف من ملفات الكائنات في مشروعك، فيمكن أن يحسن ذلك بشكل ملحوظ أوقات الربط.

إزالة القسم "name"

في المشاريع الكبيرة، خاصةً تلك التي تستخدم كثيرًا من نماذج C++ ، يمكن أن يكون قسم "name" في WebAssembly كبيرًا جدًا. في المثال الذي عرضناه، لا يشكّل هذا الرمز سوى جزء صغير من الحجم الإجمالي للملف (اطّلِع على نتائج llvm-objdump أعلاه)، ولكن في بعض الحالات، قد يكون مهمًا للغاية. إذا كان قسم "name" في تطبيقك كبيرًا جدًا، وكانت معلومات تصحيح الأخطاء القزمة كافية لتلبية احتياجات تصحيح الأخطاء، فمن المفيد إزالة قسم "name":

$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm

سيؤدي ذلك إلى إزالة قسم "اسم" WebAssembly مع الاحتفاظ بأقسام تصحيح أخطاء DWARF.

انشطار تصحيح الأخطاء

لا تؤثر البرامج الثنائية التي تحتوي على الكثير من بيانات تصحيح الأخطاء على وقت الإصدار فحسب، بل تستغرق أيضًا وقتًا تصحيح الأخطاء. ويحتاج برنامج تصحيح الأخطاء إلى تحميل البيانات وإنشاء فهرس لها، حتى يتمكّن من الردّ بسرعة على طلبات البحث، مثل "ما هو نوع المتغيّر المحلي x؟".

تتيح لنا ميزة انشطار تصحيح الأخطاء تقسيم معلومات تصحيح الأخطاء لبرنامج ثنائي إلى جزأين: أحدهما يظل في النظام الثنائي والثاني مضمّنًا في ملف منفصل يُسمى كائن DWARF (.dwo). يمكن تفعيله من خلال تمرير علامة -gsplit-dwarf إلى Emscripten:

$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc  -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

نوضّح في ما يلي الأوامر المختلفة والملفات التي يتم إنشاؤها عن طريق التجميع بدون بيانات تصحيح الأخطاء، وباستخدام بيانات تصحيح الأخطاء، وأخيرًا باستخدام بيانات تصحيح الأخطاء وانشغال تصحيح الأخطاء.

الأوامر المختلفة والملفات التي يتم إنشاؤها

عند تقسيم بيانات DWARF، يتوفّر جزء من بيانات تصحيح الأخطاء مع البيانات الثنائية، بينما يتم وضع الجزء الأكبر في ملف mandelbrot.dwo (كما هو موضّح أعلاه).

بالنسبة إلى mandelbrot، لدينا ملف مصدر واحد فقط، ولكن بشكلٍ عام تكون المشاريع أكبر من هذا الملف وتتضمّن أكثر من ملف واحد. تُنشئ ميزة انشطار تصحيح الأخطاء ملف .dwo لكل واحد منها. ولكي يتمكّن الإصدار التجريبي الحالي من برنامج تصحيح الأخطاء (0.1.6.1615) من تحميل معلومات تصحيح الأخطاء هذه، نحتاج إلى تجميع كل هذه المعلومات في ما يُعرف باسم حزمة DWARF (.dwp) على النحو التالي:

$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp

حزمة ملفات dwo في حزمة DWARF

يتميز إنشاء حزمة DWARF من الكائنات الفردية بميزة أنك تحتاج فقط إلى تقديم ملف واحد إضافي! ونعمل حاليًا على تحميل كل العناصر الفردية أيضًا في إصدار مستقبلي.

ما هي مشكلة DWARF 5؟

ربما لاحظت أننا تسللنا علامة أخرى إلى الأمر emcc أعلاه، -gdwarf-5. إن تمكين الإصدار 5 من رموز DWARF، والذي لا يمثل الإصدار الافتراضي حاليًا، هو خدعة أخرى لمساعدتنا في بدء تصحيح الأخطاء بشكل أسرع. باستخدامه، يتم تخزين معلومات معينة في البرنامج الثنائي الرئيسي الذي يتركه الإصدار 4 الافتراضي. وعلى وجه التحديد، يمكننا تحديد المجموعة الكاملة من ملفات المصدر من البرنامج الثنائي الرئيسي فقط. ويسمح ذلك لبرنامج تصحيح الأخطاء بتنفيذ إجراءات أساسية مثل عرض شجرة المصدر بالكامل وضبط نقاط التوقف بدون تحميل بيانات الرموز الكاملة وتحليلها. وهذا يجعل تصحيح الأخطاء باستخدام الرموز المقسَّمة أسرع بكثير، لذلك نستخدم دائمًا علامتَي سطر الأوامر -gsplit-dwarf و-gdwarf-5 معًا.

باستخدام تنسيق تصحيح الأخطاء DWARF5 يمكننا أيضًا الوصول إلى ميزة أخرى مفيدة. توفّر هذه السياسة فهرس الأسماء في بيانات تصحيح الأخطاء التي سيتم إنشاؤها عند تمرير علامة -gpubnames:

$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

أثناء جلسة تصحيح الأخطاء، تحدث عمليات البحث عن الرموز غالبًا من خلال البحث عن كيان بالاسم، على سبيل المثال، عند البحث عن متغير أو نوع. يعمل فهرس الأسماء على تسريع عملية البحث هذه من خلال الإشارة مباشرةً إلى وحدة التحويل البرمجي التي تعرّف هذا الاسم. وبدون فهرس الأسماء، سيُطلَب منك إجراء بحث شامل عن بيانات تصحيح الأخطاء بالكامل للعثور على وحدة التجميع الصحيحة التي تُحدِّد الكيان المُعنوَن الذي نبحث عنه.

لمحبي الاستكشاف: الاطّلاع على بيانات تصحيح الأخطاء

يمكنك استخدام llvm-dwarfdump لإلقاء نظرة على بيانات DWARF. لنجرب هذا:

llvm-dwarfdump mandelbrot.wasm

يمنحنا ذلك نظرة عامة على "وحدات التجميع" (تقريبًا، الملفات المصدر) التي نمتلك معلومات تصحيح أخطاء لها. في هذا المثال، لا تتوفّر لدينا سوى معلومات تصحيح الأخطاء الخاصة بـ "mandelbrot.cc". تتيح لنا المعلومات العامة معرفة أنّ لدينا وحدة أساسية، ما يعني أنّ البيانات غير مكتملة في هذا الملف، وأنّ هناك ملف .dwo منفصلاً يتضمّن معلومات تصحيح الأخطاء المتبقية:

معلومات mandelbrot.wasm وتصحيح الأخطاء

يمكنك الاطّلاع أيضًا على الجداول الأخرى داخل هذا الملف، على سبيل المثال: في جدول الأسطر الذي يعرض ربط رمز بايت Wasm بخطوط C++ (جرِّب استخدام llvm-dwarfdump -debug-line).

يمكننا أيضًا الاطّلاع على معلومات تصحيح الأخطاء المضمّنة في ملف .dwo المنفصل:

llvm-dwarfdump mandelbrot.dwo

معلومات mandelbrot.wasm وتصحيح الأخطاء

النصّ المختصر: ما هي ميزة استخدام انشطار تصحيح الأخطاء؟

وهناك مزايا عديدة لتقسيم معلومات تصحيح الأخطاء إذا كان أحدها يعمل مع تطبيقات كبيرة:

  1. الربط بشكل أسرع: لم يعد الرابط بحاجة إلى تحليل معلومات تصحيح الأخطاء بالكامل عادة ما تحتاج الروابط إلى تحليل بيانات DWARF الموجودة في البرنامج الثنائي بالكامل. تتعامل برامج الربط مع الملفات الثنائية الأصغر حجمًا، ما يؤدي إلى حدوث أوقات ربط أسرع (خاصةً في التطبيقات الكبيرة الحجم)، وذلك من خلال إزالة أجزاء كبيرة من معلومات تصحيح الأخطاء في ملفات منفصلة.

  2. تصحيح الأخطاء بشكل أسرع: يمكن لبرنامج تصحيح الأخطاء تخطّي تحليل الرموز الإضافية في ملفات .dwo/.dwp لإجراء بعض عمليات البحث عن الرموز. بالنسبة إلى بعض عمليات البحث (مثل الطلبات المتعلقة بتخطيط الخط لملفات Wasm-to-C++ )، لا نحتاج إلى الاطّلاع على بيانات تصحيح الأخطاء الإضافية. وهذا يوفر لنا الوقت، ولا نحتاج إلى تحميل بيانات تصحيح الأخطاء الإضافية وتحليلها.

1: إذا لم يكن لديك إصدار حديث من llvm-objdump على نظامك، وكنت تستخدم emsdk، يمكنك العثور عليه في الدليل emsdk/upstream/bin.

تنزيل قنوات المعاينة

يمكنك استخدام Chrome كناري أو إصدار مطوّري البرامج أو الإصدار التجريبي من المتصفِّح التلقائي للتطوير. وتتيح لك قنوات المعاينة هذه الوصول إلى أحدث ميزات "أدوات مطوري البرامج" واختبار أحدث واجهات برمجة التطبيقات للأنظمة الأساسية للويب والعثور على المشاكل على موقعك الإلكتروني قبل أن يفعلها المستخدمون.

التواصل مع فريق "أدوات مطوري البرامج في Chrome"

استخدِم الخيارات التالية لمناقشة الميزات والتغييرات الجديدة في المشاركة أو مناقشة أي معلومات أخرى متعلّقة بأدوات مطوري البرامج.

  • يمكنك إرسال اقتراح أو ملاحظات إلينا عبر crbug.com.
  • الإبلاغ عن مشكلة في "أدوات مطوري البرامج" باستخدام خيارات إضافية   المزيد > مساعدة > الإبلاغ عن مشاكل في "أدوات مطوري البرامج" في "أدوات مطوري البرامج"
  • يمكنك نشر تغريدة على @ChromeDevTools.
  • شارِك في التعليقات على الميزات الجديدة في فيديوهات YouTube أو نصائح حول أدوات مطوّري البرامج فيديوهات YouTube.