مقدمة في العمل المتقدم مع WebAssembly وJavaScript في Node.js
\n\nلم يعد WebAssembly (Wasm) تقنية غريبة للمتصفحات. في نظام Node.js، يفتح إمكانيات فريدة لتنفيذ العمليات الحسابية عالية الأداء المكتوبة بلغة C/C++ أو Rust أو Go، بأقل تكاليف إضافية. على المستوى المتقدم، يتطلب العمل مع Wasm في Node.js فهماً عميقاً لإدارة الذاكرة، والاستدعاءات غير المتزامنة، وتحسين نقل البيانات بين بيئات التشغيل. في هذه المقالة، سنستعرض أمثلة عملية لدمج WebAssembly مع JavaScript باستخدام Node.js.
\n\n1. تجميع وتحميل وحدات Wasm في Node.js
\n\nتحضير ملف Wasm
\nللبدء، نحتاج إلى وحدة Wasm مُجمَّعة. لنفترض أن لدينا دالة بسيطة بلغة C تجمع رقمين:
\n\n// sum.c\nint add(int a, int b) {\n return a + b;\n}\n\nنقوم بالتجميع باستخدام Emscripten أو wasm-pack:
\nemcc sum.c -O3 -o sum.wasm --no-entry -s EXPORTED_FUNCTIONS="['_add']"\n\nتحميل الوحدة في Node.js
\nفي Node.js، يمكننا تحميل وحدة Wasm بطريقتين: عبر readFileSync المتزامن أو WebAssembly.instantiate غير المتزامن. لننظر إلى الخيار المتقدم غير المتزامن مع الاستيراد:
const fs = require('fs');\nconst path = require('path');\n\nasync function loadWasmModule(filePath, imports = {}) {\n const wasmBuffer = fs.readFileSync(path.resolve(filePath));\n const result = await WebAssembly.instantiate(wasmBuffer, imports);\n return result.instance;\n}\n\n(async () => {\n const instance = await loadWasmModule('./sum.wasm');\n const { add } = instance.exports;\n console.log('النتيجة:', add(5, 7)); // 12\n})();\n\nهنا imports هو كائن يمكن أن يحتوي على دوال JavaScript يتم استدعاؤها من Wasm (مثل التسجيل أو العمل مع الذاكرة).
2. إدارة الذاكرة: العمل مع الذاكرة الخطية لـ Wasm
\n\nأحد التحديات الرئيسية في العمل المتقدم مع Wasm هو إدارة الذاكرة الخطية. وحدات Wasm لها ذاكرة معزولة خاصة بها (ArrayBuffer) يمكن لـ JavaScript الوصول إليها مباشرة عبر instance.exports.memory. لننظر إلى مثال حيث نمرر مصفوفة أرقام من JS إلى Wasm، ونعالجها ونعيد النتيجة.
مثال: جمع مصفوفة بلغة C
\n// sum_array.c\nint sum_array(int* arr, int len) {\n int sum = 0;\n for(int i = 0; i < len; i++) {\n sum += arr[i];\n }\n return sum;\n}\n\nنقوم بالتجميع مع تصدير الذاكرة:
\nemcc sum_array.c -O3 -o sum_array.wasm --no-entry \\\n -s EXPORTED_FUNCTIONS="['_sum_array']" \\\n -s EXPORTED_RUNTIME_METHODS="['cwrap', 'getValue']"\n\nكود JavaScript لنقل البيانات
\nasync function sumArrayWithWasm(numbers) {\n const instance = await loadWasmModule('./sum_array.wasm');\n const memory = instance.exports.memory;\n const sumArray = instance.exports.sum_array;\n\n // تخصيص ذاكرة للمصفوفة (4 بايت لكل int)\n const bytesPerElement = 4;\n const offset = instance.exports.malloc ? \n instance.exports.malloc(numbers.length * bytesPerElement) : \n 0; // إذا لم يكن هناك malloc، استخدم عنواناً ثابتاً\n\n // إنشاء Uint32Array لكتابة البيانات\n const buffer = new Uint32Array(memory.buffer, offset, numbers.length);\n buffer.set(numbers);\n\n // استدعاء دالة Wasm\n const result = sumArray(offset, numbers.length);\n\n // تحرير الذاكرة (إذا كان هناك free)\n if (instance.exports.free) {\n instance.exports.free(offset);\n }\n\n return result;\n}\n\n(async () => {\n const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n const sum = await sumArrayWithWasm(arr);\n console.log('مجموع المصفوفة:', sum); // 55\