
Binaryen هي مكتبة برمجية للبنية التحتية للبنية التحتية و CHALCHAIN من أجل webassembly ، مكتوبة في C ++. يهدف إلى جعل التجميع إلى webassembly سهلة وسريعة وفعالة :
سهل : يحتوي Binaryen على واجهة برمجة تطبيقات C بسيطة في رأس واحد ، ويمكن استخدامها أيضًا من JavaScript. إنه يقبل الإدخال في شكل يشبه webassembly ولكنه يقبل أيضًا رسمًا عامًا لتدفق التحكم العام للمجموعات التي تفضل ذلك.
FAST : يستخدم IR الداخلي Binaryen هياكل بيانات مضغوطة ومصممة لعمود الكود والتحسين المتوازي تمامًا ، باستخدام جميع مراكز وحدة المعالجة المركزية المتاحة. يقوم Binaryen's IR أيضًا بتجميع ويب بسهولة وسرعة وسرعة لأنه مجموعة فرعية من التجميع.
فعال : يحتوي محسن Binaryen على العديد من التمريرات (انظر نظرة عامة في وقت لاحق) والتي يمكن أن تحسن حجم الرمز وسرعة. تهدف هذه التحسينات إلى جعل الثنائي قويًا بما يكفي لاستخدامه كوجود برمجي خلفي في حد ذاته. يتمثل أحد مجالات التركيز المحددة في التحسينات الخاصة بالتجميع (التي قد لا يفعلها المجمعون للأغراض العامة) ، والتي يمكنك التفكير فيها كإعطاء WASM ، على غرار التعديل لـ JavaScript و CSS ، وما إلى ذلك ، وكلها خاصة باللغة.
تشمل أدوات الأدوات التي تستخدم ثنائيًا كمكون (عادةً تشغيل wasm-opt ):
Emscripten (C/C ++)wasm-pack (Rust)J2CL (Java ؛ J2Wasm )Kotlin (Kotlin/WASM)Dart (رفرفة)wasm_of_ocaml (OCAML)لمعرفة المزيد حول كيفية عمل بعض هذه الأعمال ، راجع أجزاء بنية أدوات Alogchain في مدونة V8 WASMGC Porting.
المترجمين الذين يستخدمون ثنائيين كمكتبة تشمل:
AssemblyScript التي تجمع متغيرًا من TypeScript to webassemblywasm2js الذي يجمع webassembly إلى JSAsterius الذي يجمع Haskell إلى webassemblyGrain التي تجمع الحبوب إلى webassemblyيوفر Binaryen أيضًا مجموعة من مرافق أدوات الأدوات التي يمكنها
راجع التعليمات المساهمة إذا كنت مهتمًا بالمشاركة.
تم تصميم الأشعة تحت الحمراء الخاصة بـ Binaryen لتكون
هناك بعض الاختلافات بين Binaryen IR ولغة Webassembly:
catch في ميزة معالجة الاستثناءات) كمعدورة فرعية pop .--generate-stack-ir --print-stack-ir ، الذي يطبع مكدس الأشعة تحت الحمراء ، ويضمن أن يكون ذلك صالحًا لمحلات WASM.)ref.func (elem declare func $..) سوف تنبعث من Binaryen هذه البيانات عند الضرورة ، لكنها لا تمثلها في IR. وهذا هو ، يمكن العمل على الأشعة تحت الحمراء دون الحاجة إلى التفكير في إعلان مراجع الوظيفة.local.get local.set من أجل التحقق من صحة (هذا يضمن عدم قراءة القيمة الافتراضية للخلفية). على الرغم من التوافق مع مواصفات WASM ، هناك بعض التفاصيل البسيطة التي قد تلاحظها:Block بدون اسم في IR الثنائية مع التحقق من الصحة. لا تنبعث من الكتل بدون اسم في التنسيق الثنائي (نحن فقط ننشر محتوياتها) ، لذلك نتجاهلها لأغراض السكان المحليين غير القابلين للفرق. نتيجةً لذلك ، إذا قرأت نص WASM المنبعث من قبل Binaryen ، فقد ترى ما يبدو أنه رمز لا ينبغي التحقق منه لكل المواصفات (ولا قد لا يتحقق في محلات نصية WASM) ، لكن هذا الاختلاف لن يكون موجودًا في التنسيق الثنائي (الثنائيات المنبعثة من Binaryen ستعمل دائمًا في كل مكان ، إلى جانب الأخطاء بالطبع).requiresNonNullableLocalFixups() في pass.h وفئة LocalStructuralDominance .funcref من ref.func . كما أنها غير قابلة للفرق.try_table على الفروع (إذا كنا نفرع ، فلا يتم إرسال فارغ أبدًا) ، أي أنه يرسل (Ref Exn) وليس (المرجع EXN). في كلتا الحالتين إذا لم يتم تمكين GC ، فإننا ننشر النوع الأقل ثناء في الثنائي. عند قراءة ثنائي ، سيتم تطبيق الأنواع الأكثر دقة أثناء قيامنا ببناء IR.br_if أكثر دقة في IR Binaryen: لديهم نوع القيمة ، عندما تتدفق القيمة. في مواصفات WASM ، يكون النوع هو هدف الفرع ، والذي قد يكون أقل دقة. يضمن استخدام النوع الأكثر دقة هنا أن نتحسن بأفضل طريقة ممكنة ، باستخدام جميع معلومات النوع ، ولكن هذا يعني أن بعض عمليات الاستقطاب قد تبدو مختلفة بعض الشيء. على وجه الخصوص ، عندما ننبعث من br_if الذي يكون نوعه أكثر دقة في IR Binaryen ، فإننا ننبعث من فريق Turm مباشرة بعد ذلك ، بحيث يكون للإخراج النوع الصحيح في مواصفات WASM. قد يتسبب ذلك في بعض البايتات ذات الحجم الإضافي في حالات نادرة (نتجنب هذا النفقات العامة في الحالة المشتركة التي تكون فيها قيمة br_if غير مستخدمة).stringview_wtf16 وما إلى ذلك) أن يتم إلقاؤها باستخدام ref.cast . هذا يبسط الأشعة تحت الحمراء ، لأنه يتيح استخدام ref.cast دائمًا في جميع الأماكن (ويتم تخفيضه إلى ref.as_non_null حيثما أمكن ذلك في المحسن). لا يبدو أن مواصفات StringRef تسمح بذلك ، ولإصلاح أن الكاتب الثنائي سيحل محل ref.cast الذي يلقي طريقة عرض سلسلة إلى نوع غير قابل للفرق إلى ref.as_non_null . يتم تخطي ref.cast لعرض السلسلة الذي لا يتم تخطيه بالكامل.نتيجة لذلك ، قد تلاحظ أن تحويلات الرحلات الجوية (WASM => Binaryen IR => WASM) تغيير رمز قليلاً في بعض حالات الزاوية.
src/wasm-stack.h ). يسمح Stack IR بمجموعة من التحسينات المصممة خصيصًا لشكل آلة المكدس للتنسيق الثنائي لـ WebAssembly (ولكن المكدس IR أقل كفاءة في التحسينات العامة من IR الثنائية الرئيسية). إذا كان لديك ملف WASM الذي تم تحسينه بشكل خاص ، فقد يتسبب التحويل البسيط في رحلة ذهابًا وإيابًا (مجرد قراءة والكتابة ، دون تحسين) إلى وجود اختلافات أكثر وضوحًا ، حيث أن ثنائية الثنائية تتناسب مع تنسيق ثنائي الأشعة تحت الحمراء. إذا قمت أيضًا بالتحسين أثناء تحويل الرحلة المستديرة ، فسيتم تشغيل مكدس IR وسوف يتم تحسين WASM النهائي بشكل أفضل.ملاحظات عند العمل مع Binaryen IR:
تبدو الوظائف الجوهرية الثنائية مثل الدعوات إلى الواردات ، على سبيل المثال ،
( import " binaryen-intrinsics " " foo " ( func $foo ))يتيح تنفيذها بهذه الطريقة قراءتها وكتابتها بواسطة أدوات أخرى ، وتجنب الأخطاء المربكة على خطأ تنسيق ثنائي يمكن أن يحدث في هذه الأدوات إذا كان لدينا امتداد تنسيق ثنائي مخصص.
قد يتم تحسين طريقة جوهرية من قبل المحسن. إذا لم يكن الأمر كذلك ، فيجب تخفيضه قبل شحن WASM ، وإلا فإنه سيبدو كمكالمة لاستيراد غير موجود (وسوف تظهر VMs خطأً في عدم وجود قيمة مناسبة لهذا الاستيراد). لا يتم التخفيض النهائي تلقائيًا. يجب أن يقوم مستخدم الأدوات الجوهرية بتشغيل التمريرة لذلك بشكل صريح ، لأن الأدوات لا تعرف متى يعتزم المستخدم الانتهاء من التحسين ، حيث قد يكون للمستخدم خط أنابيب لخطوات التحسين المتعددة ، أو قد يقوم بإجراء تجربة محلية ، أو تخفيض/تخفيض ، إلخ. لاحظ أنه ، بشكل عام ، قد تكون بعض التحسينات الإضافية ممكنة بعد الانخفاض النهائي ، وبالتالي فإن نمطًا مفيدًا هو التحسين بمجرد أن يكون ذلك بشكل طبيعي ، ثم خفضها بعيدًا ، ثم قم بتحسينها بعد ذلك ، على سبيل المثال:
wasm-opt input.wasm -o output.wasm -O --intrinsic-lowering -Oتحدد كل ما جوهرية دلالاتها ، والتي تتضمن ما يسمح له المحسن بالقيام به وما الذي سيحوله التخفيض النهائي إليه. انظر الجوهارات. يظهر ملخص سريع هنا:
call.without.effects : على غرار call_ref من حيث أنه يتلقى معلمات ، ومرجع إلى وظيفة للاتصال ، والمكالمات هذه الوظيفة مع تلك المعلمات ، باستثناء أن المُحسِّن يمكن أن يفترض أن المكالمة لا تحتوي على آثار جانبية ، وقد تكون قادرة على تحسينها (إذا لم يكن لها نتيجة مستخدمة بشكل عام). يحتوي هذا المستودع على رمز يبني الأدوات التالية في bin/ (انظر تعليمات المبنى):
wasm-opt : TOLDS WEBASSEMBLY ويدير ثنائي الأشعة تحت الحمراء يمر بها.wasm-as : يجمع تجميع webassembly في تنسيق النص (تنسيق التعبير S حاليًا) إلى تنسيق ثنائي (يمر عبر IR الثنائية).wasm-dis : Un-Assembles Webassembly بتنسيق ثنائي في تنسيق نص (يمر عبر IR الثنائية).wasm2js : مترجم Webassembly-to-JS. يتم استخدام هذا من قبل emscripten لتوليد JavaScript كبديل لعلاج webassembly.wasm-reduce : مخفض testcase لملفات Webassembly. بالنظر إلى ملف WASM مثير للاهتمام لسبب ما (على سبيل المثال ، إنه يعطل VM محدد) ، يمكن لـ WASM-Reduce العثور على ملف WASM أصغر يحتوي على نفس الخاصية ، والذي غالبًا ما يكون تصحيحًا أسهل. انظر المستندات لمزيد من التفاصيل.wasm-shell : قذيفة يمكنها تحميل وتفسير رمز التجميع. يمكن أيضًا تشغيل جناح اختبار المواصفات.wasm-emscripten-finalize : يأخذ ثنائي WASM الذي تنتجه LLVM+LLD ويؤدي تمريرات خاصة به.wasm-ctor-eval : أداة يمكنها تنفيذ وظائف (أو أجزاء من الوظائف) في وقت الترجمة.wasm-merge : دمج ملفات WASM متعددة في ملف واحد ، وتوصيل الواردات المقابلة للتصدير كما تفعل. مثل حزمة ل JS ، ولكن ل WASM.wasm-metadce : أداة لإزالة أجزاء من ملفات WASM بطريقة مرنة تعتمد على كيفية استخدام الوحدة النمطية.binaryen.js : مكتبة JavaScript المستقلة التي تكشف الأساليب الثنائية لإنشاء وحدات WASM وتحسينها. للبناء ، راجع binaryen.js على NPM (أو قم بتنزيله مباشرة من GitHub أو UNPKG). الحد الأدنى من المتطلبات: Node.js V15.8 أو Chrome V75 أو Firefox V78.جميع الأدوات الثنائية حتمية ، أي بالنظر إلى نفس المدخلات التي يجب عليك دائمًا الحصول عليها نفس المخرجات. (إذا رأيت حالة تتصرف بخلاف ذلك ، فيرجى تقديم مشكلة.)
تعليمات الاستخدام لكل منها أدناه.
يحتوي Binaryen على الكثير من تمريرات التحسين لجعل التجميع على شبكة الإنترنت أصغر وأسرع. يمكنك تشغيل مُحسِّن Binaryen باستخدام wasm-opt ، ولكن يمكن تشغيلها أيضًا أثناء استخدام أدوات أخرى ، مثل wasm2js و wasm-metadce .
addDefaultFunctionOptimizationPasses .wasm-opt --help حول كيفية ضبطها وتفاصيل أخرى.راجع كل تمريرة تحسين للحصول على تفاصيل ما يفعله ، ولكن إليك نظرة عامة سريعة على بعض تلك ذات الصلة:
if الأسلحة بعض التعليمات المشتركة في نهايتها).block على واحدة خارجية حيثما كان ذلك ممكنا ، مما يقلل من عددها.local.set ذات قيمة موجودة بالفعل في محلي. (يتداخل مع coalescelocals ؛ وهذا يحقق العملية المحددة المذكورة للتو دون كل عمل عمل colescelocals ، وبالتالي فهو مفيد في أماكن أخرى في خط أنابيب التحسين.)br أو br_table (مثل تشغيل block مع br في الوسط إلى if كان ذلك ممكنًا).local.get/set/tee " تمريرة ، القيام بأشياء مثل استبدال مجموعة وحصول على نقل قيمة المجموعة إلى GET (وإنشاء نقطة الإنطلاق) حيثما أمكن. يقوم أيضًا بإنشاء قيم إرجاع block/if/loop بدلاً من استخدام محلي لتمرير القيمة.if التي لا تحتوي على محتويات ، وقطعة ذات قيمة ثابتة بدون آثار جانبية ، block مع طفل واحد ، إلخ."LTO" في ما سبق يعني أن التحسين يشبه وقت الارتباط من حيث أنه يعمل عبر وظائف متعددة ، ولكن إلى حد ما هو دائمًا "LTO" لأنه عادة ما يتم تشغيله على WASM المرتبط النهائي.
تشمل تقنيات التحسين المتقدمة في مُحسِّن ثنائي SSAIFACE و IR Flat و Stack/Poppy IR.
راجع صفحة Wiki كتاب Optimizer لمعرفة المزيد حول كيفية استخدام المحسن بشكل فعال.
يحتوي Binaryen أيضًا على تمريرات مختلفة تقوم بأشياء أخرى غير التحسينات ، مثل تقنين JavaScript ، غير متزامن ، إلخ.
يستخدم Binaryen العارض الفرعي GIT (في وقت الكتابة فقط من أجل GTEST) ، لذلك قبل أن تنشئ ، سيتعين عليك تهيئة العارض الفرعي:
git submodule init
git submodule updateبعد ذلك يمكنك البناء مع CMake:
cmake . && make مطلوب برنامج التحويل البرمجي C ++ 17. على MacOS ، تحتاج إلى تثبيت cmake ، على سبيل المثال ، عبر brew install cmake . لاحظ أنه يمكنك أيضًا استخدام ninja كمولدك: cmake -G Ninja . && ninja .
لتجنب التبعية GTEST ، يمكنك تمرير -DBUILD_TESTS=OFF إلى cmake.
يمكن بناء binaryen.js باستخدام emscripten ، والتي يمكن تثبيتها عبر SDK.
emcmake cmake . && emmake make binaryen_jsemcmake cmake -DBUILD_FOR_BROWSER=ON . && emmake makeباستخدام مثبت Microsoft Visual Studio ، قم بتثبيت مكون "Visual C ++ لأدوات CMake".
توليد المشاريع:
mkdir build
cd build
" %VISUAL_STUDIO_ROOT%Common7IDECommonExtensionsMicrosoftCMakeCMakebincmake.exe " ..استبدال Visual_studio_root مع المسار إلى تثبيت Visual Studio الخاص بك. في حالة استخدام أدوات إنشاء Visual Studio ، سيكون المسار "C: Program Files (x86) Microsoft Visual Studio 2017 buildtools".
من موجه أوامر المطور ، قم ببناء المشاريع المطلوبة:
msbuild binaryen.vcxprojيقوم Cmake بإنشاء مشروع يسمى "all_build.vcxproj" لبناء جميع المشاريع بشكل مناسب.
يتم توزيع التصميمات من خلال مجموعة الأدوات المختلفة التي تستخدم ثنائية ، مثل emscripten ، wasm-pack ، وما إلى ذلك. هناك أيضًا إصدارات رسمية على Github:
https://github.com/webassembly/binaryen/release
يتم تضمين بناء المنصات التالية حاليًا:
Linux-x86_64Linux-arm64MacOS-x86_64MacOS-arm64Windows-x86_64Node.js (تجريبي): منفذ wasm-opt إلى JavaScript+Webassembly. RUN node wasm-opt.js كبديل إسقاط لبناء أصلي لـ wasm-opt ، على أي منصة تعمل Node.js. يتطلب node.js 18+ (ل WASM EH و WASM المواضيع). (لاحظ أن هذا البناء قد يعمل أيضًا في بيئات Deno أو BUN أو أخرى JavaScript+Webassembly ، ولكن يتم اختباره فقط على Node.js.) يجري
bin/wasm-opt [.wasm or .wat file] [options] [passes, see --help] [--help]يتلقى مُحسِّن WASM Webassembly كمدخلات ، ويمكنه تشغيل التحول عليه ، وكذلك طباعته (قبل و/أو بعد التحولات). على سبيل المثال ، حاول
bin/wasm-opt test/lit/passes/name-types.wast -all -S -o -سوف يخرج إحدى حالات الاختبار في جناح الاختبار. لتشغيل تمريرة التحول ، حاول
bin/wasm-opt test/lit/passes/name-types.wast --name-types -all -S -o - يضمن تمريرة name-types أن كل نوع له اسم ويعيد تسمية أسماء طويلة بشكل استثنائي. يمكنك رؤية التغيير أسباب التحول من خلال مقارنة إخراج الأوامر.
من السهل إضافة تمريرات التحول الخاصة بك إلى shell ، ما عليك سوى إضافة ملفات .cpp إلى src/passes ، وإعادة بناء القشرة. على سبيل المثال رمز ، ألقِ نظرة على ممر name-types .
المزيد من الملاحظات:
bin/wasm-opt --help للحصول على القائمة الكاملة للخيارات والمرور.--debug سوف ينبعث من بعض معلومات التصحيح. يمكن تمكين قنوات التصحيح الفردية (المحددة في الكود المصدري عبر #define DEBUG_TYPE xxx ) عن طريق تمريرها كقائمة من السلاسل المفصلية للفاصلة. على سبيل المثال: bin/wasm-opt --debug=binary . يمكن أيضًا تمكين قنوات التصحيح عبر متغير بيئة BINARYEN_DEBUG .يجري
bin/wasm2js [input.wasm file]سيؤدي ذلك إلى طباعة JavaScript إلى وحدة التحكم.
على سبيل المثال ، حاول
bin/wasm2js test/hello_world.watهذا الإخراج يحتوي
function add ( x , y ) {
x = x | 0 ;
y = y | 0 ;
return x + y | 0 | 0 ;
}كترجمة ل
( func $add (; 0 ;) ( type $0 ) ( param $x i32 ) ( param $y i32 ) ( result i32 )
( i32.add
( local.get $x )
( local.get $y )
)
)ناتج WASM2JS هو بتنسيق وحدة ES6 - بشكل أساسي ، يحول وحدة WASM إلى وحدة ES6 (لتشغيلها على المتصفحات القديمة وإصدارات Node.js يمكنك استخدام Babel وما إلى ذلك لتحويلها إلى ES5). دعونا نلقي نظرة على مثال كامل على استدعاء هذا العالم مرحبا وات ؛ أولاً ، قم بإنشاء ملف JS الرئيسي:
// main.mjs
import { add } from "./hello_world.mjs" ;
console . log ( 'the sum of 1 and 2 is:' , add ( 1 , 2 ) ) ;تشغيل هذا (لاحظ أنك تحتاج إلى Node.js جديد بما يكفي مع دعم وحدة ES6):
$ bin/wasm2js test/hello_world.wat -o hello_world.mjs
$ node --experimental-modules main.mjs
the sum of 1 and 2 is: 3تظل الأمور في الاعتبار مع إخراج WASM2JS:
-O أو مستوى تحسين آخر. سوف يتحسن على طول خط الأنابيب بأكمله (WASM و JS). لن يفعل كل ما يفعله Minifer JS ، مثل Minify WhiteSpace ، لذلك لا يزال يتعين عليك تشغيل Minifer JS العادي بعد ذلك. ينفذ wasm-ctor-eval وظائف ، أو أجزاء منها ، في وقت الترجمة. بعد القيام بذلك ، يقوم بتسلسل حالة وقت التشغيل في WASM ، والتي تشبه أخذ "لقطة". عندما يتم تحميل WASM لاحقًا وتشغيله في VM ، ستواصل التنفيذ من تلك النقطة ، دون إعادة تشغيل العمل الذي تم تنفيذه بالفعل.
على سبيل المثال ، فكر في هذا البرنامج الصغير:
( module
;; A global variable that begins at 0.
( global $global ( mut i32 ) ( i32.const 0 ))
( import " import " " import " ( func $import ))
( func " main "
;; Set the global to 1.
( global.set $global
( i32.const 1 ))
;; Call the imported function. This *cannot* be executed at
;; compile time.
( call $import )
;; We will never get to this point, since we stop at the
;; import.
( global.set $global
( i32.const 2 ))
)
)يمكننا تقييم جزء منه في وقت الترجمة مثل هذا:
wasm-ctor-eval input.wat --ctors=main -S -o - هذا يخبرها أن هناك وظيفة واحدة نريد تنفيذها ("CTOR" قصيرة بالنسبة لـ "Global Constructor" ، وهو اسم يأتي من رمز يتم تنفيذه قبل نقطة إدخال البرنامج) ثم طباعته كنص إلى stdout . والنتيجة هي:
trying to eval main
...partial evalling successful, but stopping since could not eval: call import: import.import
...stopping
(module
(type $none_ = > _none (func))
(import " import " " import " (func $import ))
(global $global (mut i32) (i32.const 1))
(export " main " (func $0 _0))
(func $0 _0
(call $import )
(global.set $global
(i32.const 2)
)
)
) يوضح لنا التسجيل أن تمكننا من تقييم جزء من main() ، ولكن ليس كل ذلك ، كما هو متوقع: يمكننا تقييم The First global.get ، ولكن بعد ذلك نتوقف عند الدعوة إلى الوظيفة المستوردة (لأننا لا نعرف ما الذي ستكون عليه هذه الوظيفة عندما يتم تشغيل WASM فعليًا في VM لاحقًا). لاحظ كيف تم تحديث قيمة Global في الإخراج من 0 إلى 1 ، وأنه تمت إزالة global.get الأول: سيستمر WASM الآن في حالة ، عندما نديرها في VM ، ستستمر بسلاسة في الجري من النقطة التي توقف فيها wasm-ctor-eval .
في هذا المثال الصغير ، أنقذنا كمية صغيرة من العمل. يعتمد مقدار العمل الذي يمكن حفظه على برنامجك. (يمكن أن يساعد في إجراء حساب خالص في المقدمة ، وترك المكالمات إلى الواردات في وقت متأخر قدر الإمكان.)
لاحظ أن اسم wasm-ctor-eval مرتبط بوظائف المنشئ العالمي ، كما ذكرنا سابقًا ، ولكن لا يوجد أي قيود على ما يمكنك تنفيذه هنا. يمكن تنفيذ أي تصدير من WASM ، إذا كانت محتوياتها مناسبة. على سبيل المثال ، في emscripten wasm-ctor-eval يتم تشغيله على main() عندما يكون ذلك ممكنًا.
يجمع wasm-merge بين ملفات WASM معًا. على سبيل المثال ، تخيل أن لديك مشروعًا يستخدم ملفات WASM من مجموعة أدوات متعددة. بعد ذلك ، قد يكون من المفيد دمجهم جميعًا في ملف WASM واحد قبل الشحن ، حيث في ملف WASM واحد ، تصبح المكالمات بين الوحدات مجرد مكالمات عادية داخل وحدة ، والتي تسمح لهم بالضغط ، وتم إلغاء الرمز الميت ، وما إلى ذلك ، مما يحسن السرعة والحجم.
تعمل wasm-merge على ملفات WASM العادية. إنه يختلف عن wasm-ld في هذا الصدد ، حيث تعمل wasm-ld على ملفات كائن WASM. يمكن أن تساعد wasm-merge في مواقف Toolchain متعددة الأدوات حيث لا يستخدم أحد أدوات واحدة على الأقل ملفات كائن WASM.
على سبيل المثال ، تخيل أن لدينا هذين ملفين WASM:
;; a.wasm
( module
( import " second " " bar " ( func $second.bar ))
( export " main " ( func $func ))
( func $func
( call $second.bar )
)
) ;; b.wasm
( module
( import " outside " " log " ( func $log ( param i32 )))
( export " bar " ( func $func ))
( func $func
( call $log
( i32.const 42 )
)
)
) أسماء الملفات الموجودة على محرك الأقراص المحلي هي a.wasm و b.wasm ، ولكن لأغراض الدمج / التجميع ، دعنا نقول أن الأول يُعرف باسم "first" والثاني باسم "second" . أي أننا نريد استيراد الوحدة الأولى لـ "second.bar" للاتصال بالوظيفة $func في الوحدة الثانية. فيما يلي أمر WASM-MERGE لذلك:
wasm-merge a.wasm first b.wasm second -o output.wasmنعطيه أول ملف WASM ، ثم اسمه ، ثم ملف WASM الثاني ثم اسمه. الإخراج المدمج هو:
( module
( import " outside " " log " ( func $log ( param i32 )))
( export " main " ( func $func ))
( export " bar " ( func $func_2 ))
( func $func
( call $func_2 )
)
( func $func_2
( call $log
( i32.const 42 )
)
)
) wasm-merge بدمج الملفان في واحد ، ودمج وظائفهما ، ووارداتهما ، وما إلى ذلك ، كل ذلك أثناء إصلاح تعارضات الأسماء وتوصيل الواردات المقابلة للتصدير. على وجه الخصوص ، لاحظ كيف يستدعي $func $func_2 ، وهو بالضبط ما أردنا: $func_2 هي الوظيفة من الوحدة الثانية (أعيدت تسميتها لتجنب تصادم الاسم).
لاحظ أن إخراج WASM في هذا المثال يمكن أن يستفيد من تحسين إضافي. أولاً ، يمكن الآن توضيح المكالمة إلى $func_2 بسهولة ، حتى نتمكن من تشغيل wasm-opt -O3 للقيام بذلك من أجلنا. أيضًا ، قد لا نحتاج إلى جميع الواردات والصادرات ، والتي يمكننا تشغيل WASM-Metadce. يمكن أن يكون سير العمل الجيد هو تشغيل wasm-merge ، ثم wasm-metadce ، ثم ينتهي بـ wasm-opt .
wasm-merge يشبه إلى حد ما حزم ملفات WASM ، بمعنى "Bundler JS" ولكن لـ WASM. وهذا هو ، مع ملفات WASM أعلاه ، تخيل أن لدينا رمز JS هذا للتنظيم وتوصيلها في وقت التشغيل:
// Compile the first module.
var first = await fetch ( "a.wasm" ) ;
first = new WebAssembly . Module ( first ) ;
// Compile the first module.
var second = await fetch ( "b.wasm" ) ;
second = new WebAssembly . Module ( second ) ;
// Instantiate the second, with a JS import.
second = new WebAssembly . Instance ( second , {
outside : {
log : ( value ) => {
console . log ( 'value:' , value ) ;
}
}
} ) ;
// Instantiate the first, importing from the second.
first = new WebAssembly . Instance ( first , {
second : second . exports
} ) ;
// Call the main function.
first . exports . main ( ) ; ما يفعله wasm-merge هو ما يفعله JS بشكل أساسي: إنه يربط الواردات بالتصدير ، وحل الأسماء باستخدام أسماء الوحدات التي قدمتها. وهذا يعني ، من خلال تشغيل wasm-merge ، ننقل عمل توصيل الوحدات النمطية من وقت التشغيل إلى وقت التجميع. نتيجة لذلك ، بعد تشغيل wasm-merge نحتاج إلى أقل بكثير من JS للحصول على نفس النتيجة:
// Compile the single module.
var merged = await fetch ( "merged.wasm" ) ;
merged = new WebAssembly . Module ( merged ) ;
// Instantiate it with a JS import.
merged = new WebAssembly . Instance ( merged , {
outside : {
log : ( value ) => {
console . log ( 'value:' , value ) ;
}
}
} ) ;
// Call the main function.
merged . exports . main ( ) ;ما زلنا بحاجة إلى جلب وتجميع WASM المدمج ، ولتوفيره استيراد JS ، لكن العمل لتوصيل وحدتي WASM لم يعد مطلوبًا.
بشكل افتراضي أخطاء wasm-merge إذا كانت هناك أسماء تصدير متداخلة. أي أن wasm-merge ستتعامل تلقائيًا من معالجة أسماء الوظائف المتداخلة وما إلى ذلك ، لأنها ليست مرئية خارجيًا (لا يزال الكود يتصرف كما هو) ، ولكن إذا قمنا بإعادة تسمية الصادرات ، فيجب تعديل الخارج لتوقع أسماء التصدير الجديدة ، ولذا فإننا نخطئ بدلاً من ذلك في تعارضات الاسم.
إذا كنت ترغب في إعادة تسمية الصادرات ، فسيتم تشغيل wasm-merge مع- --rename-export-conflicts . ستحصل الصادرات اللاحقة على لاحقة ملحق لهم للتأكد من عدم تداخلها مع الصادرات السابقة. اللواحق حتمية ، لذلك بمجرد أن ترى ما هي يمكنك الاتصال بهم من الخارج.
خيار آخر هو الاستخدام- --skip-export-conflicts والتي ستتخطى ببساطة الصادرات اللاحقة التي لها أسماء متضاربة. على سبيل المثال ، يمكن أن يكون هذا مفيدًا في الحالة التي تكون فيها الوحدة الأولى هي الوحيدة التي تتفاعل مع الخارج والوحدة اللاحقة تتفاعل مع الوحدة الأولى.
يستخدم wasm-merge ميزات متعددة الذاكرة ومتعددة الطاولات. وهذا يعني أنه إذا كان لكل منها وحدات إدخال متعددة ذاكرة ، فسيكون لدى WASM الناتج عدة ذكريات ، وسوف يعتمد على ميزة متعددة الذاكرة ، مما يعني أن VMs الأقدم قد لا تكون قادرة على تشغيل WASM. (كحل بديل لمثل هذه VMs الأقدم ، يمكنك تشغيل wasm-opt --multi-memory-lowering لخفض ذكريات متعددة إلى واحدة.)
./check.py (أو python check.py ) سيتم تشغيل wasm-shell ، wasm-opt ، إلخ. على testcases في test/ ، والتحقق من مخرجاتها.
يدعم البرنامج النصي check.py بعض الخيارات:
./check.py [--interpreter = /path/to/interpreter] [TEST1] [TEST2].../check.py --list-suites .emcc أو nodejs في المسار. لن يتم تشغيلهم إذا كان لا يمكن العثور على الأداة ، وسترى تحذيرًا.tests/spec ، في الجهاز الفرعي GIT. الجري ./check.py يجب تحديث تلك. لاحظ أننا نحاول تنفيذ اختبارات WASM-OPT التدريجية لاستخدام lit و filecheck أثناء تعديلها. بالنسبة passes التي يتم إخراج الإخراج ، يمكن القيام بذلك تلقائيًا باستخدام scripts/port_passes_tests_to_lit.py وللتحديدات التي لا passes تلك التي يتم إخراجها ، انظر #4779 للحصول على مثال على كيفية القيام بمنفذ يدوي بسيط.
بالنسبة للاختبارات المضاءة ، يمكن تحديث توقعات الاختبار (خطوط الفحص) تلقائيًا حيث يتم إجراء تغييرات على الثنائي. راجع scripts/update_lit_checks.py .
يمكن أيضًا تحديث الاختبارات غير الضارة تلقائيًا في معظم الحالات. انظر scripts/auto_update_tests.py .
./third_party/setup.py [mozjs | v8 | wabt | all] (أو python third_party/setup.py ) يقوم بتثبيت التبعيات المطلوبة مثل Spidermonkey JS Shell و V8 JS Shell و WABT في third_party/ . البرامج النصية الأخرى تلقائيًا تلتقطها عند تثبيتها.
قم بتشغيل pip3 install -r requirements-dev.txt للحصول على متطلبات الاختبارات lit . لاحظ أنك تحتاج إلى تثبيت pip الموقع في $PATH الخاص بك (على Linux ، ~/.local/bin ).
./scripts/fuzz_opt.py [--binaryen-bin = build/bin] (أو python scripts/fuzz_opt.py ) ستقوم بتشغيل أوضاع مختلفة على مدخلات عشوائية مع تمريرات عشوائية حتى يجد خطأً ممكنًا. انظر صفحة الويكي للحصول على جميع التفاصيل.
Binaryen can read and write source maps (see the -ism and -osm flags to wasm-opt ). It can also read and read source map annotations in the text format, that is,
;; @ src.cpp:100:33
( i32.const 42 ) That 42 constant is annotated as appearing in a file called src.cpp at line 100 and column 33 . Source maps and text format annotations are interchangeable, that is, they both lead to the same IR representation, so you can start with an annotated wat and have Binaryen write that to a binary + a source map file, or read a binary + source map file and print text which will contain those annotations.
The IR representation of source map info is simple: in each function we have a map of expressions to their locations. Optimization passes should update the map as relevant. Often this "just works" because the optimizer tries to reuse nodes when possible, so they keep the same debug info.
The text format annotations support a shorthand in which repeated annotations are not necessary. For example, children are tagged with the debug info of the parent, if they have no annotation of their own:
;; @ src.cpp:100:33
( i32.add
( i32.const 41 ) ;; This receives an annotation of src.cpp:100:33
;; @ src.cpp:111:44
( i32.const 1 )
)The first const will have debug info identical to the parent, because it has none specified, and generally such nesting indicates a "bundle" of instructions that all implement the same source code.
Note that text printing will not emit such repeated annotations, which can be confusing. To print out all the annotations, set BINARYEN_PRINT_FULL=1 in the environment. That will print this for the above add :
[i32] ;; @ src.cpp:100:33
( i32.add
[i32] ;; @ src.cpp:100:33
( i32.const 41 )
[i32] ;; @ src.cpp:111:44
( i32.const 1 )
) (full print mode also adds a [type] for each expression, right before the debug location).
The debug information is also propagated from an expression to its next sibling:
;; @ src.cpp:100:33
( local.set $x
( i32.const 0 )
)
( local.set $y ;; This receives an annotation of src.cpp:100:33
( i32.const 0 )
) You can prevent the propagation of debug info by explicitly mentioning that an expression has not debug info using the annotation ;;@ with nothing else:
;; @ src.cpp:100:33
( local.set $x
;; @
( i32.const 0 ) ;; This does not receive any annotation
)
;; @
( local.set $y ;; This does not receive any annotation
( i32.const 7 )
) This stops the propagatation to children and siblings as well. So, expression (i32.const 7) does not have any debug info either.
There is no shorthand in the binary format. That is, roundtripping (writing and reading) through a binary + source map should not change which expressions have debug info on them or the contents of that info.
The source maps format defines a mapping using segments , that is, if a segment starts at binary offset 10 then it applies to all instructions at that offset and until another segment begins (or the end of the input is reached). Binaryen's IR represents a mapping from expressions to locations, as mentioned, so we need to map to and from the segment-based format when writing and reading source maps.
That is mostly straightforward, but one thing we need to do is to handle the lack of debug info in between things that have it. If we have ABC where B lacks debug info, then just emitting a segment for A and C would lead A 's segment to also cover B , since in source maps segments do not have a size - rather they end when a new segment begins. To avoid B getting smeared in this manner, we emit a source maps entry to B of size 1, which just marks the binary offset it has, and without the later 3 fields of the source file, line number, and column. (This appears to be the intent of the source maps spec, and works in browsers and tools.)
Binaryen also has optional support for DWARF. This primarily just tracks the locations of expressions and rewrites the DWARF's locations accordingly; it does not handle things like re-indexing of locals, and so passes that might break DWARF are disabled by default. As a result, this mode is not suitable for a fully optimized release build, but it can be useful for local debugging.
Binaryen's name was inspired by Emscripten 's: Emscripten's name suggests it converts something into a script - specifically JavaScript - and Binaryen's suggests it converts something into a binary - specifically WebAssembly . Binaryen began as Emscripten's WebAssembly generation and optimization tool, so the name fit as it moved Emscripten from something that emitted the text-based format JavaScript (as it did from its early days) to the binary format WebAssembly (which it has done since WebAssembly launched).
"Binaryen" is pronounced in the same manner as "Targaryen".
نعم ، يفعل. Here's a step-by-step tutorial on how to compile it under Windows 10 x64 with with CMake and Visual Studio 2015 . However, Visual Studio 2017 may now be required. Help would be appreciated on Windows and OS X as most of the core devs are on Linux.