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

Philip Pfaffe
Kim-Anh Tran
Kim-Anh Tran
Eric Leese
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. يتسبب هذا في زيادة طفيفة في حجم الرمز البرمجي، ولكنه يمكن أن يسرع أوقات الربط ويمكن أن يكون مفيدًا لعمليات تصحيح الأخطاء.

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

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

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

$ 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 إلى C++)، لا نحتاج إلى الاطّلاع على بيانات تصحيح الأخطاء الإضافية. ويساعدنا ذلك في توفير الوقت، إذ لا نحتاج إلى تحميل بيانات تصحيح الأخطاء الإضافية وتحليلها.

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

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

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

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

استخدِم الخيارات التالية لمناقشة الميزات الجديدة أو التحديثات أو أي شيء آخر مرتبط بـ "أدوات مطوّري البرامج".