Design: الاقتراح: انتظر

تم إنشاؤها على ١٨ مايو ٢٠٢٠  ·  96تعليقات  ·  مصدر: WebAssembly/design

rreverser وأنا أود اقتراح اقتراح جديد لـ WebAssembly: انتظار .

الدافع وراء هذا الاقتراح هو مساعدة التعليمات البرمجية " المتزامنة " المترجمة إلى WebAssembly ، والتي تقوم بشيء مثل القراءة من ملف:

fread(buffer, 1, num, file);
// the data is ready to be used right here, synchronously

لا يمكن تنفيذ هذا الرمز بسهولة في بيئة مضيفة غير متزامنة بشكل أساسي ، والتي من شأنها تنفيذ "القراءة من ملف" بشكل غير متزامن ، على سبيل المثال على الويب ،

const result = fetch("http://example.com/data.dat");
// result is a Promise; the data is not ready yet!

بمعنى آخر ، الهدف هو المساعدة في مشكلة المزامنة / غير المتزامن الشائعة جدًا مع الوسم على الويب.

تعد مشكلة المزامنة / غير المتزامن مشكلة خطيرة. بينما يمكن كتابة تعليمات برمجية جديدة مع أخذها في الاعتبار ، لا يمكن في كثير من الأحيان إعادة هيكلة قواعد البرمجة الكبيرة الموجودة للتغلب عليها ، مما يعني أنه لا يمكن تشغيلها على الويب. لدينا Asyncify أي أدوات ملف wasm للسماح بالإيقاف المؤقت والاستئناف ، والذي سمح بنقل بعض قواعد التعليمات البرمجية ، لذلك لم يتم حظرنا تمامًا هنا. ومع ذلك ، فإن استخدام أدوات الوسم له عبء كبير ، مثل زيادة بنسبة 50٪ في حجم الكود وتباطؤ بنسبة 50٪ في المتوسط ​​(ولكن في بعض الأحيان أسوأ بكثير) ، لأننا نضيف تعليمات لكتابة / قراءة مرة أخرى في الحالة المحلية ومكدس المكالمات و هكذا دواليك. هذا الحمل هو قيد كبير ويستبعد Asyncify في كثير من الحالات!

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

الفكرة باختصار

تكمن المشكلة الأساسية هنا بين كون كود wasm متزامنًا وبيئة المضيف غير المتزامنة. لذلك يركز نهجنا على حدود مثيل wasm والخارج. من الناحية المفاهيمية ، عند تنفيذ تعليمة جديدة await ، فإن مثيل الوسم "ينتظر" شيئًا من الخارج. قد يختلف معنى "انتظار" على الأنظمة الأساسية المختلفة ، وقد لا يكون مناسبًا لجميع الأنظمة الأساسية (مثل قد لا تجد جميع الأنظمة الأساسية أن اقتراح wasm atomics مناسبًا) ، ولكن على منصة الويب على وجه التحديد ، سينتظر مثيل wasm عند وعد ويتوقف مؤقتًا حتى يتم حلها أو رفضها. على سبيل المثال ، يمكن أن يتوقف مثيل wasm مؤقتًا في عملية شبكة fetch ، ويكتب شيئًا كهذا في .wat :

;; call an import which returns a promise
call $do_fetch
;; wait for the promise just pushed to the stack
await
;; do stuff with the result just pushed to the stack

لاحظ التشابه العام مع await في JS ولغات أخرى. في حين أن هذا لا يتطابق معهما (انظر التفاصيل أدناه) ، فإن الميزة الرئيسية هي أنه يسمح لكتابة رمز متزامن المظهر (أو بالأحرى ، لتجميع كود متزامن المظهر في wasm).

التفاصيل

المواصفات الأساسية wasm

التغييرات التي تم إجراؤها على مواصفات الوصلة الأساسية ضئيلة للغاية:

  • أضف نوع waitref .
  • أضف تعليمات await .

يتم تحديد نوع لكل تعليمات await (مثل call_indirect ) ، على سبيل المثال:

;; elaborated wat from earlier, now with full types

(type $waitref_=>_i32 (func (param waitref) (result i32)))
(import "env" "do_fetch" (func $do_fetch (result waitref)))

;; call an import which returns a promise
call $do_fetch
;; wait for the promise just pushed to the stack
await (type $waitref_=>_i32)
;; do stuff with the result just pushed to the stack

يجب أن يتلقى النوع waitref ، ويمكنه إرجاع أي نوع (أو لا شيء).

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

هذا كل شيء عن المواصفات الأساسية wasm!

Wasm JS المواصفات

تعد التغييرات التي تم إجراؤها على مواصفات wasm JS (والتي تؤثر فقط على بيئات JS مثل الويب) أكثر إثارة للاهتمام:

  • waitref الصالحة هي وعد JS.
  • عندما يتم تنفيذ await على أحد الوعد ، فإن مثيل wasm بأكمله يتوقف مؤقتًا وينتظر أن يتم حل هذا الوعد أو رفضه.
  • إذا تم حل الوعد ، يستأنف المثيل التنفيذ بعد دفع القيمة المستلمة من الوعد إلى المكدس (إذا كان هناك واحد)
  • إذا تم رفض الوعد ، فإننا نستأنف التنفيذ ونطرح استثناءً من موقع await .

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

كيف يبدو الأمر لـ JS عندما تستدعي مثيل wasm الذي يتوقف بعد ذلك مؤقتًا؟ لتوضيح ذلك ، دعنا أولاً نلقي نظرة على مثال شائع تمت مواجهته عند نقل التطبيقات الأصلية إلى wasm ، وهي حلقة حدث:

void event_loop_iteration() {
  // ..
  while (auto task = getTask()) {
    task.run(); // this *may* be a network fetch
  }
  // ..
}

تخيل أنه يتم استدعاء هذه الوظيفة مرة واحدة لكل requestAnimationFrame . ينفذ المهام الموكلة إليه ، والتي قد تشمل: العرض ، والفيزياء ، والصوت ، وجلب الشبكة. إذا كان لدينا حدث جلب من الشبكة ، فعندئذ فقط ينتهي بنا الأمر بتشغيل تعليمات await fetch . قد نقوم بذلك 0 مرة لمكالمة واحدة event_loop_iteration ، أو مرة واحدة ، أو عدة مرات. نحن نعرف فقط ما إذا كنا سننتهي بفعل ذلك أثناء تنفيذ هذا الوسم - ليس من قبل ، ولا سيما في مستدعي JS لتصدير wasm هذا. لذلك يجب أن يكون هذا المتصل جاهزًا للمثيل إما أن يتوقف مؤقتًا أو لا.

يمكن أن يحدث موقف مشابه إلى حد ما في JavaScript خالص:

function foo(bar) {
  // ..
  let result = bar(42);
  // ..
}

يحصل foo على دالة JS bar ويستدعيها ببعض البيانات. في JS bar قد تكون وظيفة غير متزامنة أو قد تكون عادية. إذا كان غير متزامن ، فإنه يُرجع وعدًا ، وينتهي تنفيذه لاحقًا. إذا كان الأمر طبيعيًا ، فسيتم تنفيذه قبل العودة ويعيد النتيجة الفعلية. يمكن أن يفترض foo أنه يعرف أي نوع bar (لا يوجد نوع مكتوب بلغة JS ، في الواقع قد لا يكون bar حتى دالة!) ، أو يمكنه التعامل كلا النوعين من الوظائف لتكون عامة بالكامل.

الآن ، عادة ما تعرف بالضبط ما هي مجموعة الوظائف bar قد تكون! على سبيل المثال ، ربما تكون قد كتبت foo و bar s المحتملة بالتنسيق ، أو قمت بتوثيق التوقعات بالضبط. لكن تفاعل wasm / JS الذي نتحدث عنه هنا هو في الواقع أكثر شبهاً بالحالة التي لا يوجد فيها مثل هذا الاقتران الوثيق بين الأشياء ، وحيث تحتاج في الواقع إلى التعامل مع كلتا الحالتين. كما ذكرنا سابقًا ، يتطلب المثال event_loop_iteration ذلك. ولكن بشكل أكثر عمومية ، غالبًا ما يكون wasm هو تطبيقك المترجم بينما JS هو رمز "وقت تشغيل" عام ، لذلك يتعين على JS التعامل مع جميع الحالات. يمكن لـ JS القيام بذلك بسهولة ، بالطبع ، على سبيل المثال استخدام result instanceof Promise للتحقق من النتيجة ، أو استخدام JS await :

async function runEventLoopIteration() {
  // await in JavaScript can handle Promises as well as regular synchronous values
  // in the same way, so the log is guaranteed to be written out consistently after
  // the operation has finished (note: this handles 0 or 1 iterations, but could be
  // generalized)
  await wasm.event_loop_iteration();
  console.log("the event loop iteration is done");
}

(لاحظ أنه إذا لم نكن بحاجة إلى هذا console.log ، فلن نحتاج إلى JS await في هذا المثال ، وسيكون لدينا مجرد مكالمة عادية لتصدير wasm)

لتلخيص ما ورد أعلاه ، نقترح أن يتم تصميم سلوك مثيل تم إيقاف مؤقت على نموذج JS لوظيفة قد تكون أو لا تكون غير متزامنة ، والتي يمكننا أن نقولها على النحو التالي:

  • عندما يتم تنفيذ await ، يخرج مثيل wasm على الفور مرة أخرى لمن استدعاه (عادةً ما يكون JS يستدعي تصدير wasm ، لكن انظر الملاحظات لاحقًا). يتلقى المتصل وعدًا يمكنه استخدامه لمعرفة موعد انتهاء تنفيذ الوسم ، وللحصول على نتيجة إذا كان هناك وعد.

Toolchain / دعم المكتبة

من خلال تجربتنا مع Asyncify والأدوات ذات الصلة ، من السهل (والممتع!) كتابة القليل من JS للتعامل مع حالة الانتظار. بصرف النظر عن الخيارات المذكورة سابقًا ، يمكن للمكتبة القيام بأحد الإجراءات التالية:

  1. التفاف حول مثيل wasm لجعل صادراته دائمًا تعيد الوعد. يوفر ذلك واجهة بسيطة لطيفة للخارج (ومع ذلك ، فإنه يضيف عبءًا على المكالمات السريعة إلى wasm التي لا تتوقف مؤقتًا). هذا ما تفعله مكتبة مساعد Asyncify المستقلة ، على سبيل المثال.
  2. اكتب حالة عامة عند توقف مثيل مؤقتًا وتحقق من ذلك من JS الذي استدعى المثيل. هذا ما يفعله تكامل Asyncify من Emscripten ، على سبيل المثال.

يمكن بناء المزيد على مثل هذه الأساليب أو غيرها. نفضل ترك كل ذلك لسلاسل الأدوات والمكتبات لتجنب التعقيد في الاقتراح وفي الأجهزة الافتراضية.

التنفيذ والأداء

يجب أن تساعد عدة عوامل في الحفاظ على بساطة تطبيقات VM:

  1. توقف مؤقت / استئناف يحدث فقط في انتظار ، ونحن نعرف مواقعهم بشكل ثابت داخل كل وظيفة.
  2. عندما نستأنف نواصل بالضبط من حيث تركنا الأشياء ، ونحن نفعل ذلك مرة واحدة فقط. على وجه الخصوص ، نحن لا نقوم بتنفيذ "fork" مطلقًا: لا شيء هنا يعود مرتين ، على عكس C setjmp أو coroutine في نظام يسمح بالاستنساخ / التفرّع.
  3. من المقبول أن تكون سرعة await أبطأ من سرعة المكالمة العادية لـ JS ، لأننا سننتظر وعدًا ، والذي يعني على الأقل أنه تم تخصيص الوعد وأننا ننتظر حلقة الحدث ( الذي يحتوي على حد أدنى من النفقات العامة بالإضافة إلى احتمال انتظار تشغيل أشياء أخرى حاليًا ). أي أن حالات الاستخدام هنا لا تتطلب من منفذي VM إيجاد طرق لكسب await بسرعة فائقة. نريد فقط await ليكون فعالًا مقارنة بالمتطلبات هنا ، ونتوقع على وجه الخصوص أن يكون أسرع بكثير من النفقات العامة الكبيرة لـ Asyncify.

بالنظر إلى ما سبق ، فإن التنفيذ الطبيعي هو نسخ المكدس عندما نتوقف مؤقتًا. في حين أن هذا يحتوي على بعض النفقات العامة ، نظرًا لتوقعات الأداء هنا ، يجب أن يكون معقولًا جدًا. وإذا قمنا بنسخ المكدس فقط عندما نتوقف مؤقتًا ، فيمكننا تجنب القيام بعمل إضافي للتحضير للإيقاف المؤقت. وهذا يعني أنه لا ينبغي أن يكون هناك نفقات عامة إضافية (والتي تختلف كثيرًا عن Asyncify!)

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

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

نحن مهتمون جدًا بسماع تعليقات منفذي VM في هذا القسم!

توضيحات

يقوم هذا الاقتراح بإيقاف تشغيل WebAssembly فقط مؤقتًا للخارج إلى من يستدعي مثيل wasm. لا يسمح بإيقاف إطارات مكدس المضيف (JS أو المتصفح) مؤقتًا. يعمل await على مثيل wasm ، ويؤثر فقط على إطارات المكدس بداخله.

لا بأس من الاتصال بمثيل WebAssembly أثناء حدوث توقف مؤقت ، ويمكن أن تكون أحداث الإيقاف المؤقت / الاستئناف المتعددة في حالة طيران في وقت واحد. (لاحظ أنه إذا اتخذ VM نهج نسخ المكدس ، فهذا لا يعني أنه يجب تخصيص مكدس جديد في كل مرة ندخل فيها الوحدة ، حيث نحتاج فقط إلى نسخها إذا توقفنا بالفعل.)

صلة بالمقترحات الأخرى

استثناءات

الوعد برفض استثناء يعني أن هذا الاقتراح يعتمد على اقتراح استثناءات ISM.

كوروتين

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

على وجه التحديد ، يتعامل اقتراح coroutines مع إنشاء المكدس بطريقة صريحة (يتم توفير الإرشادات لإنشاء coroutine ، لإيقاف واحد مؤقتًا ، وما إلى ذلك). لا يتحدث اقتراح الانتظار إلا عن الإيقاف المؤقت والاستئناف وبالتالي فإن التعامل مع المكدس ضمني . يعد التعامل الصريح مع المكدس مناسبًا عندما تعلم أنك تقوم بإنشاء coroutines معينة ، بينما يكون الأمر الضمني مناسبًا عندما تعرف فقط أنك بحاجة إلى انتظار شيء ما أثناء التنفيذ (انظر المثال السابق باستخدام event_loop_iteration ).

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

هناك اختلاف مهم آخر وهو أن await هو تعليمة واحدة توفر جميع احتياجات الوحدة النمطية wasm من أجل إصلاح عدم تطابق المزامنة / غير المتزامن مع الويب (انظر المثال الأول .wat من نفس بداية). كما أنه من السهل جدًا استخدامه من جانب JS والذي يمكنه فقط تقديم و / أو تلقي وعد (بينما قد يكون من المفيد إضافة رمز مكتبة صغير ، كما ذكرنا سابقًا ، يمكن أن يكون ضئيلاً للغاية).

من الناحية النظرية ، يمكن تصميم الاقتراحين ليكونا متكاملين. ربما يكون await أحد الإرشادات في اقتراح coroutines بطريقة أو بأخرى؟ هناك خيار آخر وهو السماح لـ await بالعمل على coroutine (بشكل أساسي إعطاء مثيل wasm طريقة سهلة لانتظار نتائج coroutine).

الواسي # 276

بالصدفة تم نشر WASI # 276 بواسطة tqchen بينما كنا ننتهي من كتابة هذا. يسعدنا جدًا أن نرى ذلك لأنه يشاركنا إيماننا بأن coroutines والدعم غير المتزامن هما وظيفتان منفصلتان.

نعتقد أن تعليمة await يمكن أن تساعد في تنفيذ شيء مشابه جدًا لما هو مقترح هناك (الخيار C3) ، مع اختلاف أنه لن تكون هناك حاجة إلى أن تكون عمليات syscalls خاصة غير متزامنة ، ولكن بدلاً من ذلك ، قد تؤدي بعض عمليات syscalls إلى إرجاع waitref والتي يمكن أن تكون await -ed.

بالنسبة إلى JavaScript ، عرّفنا الانتظار على أنه إيقاف مؤقت لمثيل wasm ، وهو أمر منطقي لأنه يمكن أن يكون لدينا مثيلات متعددة بالإضافة إلى JavaScript على الصفحة. ومع ذلك ، في بعض بيئات الخوادم ، قد يكون هناك فقط المضيف ومثيل واحد wasm ، وفي هذه الحالة ، يمكن أن يكون الانتظار أبسط بكثير ، وربما انتظارًا حرفيًا على واصف الملف أو على وحدة معالجة الرسومات. أو قد يؤدي الانتظار إلى إيقاف جهاز wasm VM بالكامل مع الاستمرار في تشغيل حلقة الحدث. ليس لدينا أفكار محددة هنا بأنفسنا ، ولكن بناءً على المناقشة في هذه المسألة قد تكون هناك احتمالات مثيرة للاهتمام هنا ، نحن فضوليون بشأن ما يعتقده الناس!

حالة الزاوية: مثيل wasm => مثيل wasm => انتظار

في بيئة JS ، عندما تتوقف نسخة wasm مؤقتًا ، فإنها تعود على الفور لمن دعاها. لقد وصفنا ما يحدث إذا كان المتصل من JS ، ويحدث نفس الشيء إذا كان المتصل هو المتصفح (على سبيل المثال ، إذا قمنا بعمل setTimeout على تصدير wasm يتوقف مؤقتًا ؛ ولكن لا يحدث شيء مثير للاهتمام هناك ، مثل الوعد الذي تم إرجاعه يتم تجاهله فقط). ولكن هناك حالة أخرى ، من المكالمة القادمة من wasm ، أي ، حيث يستدعي مثيل wasm A مباشرة تصديرًا من المثال B ، و B يتوقف مؤقتًا. يجعلنا الإيقاف المؤقت نخرج فورًا من B ونعيد Promise .

عندما يكون المتصل هو JavaScript ، فهذه ليست مشكلة ، باعتبارها لغة ديناميكية ، وفي الحقيقة من المعقول أن نتوقع أن يقوم المتصل بالتحقق من النوع كما تمت مناقشته سابقًا. عندما يكون المتصل هو WebAssembly ، وهو مكتوب بشكل ثابت ، يكون هذا أمرًا محرجًا. إذا لم نفعل شيئًا في الاقتراح لهذا ، فسيتم تحويل القيمة ، في مثالنا من وعد إلى أي مثيل يتوقعه A (إذا كان i32 ، فسيتم تحويله إلى أ 0 ). بدلاً من ذلك ، نقترح حدوث خطأ:

  • إذا كان مثيل wasm يستدعي (مباشرة أو باستخدام call_indirect ) دالة من مثيل آخر لـ wasm ، وأثناء التشغيل في المثيل الآخر ، يتم تنفيذ await ، فإن استثناء RuntimeError هو ألقيت من موقع await .

الأهم من ذلك ، يمكن القيام بذلك بدون نفقات إضافية ما لم يتم الإيقاف المؤقت ، أي الاحتفاظ بالمكالمات العادية wasm instance -> wasm instance بأقصى سرعة ، عن طريق التحقق من المكدس فقط عند القيام بإيقاف مؤقت.

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

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

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

النظر في النهج البديلة

ربما لا نحتاج إلى تعليمات await جديدة على الإطلاق ، إذا توقفت wasm مؤقتًا عندما يُرجع استيراد JS وعدًا؟ تكمن المشكلة في أنه في الوقت الحالي عندما تقوم JS بإرجاع وعد فهذا ليس خطأ. مثل هذا التغيير غير المتوافق مع الإصدارات السابقة قد يعني أن wasm لم يعد بإمكانه تلقي وعد دون توقف ، ولكن هذا قد يكون مفيدًا أيضًا.

هناك خيار آخر درسناه وهو وضع علامة على الواردات بطريقة ما بحيث نقول "يجب أن يتوقف هذا الاستيراد مؤقتًا إذا أعاد التعهد". فكرنا في الخيارات المختلفة لكيفية وضع علامة عليها ، سواء في JS أو على جانب wasm ، لكننا لم نجد أي شيء يبدو صحيحًا. على سبيل المثال ، إذا وضعنا علامة على الواردات من جانب JS ، فلن تعرف الوحدة النمطية wasm ما إذا كانت المكالمة إلى استيراد ستتوقف مؤقتًا أم لا حتى خطوة الارتباط ، عند وصول الواردات. أي أن مكالمات الاستيراد والتوقف المؤقت ستمزج "معًا". يبدو أن الأمر الأكثر وضوحًا هو مجرد الحصول على تعليمات جديدة لهذا ، await ، وهي صريحة بشأن الانتظار. من الناحية النظرية ، قد تكون هذه القدرة مفيدة خارج الويب أيضًا (انظر الملاحظات سابقًا) ، لذا فإن وجود تعليمات للجميع قد يجعل الأمور أكثر اتساقًا بشكل عام.

المناقشات السابقة ذات الصلة

https://github.com/WebAssembly/design/issues/1171
https://github.com/WebAssembly/design/issues/1252
https://github.com/WebAssembly/design/issues/1294
https://github.com/WebAssembly/design/issues/1321

شكرا على القراءة ، نرحب بالتعليقات!

التعليق الأكثر فائدة

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

لدى Await سلوك يمكن ملاحظته أبسط بكثير من coroutines العامة أو تبديل المكدس ، لكن الأشخاص VM الذين تحدثت معهم يتفقون مع rossberg على أن عمل VM في النهاية من المحتمل أن يكون مشابهًا لكليهما. ويعتقد بعض الأشخاص على الأقل من VM أننا سنحصل على coroutines أو تبديل المكدس بأي طريقة ، وأنه يمكننا دعم حالات استخدام الانتظار باستخدام ذلك. سيعني ذلك إنشاء coroutine / مكدس جديد على كل مكالمة في wasm (على عكس هذا الاقتراح) ، ولكن على الأقل يعتقد بعض الأشخاص VM أن ذلك يمكن أن يتم بالسرعة الكافية.

بالإضافة إلى عدم اهتمام الأشخاص VM ، لدينا بعض الاعتراضات القوية على هذا الاقتراح هنا من fgmccabe و RossTate ، كما تمت مناقشته أعلاه. نحن نختلف في بعض الأمور لكنني أقدر وجهات النظر تلك والوقت الذي تم فيه شرحها.

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

لاحظ أن جزء JS من هذا الاقتراح قد يكون ذا صلة في المستقبل ، مثل JS sugar أساسًا من أجل تكامل الوعد المريح. سنحتاج إلى انتظار تبديل المكدس أو coroutines ومعرفة ما إذا كان هذا يمكن أن يعمل علاوة على ذلك. لكنني لا أعتقد أن الأمر يستحق إبقاء القضية مفتوحة لذلك ، لذا أغلق.

ال 96 كومينتر

كتابة ممتازة! تعجبني فكرة التعليق الذي يتحكم فيه المضيف. يناقش اقتراح rossberg أيضًا أنظمة التأثيرات الوظيفية ، وأنا أعترف بأنني لست خبيرًا معهم ، ولكن للوهلة الأولى يبدو أن تلك الأنظمة يمكن أن تفي بنفس حاجة تدفق التحكم غير المحلي.

بخصوص: "بالنظر إلى ما سبق ، فإن التنفيذ الطبيعي هو نسخ المكدس عندما نتوقف مؤقتًا." كيف يعمل هذا لمكدس التنفيذ؟ أتخيل أن معظم محركات JIT تشترك في مكدس تنفيذ C الأصلي بين JS و wasm ، لذلك لست متأكدًا مما يعنيه الحفظ والاستعادة في هذا السياق. هل يعني هذا الاقتراح أن مكدس تنفيذ wasm يجب أن يكون افتراضيًا بطريقة ما؟ كان تجنب IIUC لاستخدام مكدس C مثل هذا أمرًا صعبًا للغاية عندما حاول Python فعل شيء مشابه: https://github.com/stackless-dev/stackless/wiki.

أشارك قلقًا مشابهًا لـ @ sbc100. يعد نسخ المكدس عملية صعبة بطبيعتها ، خاصةً إذا لم يكن جهاز VM الخاص بك يحتوي بالفعل على تطبيق GC.

@ sbc100

هل يعني هذا الاقتراح أن مكدس تنفيذ wasm يجب أن يكون افتراضيًا بطريقة ما؟

يجب أن أترك هذا لمنفذي VM لأنني لست خبيرًا في ذلك. وأنا لا أفهم العلاقة مع الثعبان غير المتراكم ، لكن ربما لا أعرف ما هو جيد بما يكفي لفهم الاتصال ، آسف!

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

(لسنا متأكدين مما إذا كانت هذه الأساليب يمكن أن تنجح في الأجهزة الافتراضية الخاصة بـ wasm أم لا - على أمل أن نسمع من المنفذين إذا كانت الإجابة بنعم أم لا ، وما إذا كانت هناك خيارات أفضل!)

تضمين التغريدة

هل يمكنك أن تشرح بمزيد من التفصيل ما تقصده بعبارة GC لتسهيل الأمور؟ أنا لا أتبع.

تمتلك GCskripken غالبًا (ولكن ليس دائمًا) القدرة على السير في مكدس ، وهو أمر ضروري إذا كنت بحاجة إلى إعادة كتابة المؤشرات على المكدس للإشارة إلى المكدس الجديد. ربما يمكن للشخص الذي يعرف المزيد عن JSC تأكيد أو نفي ذلك.

تضمين التغريدة

شكرًا ، الآن أرى ما تقوله.

لا نقترح أن السير في المكدس بهذه الطريقة الكاملة (تحديد كل محلي على طول الطريق ، وما إلى ذلك) ضروري للقيام بذلك. (للاطلاع على الأساليب الأخرى الممكنة ، راجع الرابط في تعليقي الأخير حول طرق تنفيذ coroutine منخفضة المستوى.)

أعتذر عن مصطلحات "نسخ المكدس" في الاقتراح - أرى أنه لم يكن واضحًا بدرجة كافية ، بناءً على تعليقاتك و @ sbc100 . مرة أخرى ، لا نريد اقتراح نهج محدد لتطبيق VM. أردنا فقط أن نقول أنه إذا كان نسخ المكدس ضروريًا في بعض الأساليب ، فلن يمثل ذلك مشكلة بالنسبة للسرعة.

بدلاً من اقتراح نهج تنفيذ محدد ، نأمل أن نسمع من الأشخاص VM كيف يعتقدون أنه يمكن القيام بذلك!

أنا متحمس جدًا لرؤية هذا الاقتراح. كان لدى Lucet مشغلي yield و resume لفترة من الوقت الآن ، ونحن نستخدمهم على وجه التحديد للتفاعل مع التعليمات البرمجية غير المتزامنة التي تعمل في بيئة مضيف Rust.

كان هذا واضحًا إلى حد ما لإضافته إلى Lucet ، نظرًا لأن تصميمنا ملتزم بالفعل بالحفاظ على مكدس منفصل لتنفيذ Wasm ، لكن يمكنني أن أتخيل أنه قد يمثل بعض صعوبات التنفيذ لأجهزة VM التي لا تفعل ذلك.

هذا الاقتراح يبدو رائعًا! لقد كنا نحاول الوصول إلى طريقة جيدة لإدارة الشفرة غير المتزامنة على wasmer-js قليلاً (نظرًا لعدم توفر إمكانية الوصول إلى العناصر الداخلية لـ VM في سياق المستعرض).

بدلاً من اقتراح نهج تنفيذ محدد ، نأمل أن نسمع من الأشخاص VM كيف يعتقدون أنه يمكن القيام بذلك!

أعتقد أن استخدام إستراتيجية رد الاتصال للوظائف غير المتزامنة قد يكون أسهل طريقة لتدوير الأشياء وأيضًا بطريقة لا تعرف اللغة.

يبدو أنه يمكن استدعاء .await في JsPromise داخل دالة الصدأ باستخدام wasm-bindgen-futures ؟ كيف يمكن أن يعمل هذا بدون تعليمات await المقترحة هنا؟ أنا آسف لجهلي ، فأنا أبحث عن حلول لاستدعاء الجلب داخل الصنبور وأتعلم عن Asyncify ، لكن من الصعب أن يكون حل الصدأ أبسط. ما الذي أفتقده هنا؟ هل يمكن لأي شخص أن يوضح لي ذلك؟

أنا متحمس جدًا لهذا الاقتراح. الميزة الرئيسية للاقتراح هي بساطته ، حيث يمكننا إنشاء واجهات برمجة التطبيقات (API) التي تتم مزامنتها مع POV الخاص بـ wasm ، كما أنه يجعل من السهل جدًا نقل التطبيقات دون الحاجة إلى التفكير صراحة في عمليات الاسترجاعات وعدم التزامن / الانتظار. سيمكننا ذلك من جلب التعلم الآلي القائم على WASM و WebGPU إلى نظام wasm vms الأصلي باستخدام واجهة برمجة تطبيقات أصلية واحدة وتشغيلها على كل من الويب والأصلي.

الشيء الوحيد الذي أعتقد أنه يستحق المناقشة هو التوقيع على الوظائف التي من المحتمل أن تستدعي الانتظار. تخيل أن لدينا الوظيفة التالية

int test() {
   await();
   return 1;
}

توقيع الوظيفة المقابلة هو () => i32 . بموجب الاقتراح الجديد ، يمكن أن تؤدي المكالمات التي يتم إجراؤها إلى الاختبار إما إلى إرجاع i32 أو Promise<i32> . لاحظ أنه من الصعب مطالبة المستخدم بالإعلان عن التوقيع الجديد بشكل ثابت (لأن تكلفة نقل الكود ، ويمكن أن تكون مكالمات غير مباشرة داخل الوظيفة التي لا نعرف أن المكالمات تنتظرها).

هل يجب أن يكون لدينا وضع اتصال منفصل في الوظيفة المصدرة (مثل المكالمة غير المتزامنة) للإشارة إلى أن الانتظار مسموح به أثناء وقت التشغيل؟

من ناحية المصطلحات ، تشبه العملية المقترحة عملية إنتاجية في أنظمة التشغيل. نظرًا لأنه يعطي التحكم لنظام التشغيل (في هذه الحالة wasm VM) لانتظار انتهاء طلب النظام.

إذا فهمت هذا الاقتراح بشكل صحيح ، أعتقد أنه يكافئ تقريبًا إزالة التقييد بأن await في JS لا يمكن استخدامه إلا في وظائف async . أي أنه على الجانب الآخر ، يمكن أن يكون $ # waitref externref وبدلاً من التعليمات await يمكن أن يكون لديك دالة مستوردة $await : [externref] -> [] ، ومن ناحية JS يمكنك توفير foo(promise) => await promise كوظيفة للاستيراد. في الاتجاه الآخر ، إذا كنت تريد رمز JS الذي تريد await على وعد خارج وظيفة async ، يمكنك تقديم هذا الوعد إلى وحدة wasm التي تستدعي ببساطة await على المدخلات. هل هذا فهم صحيح؟

RossTate ليس تمامًا ، AIUI. يمكن أن يكون رمز wasm await وعدًا (أطلق عليه promise1 ) ، ولكن فقط تنفيذ wasm هو الذي سينتج ، وليس JS. سيعيد رمز wasm وعدًا مختلفًا (أطلق عليه promise2 ) لمتصل JS. عندما يتم حل promise1 ، فسيستمر تنفيذ wasm. أخيرًا ، عندما يخرج رمز wasm هذا بشكل طبيعي ، سيتم حل promise2 بنتيجة دالة wasm.

تضمين التغريدة

هل يجب أن يكون لدينا وضع اتصال منفصل في الوظيفة المصدرة (مثل المكالمة غير المتزامنة) للإشارة إلى أن الانتظار مسموح به أثناء وقت التشغيل؟

مثير للاهتمام - أين ترى الفائدة؟ كما قلت ، لا توجد طريقة للتأكد مما إذا كان التصدير سينتهي به الأمر بـ await أم لا ، في حالات النقل الشائعة ، لذلك في أحسن الأحوال لا يمكن استخدامه إلا في بعض الأحيان. هل سيساعد هذا الأجهزة الافتراضية داخليًا على الرغم من أنه ربما؟

قد يؤدي وجود إعلان صريح إلى التأكد من أن المستخدم يصرح بقصده بوضوح ، ويمكن للجهاز الظاهري إرسال رسالة خطأ مناسبة إذا كانت نية المستخدم لا تقوم بإجراء مكالمة تعمل بشكل غير متزامن.

من وجهة نظر المستخدم ، فإنه يجعل أيضًا كتابة الكود أكثر اتساقًا. على سبيل المثال ، يمكن للمستخدم كتابة الكود التالي ، حتى إذا لم يستدعي الاختبار انتظارًا ، وتعيد واجهة النظام Promise.resolve (test ()) تلقائيًا.

await inst.exports_async.test();

يبدو أنه يمكن استدعاء .await في JsPromise داخل دالة الصدأ باستخدام wasm-bindgen-futures ؟ await المقترحة هنا؟ أنا آسف لجهلي ، فأنا أبحث عن حلول لاستدعاء الجلب داخل الصنبور وأتعلم عن Asyncify ، لكن من الصعب أن يكون حل الصدأ أبسط. هل يمكن لأي شخص أن يوضح لي؟

malbarbo هناك القليل من التداخل بين اثنين على الرغم من حالات الاستخدام المتشابهة ؛ ما يفعله Rust هو في الأساس coroutines الكاملة ، والتي هي أكثر في نطاق الاقتراح المرتبط الآخر.

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

ما يحاول هذا الاقتراح تحقيقه بدلاً من ذلك هو طريقة لانتظار محاولات syscall التي يوفرها المضيف ، حيث تكون غير متزامنة هي مجرد تفاصيل تنفيذ ، وبالتالي يمكن استدعاء هذه الوظائف من أي مكان في قاعدة بيانات موجودة بطريقة متوافقة مع الإصدارات السابقة ، بدون الحاجة إلى إعادة كتابة كيفية عمل التطبيق بالكامل. (مثال على ذلك ملف I / O ، أي اللغات المصدرية بما في ذلك C / C ++ / Rust تتوقع عادةً أن تكون متاحة بشكل متزامن ، ولكنها ليست على الويب على سبيل المثال.)

من وجهة نظر المستخدم ، فإنه يجعل أيضًا كتابة الكود أكثر اتساقًا. على سبيل المثال ، يمكن للمستخدم كتابة الكود التالي ، حتى إذا لم يستدعي الاختبار انتظارًا ، وتعيد واجهة النظام Promise.resolve (test ()) تلقائيًا.

tqchen لاحظ أنه يمكن للمستخدم فعل ذلك بالفعل كما هو موضح في المثال في اختبار الاقتراح. أي أن JavaScript يدعم بالفعل القيم المتزامنة وغير المتزامنة ويعالجها في عامل تشغيل await بنفس الطريقة.

إذا كان الاقتراح هو فرض نوع ثابت واحد ، فإننا نعتقد أنه يمكن القيام بذلك على مستوى نظام النسالة أو النوع أو مستوى غلاف JavaScript دون إدخال التعقيد على جانب WebAssembly الأساسي أو تقييد منفذي هذه الأغلفة.

آه ، شكرًا على التصحيحbinji.

في هذه الحالة ، هل ما يلي مكافئ تقريبًا؟ أضف دالة WebAssembly.instantiateAsync(moduleBytes, imports, "name1", "name2") إلى JS API. افترض أن moduleBytes يحتوي على عدد من الواردات بالإضافة إلى استيراد إضافي import "name1" "name2" (func (param externref)) . ثم تقوم هذه الوظيفة بإنشاء مثيل للواردات بالقيم المعطاة بواسطة imports وتقوم بإنشاء مثيل الاستيراد الإضافي مع ما هو نظريًا await . عندما يتم إنشاء الوظائف المصدرة من هذه الوحدة ، يتم حراستها بحيث أنه عندما يتم استدعاء هذا await فإنه يسير في المكدس للعثور على الحارس الأول ثم نسخ محتويات المكدس إلى وعد جديد يكون ذلك الحين عاد على الفور.

هل هذا العمل؟ أشعر أن هذا الاقتراح يمكن أن يتم فقط عن طريق تعديل JS API دون الحاجة إلى تعديل WebAssembly نفسه. بالطبع ، حتى ذلك الحين لا يزال يضيف الكثير من الوظائف المفيدة.

kripken كيف سيتم التعامل مع الوظيفة start ؟ هل سيرفض بشكل ثابت await ، أم أنه سيتفاعل بطريقة ما مع إنشاء مثيل Wasm؟

malbarbo wasm-bindgen-futures يسمح لك بتشغيل كود async في Rust. هذا يعني أنه يجب عليك كتابة برنامجك بطريقة غير متزامنة: يجب عليك وضع علامة على وظائفك على أنها async ، وتحتاج إلى استخدام .await . لكن هذا الاقتراح يسمح لك بتشغيل رمز غير متزامن دون استخدام async أو .await ، بدلاً من ذلك يبدو وكأنه استدعاء دالة متزامنة عادية.

بمعنى آخر ، لا يمكنك حاليًا استخدام واجهات برمجة التطبيقات لنظام التشغيل المتزامن (مثل std::fs ) لأن الويب يحتوي فقط على واجهات برمجة تطبيقات غير متزامنة. ولكن مع هذا الاقتراح ، يمكنك استخدام واجهات برمجة تطبيقات OS متزامنة: ستستخدم الوعود داخليًا ، لكنها ستبدو متزامنة مع Rust.

حتى إذا تم تنفيذ هذا الاقتراح ، فسيظل wasm-bindgen-futures موجودًا وسيظل مفيدًا ، لأنه يتعامل مع حالة استخدام مختلفة (تشغيل وظائف async ). والدالات async مفيدة لأنها يمكن أن تكون متوازية بسهولة.

RossTate يبدو أن اقتراحك مشابه تمامًا لما تم تناوله في "مراعاة المناهج البديلة":

هناك خيار آخر درسناه وهو وضع علامة على الواردات بطريقة ما بحيث نقول "يجب أن يتوقف هذا الاستيراد مؤقتًا إذا أعاد التعهد". فكرنا في الخيارات المختلفة لكيفية وضع علامة عليها ، سواء في JS أو على جانب wasm ، لكننا لم نجد أي شيء يبدو صحيحًا. على سبيل المثال ، إذا وضعنا علامة على الواردات من جانب JS ، فلن تعرف الوحدة النمطية wasm ما إذا كانت المكالمة إلى استيراد ستتوقف مؤقتًا أم لا حتى خطوة الارتباط ، عند وصول الواردات. أي أن مكالمات الاستيراد والتوقف المؤقت ستمزج "معًا". يبدو أن الأمر الأكثر وضوحًا هو مجرد الحصول على تعليمات جديدة لذلك ، انتظر ، والتي تكون صريحة بشأن الانتظار. من الناحية النظرية ، قد تكون هذه القدرة مفيدة خارج الويب أيضًا (انظر الملاحظات سابقًا) ، لذا فإن وجود تعليمات للجميع قد يجعل الأمور أكثر اتساقًا بشكل عام.

كيف سيتم التعامل مع وظيفة البداية؟ هل سترفض الانتظار بشكل ثابت ، أم أنها ستتفاعل بطريقة ما مع إنشاء مثيل Wasm؟

Pauan لم نغطي هذا على وجه التحديد ، لكنني أعتقد أنه لا يوجد ما يمنعنا من السماح بـ await بـ start أيضًا. في هذه الحالة ، سيظل الوعد الذي تم إرجاعه من instantiate{Streaming} يتم حله / رفضه بشكل طبيعي عندما تنتهي وظيفة البدء من التنفيذ بالكامل ، مع الاختلاف الوحيد هو أنه سينتظر الوعود await ed.

ومع ذلك ، تنطبق نفس القيود المطبقة حاليًا وفي الوقت الحالي لن تكون مفيدة جدًا للحالات التي تتطلب الوصول إلى الذاكرة المصدرة على سبيل المثال.

RReverser كيف يعمل ذلك مع new WebAssembly.Instance المتزامن (الذي يستخدم في العمال)؟

نقطة مثيرة للاهتمام Pauan حول البدء!

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

(الذي يستخدم في العمال)؟

هم نقطة جيدة. لا أعتقد أنه يجب استخدامه في العمال ، ولكن نظرًا لوجود واجهة برمجة التطبيقات هذه بالفعل ، فربما يمكنها إرجاع وعد؟ لقد رأيت هذا كنمط ناشئ شبه شائع لإرجاع عناصر من مُنشئ مكتبات مختلفة ، على الرغم من عدم التأكد مما إذا كانت فكرة جيدة القيام بذلك في واجهة برمجة تطبيقات قياسية.

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

ربما فاتني شيئًا ما ، لكن لا توجد مناقشة لما يحدث عندما يتم إيقاف تنفيذ WASM مؤقتًا بتعليمات await وتعود الوعد إلى JS ، ثم JS يدعو مرة أخرى إلى WASM دون انتظار الوعد.

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

ماذا عن الإلغاء؟ لم يتم تنفيذه في وعود JS وهذا يسبب بعض المشاكل.

تضمين التغريدة

ربما فاتني شيء ما ، ولكن لا توجد مناقشة لما يحدث عندما يتم إيقاف تنفيذ WASM مؤقتًا بتعليمات انتظار ووعد عاد إلى JS ، ثم يتصل JS مرة أخرى بـ WASM دون انتظار الوعد.

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

ربما لا يكون النص الحالي واضحًا بدرجة كافية حول ذلك. للفقرة الأولى ، نعم ، هذا مسموح به ، راجع قسم "التوضيحات": It is ok to call into the WebAssembly instance while a pause has occurred, and multiple pause/resume events can be in flight at once.

بالنسبة للفقرة الثانية ، لا - لا يمكنك الحصول على الأحداث في وقت سابق ، ولا يمكنك جعل JS تحل الوعد قبل ذلك. دعني أحاول تلخيص الأشياء بطريقة أخرى:

  • عندما يتوقف wasm مؤقتًا عن الوعد A ، فإنه يخرج مرة أخرى إلى ما يسمى به ، ويعيد الوعد الجديد B.
  • يستأنف Wasm عندما يقرر Promise A. يحدث ذلك في الوقت العادي ، مما يعني أن كل شيء طبيعي في حلقة حدث JS.
  • بعد استئناف wasm والانتهاء أيضًا من التشغيل ، عندها فقط يتم حل Promise B.

لذلك على وجه الخصوص ، يجب على Promise B أن يحل بعد Promise A. لا يمكنك الحصول على نتيجة Promise A في وقت أبكر مما يمكن لـ JS الحصول عليه.

لوضعها بطريقة أخرى: يمكن تعويض سلوك هذا الاقتراح عن طريق Asyncify + بعض JS التي تستخدم الوعود حولها.

RReverser ، لا أعتقد أنهما متماثلان ، لكن أولاً أعتقد أننا بحاجة إلى توضيح شيء ما (إذا لم يتم توضيحه بالفعل ، في هذه الحالة أنا آسف لفقدانه).

يمكن أن تكون هناك مكالمات متعددة من JS في نفس مثيل wasm على نفس الحزمة في نفس الوقت. إذا تم تنفيذ await بواسطة المثيل ، فما المكالمة التي يتم إيقافها مؤقتًا وإرجاع الوعد؟

بالنسبة للفقرة الثانية ، لا - لا يمكنك الحصول على الأحداث في وقت سابق ، ولا يمكنك جعل JS تحل الوعد قبل ذلك.

آسف أعتقد أن سؤالي لم يكن واضحًا. في الوقت الحالي ، تستخدم تطبيقات "main-loop" في C ++ emscripten_set_main_loop بحيث يتم إرجاع التحكم بين كل تشغيل لوظيفة الإطار إلى المتصفح ، ويمكن معالجة الإدخال أو الأحداث الأخرى.

مع هذا الاقتراح ، يبدو أن ما يلي يجب أن يعمل على ترجمة تطبيقات "الحلقة الرئيسية". (على الرغم من أنني لا أعرف جيدًا حلقة حدث JS)

int main() {
  while (true) {
    frame();
    processEvents();
  }
}

// polyfillable with ASYNCIFY!
void processEvents() {
  __builtin_await(EM_ASM(
    new Promise((resolve, reject) => {
      setTimeout(0, () => resolve());
    })
  ))
}

Kangz That يجب أن يعمل ، نعم (باستثناء أن لديك مشكلة صغيرة في ترتيب الوسيطات في كود setTimeout الخاص بك بالإضافة إلى أنه يمكن تبسيطه):

int main() {
  while (true) {
    frame();
    processEvents();
  }
}

// polyfillable with ASYNCIFY!
void processEvents() {
  __builtin_await(EM_ASM_WAITREF(
    return new Promise(resolve => setTimeout(resolve));
  ));
}

يمكن أن تكون هناك مكالمات متعددة من JS في نفس مثيل wasm على نفس الحزمة في نفس الوقت. إذا تم تنفيذ الانتظار بواسطة المثيل ، فما المكالمة التي يتم إيقافها مؤقتًا وإرجاع الوعد؟

الأعمق. إنها مهمة غلاف JS لتنسيق الباقي إذا كان يرغب في القيام بذلك.

Kangz آسف ، لقد أسأت فهمك قبل ذلك الحين. نعم ، كما قال RReverser ، يجب أن يعمل هذا ، وهو مثال جيد لحالة الاستخدام المقصودة هنا!

كما قلت ، إنه غير قابل للتعبئة مع Asyncify ، وهو في الواقع مكافئ للرمز نفسه مع Asyncify اليوم باستبدال __builtin_await باستدعاء emscripten_sleep(0) (الذي يفعل setTimeout(0) ) .

شكرا ، RReverser ، للتوضيح. أعتقد أنه من المفيد إعادة صياغة الوصف ليقول أن المكالمة (الأحدث) في المثيل تتوقف مؤقتًا ، بدلاً من المثيل نفسه.

في هذه الحالة ، يبدو هذا مكافئًا تقريبًا لإضافة الدالتين الأساسيتين التاليتين إلى JS: promise-on-await(f) و await-for-promise(p) . يستدعي السابق f() ولكن ، أثناء تنفيذ f() ، تم إجراء مكالمة إلى await-for-promise(p) ، بدلاً من ذلك يُرجع وعدًا جديدًا يستأنف التنفيذ بعد حل p ويتم حل نفسها بعد اكتمال ذلك التنفيذ (أو استدعاء await-for-promise مرة أخرى). إذا تم إجراء مكالمة إلى await-for-promise ضمن سياق promise-on-await s ، فحينئذٍ تُرجع المكالمة الأحدث وعدًا. إذا تم إجراء مكالمة إلى await-for-promise خارج أي promise-on-await ، فحينئذٍ يحدث شيء سيء (تمامًا كما لو أن كود start للمثيل ينفذ await ).

هل هذا منطقي؟

RossTate هذا قريب جدًا ، نعم ، ويلتقط الفكرة العامة. (ولكن كما قلت ، يكاد يكون مكافئًا فقط ، حيث لا يمكن استخدامه لتعويض هذا ، ويفتقد معالجة حدود wasm / JS المحددة.)

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

RossTate ممتع ... أحب هذا! يجعل الطبيعة غير المتزامنة للمكالمة صريحة ( promise-on-await مطلوب لأي مكالمة يحتمل أن تكون غير متزامنة) ، ولا تتطلب أي تغييرات على Wasm. من المنطقي أيضًا (بعض) إزالة Wasm من الوسط - إذا كان promise-on-await يستدعي await-for-promise مباشرة ، فإنه يُرجع Promise .

kripken هل يمكنك الدخول في مزيد من التفاصيل حول سبب اختلاف ذلك؟ لا أفهم تمامًا سبب أهمية حدود Wasm / JS هنا.

binji قصدت فقط أن مثل هذه الوظائف في JS لن تسمح لـ wasm بعمل شيء مشابه. وصفها بأنها واردات من wasm لن ينجح. ما زلنا بحاجة إلى طريقة لجعل wasm يخرج إلى الحدود وما إلى ذلك بطريقة قابلة للاستئناف ، أليس كذلك؟

kripken صحيح ، أعتقد عند هذه النقطة أن استيراد await-for-promise يجب أن يعمل مثل Wasm جوهريًا.

كان تفكيري أنه بدلاً من إضافة تعليمات await إلى wasm ، فإن هذه الوحدة ستستورد بدلاً من ذلك await-for-promise وتستدعي ذلك. وبالمثل ، بدلاً من تغيير الوظائف المصدرة ، فإن كود JS يستدعيها داخل promise-on-await . هذا يعني أن العناصر الأولية لـ JS ستتعامل مع جميع أعمال المكدس ، بما في ذلك WebAssembly stack. سيكون أيضًا أكثر مرونة ، على سبيل المثال ، إذا كنت ترغب في منح الوحدة رد اتصال JS الذي يمكنه بعد ذلك الاتصال مرة أخرى في الوحدة وإيقاف المكالمة الخارجية مؤقتًا بدلاً من الجملة الداخلية - كل هذا يتوقف على ما إذا كان رمز JS يختار لإنهاء المكالمة بـ promise-on-await أم لا. لا أعتقد أنك بحاجة إلى تغيير أي شيء إلى wasm نفسه.

سأكون مهتمًا بسماع ما يفكر فيهsyg حول بدائل JS المحتملة.

حسنًا ، آسف - لقد أخذت تعليقك RossTate على أنه "للتأكد من فهمي ، دعني أعيد صياغته على هذا النحو ، وأخبرني ما إذا كان هذا بالشكل الصحيح" ، وليس اقتراحًا ملموسًا.

بالتفكير في الأمر ، لا تريد فكرتك التوقف مؤقتًا ليس فقط إطارات JS ولكن أيضًا في wasm ، ولكن هناك أيضًا إطارات مضيف / مستعرض. (يتجنب الاقتراح الحالي ذلك من خلال العمل فقط على الحد الذي تم استدعاؤه فيه.) إليك مثال:

myList.forEach((item) => {
  .. call something which ends up pausing ..
});

إذا تم تنفيذ forEach في كود المتصفح ، فهذا يعني إيقاف إطارات المتصفح مؤقتًا. من المهم أيضًا أن التوقف في منتصف هذه الحلقة ، والاستئناف لاحقًا ، سيكون قوة جديدة يمكن لـ JS القيام بها ، وستسمح فكرتك بذلك لحلقة عادية أيضًا:

for (let i of something) {
  .. call something which ends up pausing ..
}

وكل هذا قد يكون له تفاعلات غريبة مع وظائف async JS. يبدو أن كل هذه المناقشات كبيرة يجب إجراؤها مع المتصفح وأشخاص JS.

ولكن أيضًا ، هذا يتجنب فقط إضافة await و waitref في مواصفات الصياغة الأساسية ، لكن هذه إضافات صغيرة - لأنها لا تفعل شيئًا في المواصفات الأساسية. يحتوي الاقتراح الحالي بالفعل على 99٪ من التعقيد من جانب JS. ويتبادل اقتراحك في IIUC تلك الإضافة الصغيرة لمواصفات wasm مع إضافات أكبر بكثير على جانب JS - لذلك فهو يجعل منصة الويب ككل أكثر تعقيدًا ، ولا داعي لها لأن هذا كله مخصص لـ wasm. بالإضافة إلى ذلك ، هناك فائدة فعلية من تحديد await في المواصفات الأساسية للوسائط ، والتي قد تكون مفيدة خارج الويب.

ربما فاتني شيء في اقتراحك ، أعتذر إذا كان الأمر كذلك. بشكل عام ، أنا أشعر بالفضول ما هو دافعك لمحاولة تجنب إضافة إلى المواصفات الأساسية للوسم؟

لا أعتقد أن هذه العناصر الأولية منطقية جدًا بالنسبة لـ js ، وأعتقد أن تطبيقات wasm أكثر من تلك الموجودة في المتصفحات يمكن أن تستفيد من ذلك. ما زلت أشعر بالفضول لماذا لا تفي الاستثناءات القابلة للاستئناف (التأثيرات تقريبًا) بحالة الاستخدام هذه.

كان تعليقي مزيجًا من الاثنين. على مستوى عالٍ ، أحاول معرفة ما إذا كانت هناك طريقة لإعادة صياغة الاقتراح على أنه إثراء بحت لـ JS API (وبالمثل كيف سيتفاعل المضيفون الآخرون مع وحدات wasm). يساعد التمرين على تقييم ما إذا كانت الوسم بحاجة للتغيير حقًا ويساعد في تحديد ما إذا كان الاقتراح يضيف سراً عناصر أولية جديدة إلى JS والتي قد يوافق عليها أو لا يوافق عليها أعضاء JS. بمعنى ، إذا لم يكن من الممكن القيام بذلك باستخدام await : func (param externref) (result externref) فقط ، فمن المحتمل جدًا أن هذا يضيف وظائف جديدة إلى JS.

بالنسبة إلى بساطة التغييرات التي تم إجراؤها على wasm ، لا يزال هناك العديد من الأشياء التي يجب مراعاتها مثل ما يجب فعله بشأن استدعاءات الوحدة النمطية ، وما يجب القيام به عندما تقوم الوظائف المصدرة بإرجاع قيم GC التي تحتوي على مؤشرات إلى وظائف يمكنها تنفيذ await بعد انتهاء المكالمة ، وما إلى ذلك.

بالعودة إلى التمرين ، كما أشرت إلى وجود أسباب وجيهة لالتقاط مكدس wasm فقط. يعيدني هذا إلى اقتراحي السابق ، على الرغم من تنقيحه قليلاً مع منظور جديد. أضف دالة WebAssembly.instantiateAsync(moduleBytes, imports, "name1", "name2") إلى JS API. افترض أن moduleBytes يحتوي على عدد من الواردات بالإضافة إلى استيراد إضافي import "name1" "name2" (func (param externref) (result externref)) . ثم يقوم instantiateAsync بإنشاء مثيل للواردات الأخرى moduleBytes ببساطة مع القيم المعطاة بواسطة imports وخلق مثيلاً للاستيراد الإضافي مع ما هو نظريًا await-for-promise . عندما يتم إنشاء الوظائف المصدرة من هذا المثال ، فإنها تخضع للحراسة (من الناحية النظرية بواسطة promise-on-await ) بحيث عندما يتم استدعاء await-for-promise هذا ، فإنه يمشي في المكدس للعثور على الحارس الأول ثم ينسخ محتويات المكدس في وعد جديد يتم إعادته على الفور. الآن لدينا نفس العناصر الأولية كما ذكرت أعلاه ، لكنها لم تعد من الدرجة الأولى ، وهذا النمط المقيد يضمن أنه فقط wasm stack سيتم التقاطه من أي وقت مضى. في نفس الوقت ، لا يحتاج WebAssembly إلى التغيير لدعم النمط.

أفكار؟

تضمين التغريدة

ما زلت أشعر بالفضول لماذا لا تفي الاستثناءات القابلة للاستئناف (التأثيرات تقريبًا) بحالة الاستخدام هذه.

هم خيار في هذا الفضاء ، بالتأكيد.

ما فهمته من العرض التقديمي الأخير لـ rossberg هو أنه أراد في البداية السير في هذا الطريق ، ولكن بعد ذلك غير الاتجاه للقيام بمقاربة coroutine. انظر الشريحة بعنوان "مشاكل". بعد هذه الشريحة ، يتم وصف coroutines ، وهي خيار آخر في هذه المساحة. لذلك ربما يكون سؤالك أكثر لـ rossberg الذي ربما يمكنه التوضيح؟

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

call $get_promise
await
;; use it!

هذه البساطة في wasm مفيدة لنفسها ولكنها تعني أيضًا أنه من الواضح جدًا لـ VM ما يحدث والذي قد يكون له فوائد أيضًا.

تضمين التغريدة

هذا يعني أنه إذا لم يكن من الممكن القيام بمجرد انتظار مستورد: func (param externref) (نتيجة externref) ، فمن المحتمل جدًا أن يضيف هذا وظائف جديدة إلى JS.

أنا لا أتبع هذا الاستنتاج ، آسف. لكن يبدو لي أنها ملتوية. إذا كنت تعتقد أن هذا الاقتراح يضيف وظائف جديدة إلى JS ، فلماذا لا تظهر ذلك بشكل مباشر؟ (أعتقد اعتقادًا راسخًا أن هذا لا يحدث ، لكنني أشعر بالفضول إذا وجدت أننا ارتكبنا خطأ!)

بالنسبة إلى بساطة التغييرات التي تم إجراؤها على wasm ، لا يزال هناك العديد من الأشياء التي يجب مراعاتها مثل ما يجب فعله بشأن استدعاءات الوحدة النمطية

هل توضح مواصفات الوحدة الأساسية أي شيء عن استدعاءات الوحدة النمطية؟ لا أتذكر أنه يفعل ذلك ، ولا أرى ذلك الآن بعد قراءة الأقسام ذات الصلة . لكن ربما فاتني شيء؟

في اعتقادي أن إضافات المواصفات الأساسية للوسائط ستكون في الأساس قائمة await ، على سبيل المثال ، من المفترض أن "تنتظر شيئًا ما" ، وهذا كل شيء. لهذا السبب كتبت That's it for the core wasm spec! في الاقتراح. إذا كنت مخطئًا ، يرجى توضيح المواصفات الأساسية حيث سنحتاج إلى إضافة المزيد.

دعنا نتوقع ونقول أنه في يوم من الأيام سيكون لمواصفات النواة الأساسية تعليمات جديدة لإنشاء وحدة wasm واستدعاء طريقة عليها. في هذه الحالة ، أتخيل أننا نقول await مجرد فخاخ لأن الهدف منها هو انتظار شيء ما بالخارج ، على المضيف.

يعيدني هذا إلى اقتراحي السابق ، على الرغم من المراجعة الطفيفة مع منظور جديد [فكرة جديدة]

هل هذه الفكرة ليست هي نفسها من الناحية الوظيفية مثل الفقرة الثانية في Alternative approaches considered في الاقتراح؟ يمكن القيام بشيء كهذا ، لكننا أوضحنا لماذا نعتقد أنه ليس جيدًا.

حصلت عليها kripken . لأكون واضحًا ، أعتقد أن await يحل حالات الاستخدام المعروضة بطريقة عملية للغاية وأنيقة. أنا أيضًا آمل نوعًا ما في أن نتمكن من استخدام هذا الزخم لحل حالات الاستخدام الأخرى أيضًا من خلال توسيع التصميم قليلاً.

أعتقد أن اقتراحRossTate يبدو بالفعل مشابهًا جدًا لما هو مذكور في "مراعاة المناهج البديلة". لذلك أعتقد أننا يجب أن نناقش بمزيد من التفصيل سبب رفض هذا التوجه. أعتقد أننا يمكن أن نتفق جميعًا على أن الحل الذي لا يتضمن أي تغييرات في مواصفات wasm سيكون مفضلاً ، إذا تمكنا من جعل جانب JS عمليًا. أحاول فهم الجوانب السلبية التي حددتها في هذا القسم ، ولماذا يجعلون حل JS فقط غير مقبول على الإطلاق.

أعتقد أننا يمكن أن نتفق جميعًا على أن الحل الذي لا يتضمن تغييرات في المواصفات هو الأفضل

رقم! انظر حالات استخدام غير الويب التي تمت مناقشتها هنا. بدون await في مواصفات wasm ، سينتهي بنا الأمر مع قيام كل نظام بشيء مخصص: تقوم بيئة JS ببعض عمليات الاستيراد ، بينما تنشئ أماكن أخرى واجهات برمجة تطبيقات جديدة تحمل علامة "متزامنة" ، وما إلى ذلك. تكون أقل اتساقًا ، فسيكون من الصعب نقلها من الويب إلى أماكن أخرى ، وما إلى ذلك.

لكن نعم ، يجب أن نجعل الجزء الأساسي من المواصفات الأساسية بسيطًا قدر الإمكان. أعتقد أن هذا يفعل ذلك؟ 99٪ من المنطق في جانب JS (ولكن يبدو أن RossTate يختلف ، وما زلنا نحاول معرفة ذلك - لقد طرحت أسئلة محددة في إجابتي الأخيرة والتي آمل أن تقدم الأمور).

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

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

هل توضح مواصفات الوحدة الأساسية أي شيء عن استدعاءات الوحدة النمطية؟

يمكن إنشاء مثيل لواردات مثيل من خلال عمليات تصدير مثيل أخرى. بناءً على ما أفهمه من JS API (ومبدأ تكوين wasm) ، فإن استدعاء مثل هذا الاستيراد هو من الناحية المفاهيمية استدعاء مباشر لأي وظيفة يتم تصديرها من المثيل الآخر. الشيء نفسه ينطبق على المكالمات (غير المباشرة) على القيم الوظيفية مثل funcref التي يتم تمريرها بين المثلين.

دعنا نتوقع ونقول أنه في يوم من الأيام سيكون لمواصفات النواة الأساسية تعليمات جديدة لإنشاء وحدة wasm واستدعاء طريقة عليها. في هذه الحالة ، أتخيل أننا سنقول انتظر مجرد الفخاخ لأن الهدف منها هو انتظار شيء ما في الخارج ، على المضيف.

استنادًا إلى مبدأ تكوين الوحدة الذي تمت مناقشته في الاجتماع الشخصي ، لا ينبغي أن يصطاد. يجب أن يكون الأمر كما لو كان هناك مثيل وحدة واحدة (مكونة) وتم تنفيذ await . أي أن await يحزم المكدس حتى أحدث إطار مكدس JS.

لاحظ أن هذا يشير إلى أنه إذا كان f هو قيمة دالة أحادية تم تصديرها لبعض مثيل wasm ، فإن كائن معلمات إنشاء مثيل {"some" : {"import" : f}} سيكون مختلفًا بشكل دلالي عن {"some" : {"import" : (x) => f(x)}} لأن المكالمات إلى السابق ، ستبقى داخل حزمة wasm بينما ستدخل الاستدعاءات إلى الأخير في مكدس JS ، على الرغم من أنها بالكاد. حتى الآن تعتبر كائنات معلمات إنشاء مثيل هذه مكافئة. يمكنني أن أتطرق إلى سبب فائدة ذلك من وجهة نظر ترحيل الشفرة / توافق اللغة ، لكن هذا سيكون انحرافًا في الوقت الحالي.

أليست هذه الفكرة من الناحية الوظيفية هي نفس الفقرة الثانية في النهج البديلة التي تم النظر فيها في الاقتراح؟ يمكن القيام بشيء كهذا ، لكننا أوضحنا لماذا نعتقد أنه ليس جيدًا.

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

حقيقة أن هذا الاقتراح خفيف جدًا من ناحية الوسم لأن التعليمات await تبدو متطابقة من الناحية اللغوية مع استدعاء دالة مستوردة. بالطبع ، الاتفاقيات مهمة ، كما أشرت! لكن await ليست الوظيفة الوحيدة التي ينطبق عليها هذا ؛ وينطبق الشيء نفسه على معظم الوظائف المستوردة. في حالة await ، فإن إحساسي هو أنه يمكن معالجة القلق بشأن الاصطلاح من خلال جعل الوحدات النمطية بهذه الوظيفة تحتوي على عبارة import "control" "await" (func (param externref) (result externref)) ، وأن يكون لديك بيئات تدعم هذه الوظيفة تقوم دائمًا بإنشاء مثيل لهذا الاستيراد مع رد الاتصال المناسب.

يبدو أن هذا يوفر حلاً يوفر الكثير من العمل من خلال عدم تغيير wasm مع الاستمرار في توفير إمكانية النقل عبر الأنظمة الأساسية التي تبحث عنها. لكن ما زلت أعمل على فهم الفروق الدقيقة في الاقتراح ، وقد فاتني بالفعل مجموعة حتى الآن!

حقيقة أن هذا الاقتراح خفيف جدًا من جانب wasm هو أن التعليمات المنتظرة تبدو مطابقة لغويًا لاستدعاء وظيفة مستوردة.

FWIW هذا هو المكان الذي بدأ فيه هذا الاقتراح في الأصل ، ولكن استخدام عناصر جوهرية من هذا القبيل يبدو أكثر غموضًا بالنسبة للآلات الافتراضية ومثبطًا بشكل عام (أعتقد أن binji اقترح الابتعاد عنه في المناقشات الأصلية).

على سبيل المثال ، باتباع حجتك ، يمكن أيضًا تنفيذ شيء مثل memory.grow أو atomic.wait كـ import "control" "memory_grow" أو import "control" "atomic_wait" في المقابل ، لكنهم ليسوا كما يفعلون لا توفر نفس المستوى من فرص التحليل المتداخل والثابت (على كل من الجهاز الظاهري وجانب الأدوات) كإرشادات حقيقية.

يمكنك أن تجادل بأن memory.grow كإرشاد لا يزال مفيدًا للحالات التي لا يتم فيها تصدير الذاكرة ، ولكن يمكن بالتأكيد تنفيذ atomic.wait خارج النواة. في الواقع ، إنه مشابه جدًا لـ await ، باستثناء المستوى الذي يحدث عنده الإيقاف المؤقت / الاستئناف ولأن await كدالة تتطلب سحرًا أكثر من atomic.wait نظرًا لأنه يجب أن يكون قادرًا على التفاعل مع مكدس VM وليس فقط حظر الخيط الحالي حتى تتغير القيمة.

tlively

يجب إعادة صياغة عبارة "انتظر شيئًا ما" في المصطلحات الدقيقة المستخدمة بالفعل في المواصفات.

قطعا نعم. يمكنني اقتراح نص أكثر تحديدًا الآن إذا كان ذلك مفيدًا:

When an await instruction is executed on a waitref, the host environment is requested to do some work. Typically there would be a natural meaning to what that work is based on what a waitref is on a specific host (in particular, waiting for some form of host event), but from the wasm module's point of view, the semantics of an await are similar to a call to an imported host function, that is: we don't know exactly what the host will do, but at least expect to give it certain types and receive certain results; after the instruction executes, global state (the store) may change; and an exception may be thrown.

The behavior of an await from the host's perspective may be very different, however, from a call to an imported host function, and might involve something like pausing and resuming the wasm module. It is for this reason that this instruction is defined. For the instruction to be usable on a particlar host, the host would need to define the proper behavior.

راجع للشغل ، مقارنة أخرى جاءت لي أثناء كتابة هذا هي تلميحات المحاذاة على الأحمال والمخازن. يدعم Wasm الأحمال والمخازن غير المحاذاة ، لذلك لا يمكن أن تؤدي التلميحات إلى سلوك مختلف يمكن ملاحظته بواسطة وحدة wasm (حتى لو كان التلميح خاطئًا) ، ولكن بالنسبة للمضيف ، يقترحون تطبيقًا مختلفًا تمامًا على منصات معينة (والتي قد تكون أكثر كفاءة). هذا مثال على تعليمات مختلفة بدون دلالات مختلفة يمكن ملاحظتها داخليًا ، كما تقول المواصفات: The alignment in load and store instructions does not affect the semantics .

تضمين التغريدة

استنادًا إلى مبدأ تكوين الوحدة الذي تمت مناقشته في الاجتماع الشخصي ، لا ينبغي أن يصطاد. يجب أن يكون الأمر كما لو كان هناك مثيل وحدة (مكون) واحد فقط ويتم تنفيذه في الانتظار. وهذا يعني أن انتظار حزم المكدس يصل إلى أحدث إطار مكدس JS.

يبدو جيدًا ، ومن الجيد معرفة ذلك ، شكرًا ، لقد فاتني هذا الجزء.

أعتقد أن هذا يفسر لي جزءًا من سوء فهمنا. الوحدة النمطية => استدعاءات الوحدة النمطية ليست في أجهزة الصراف الآلي للمواصفات ، والتي كانت وجهة نظري في وقت سابق. ولكن يبدو أنك تفكر في المستقبل في المستقبل الذي قد يكونون فيه. على أي حال ، لا يبدو هذا كمشكلة هنا ، لأن التركيب يحدد بالضبط كيف يجب أن يتصرف الانتظار في هذه الحالة (وهو ليس ما اقترحته سابقًا! ولكنه أكثر منطقية).

هل توضح مواصفات الوحدة الأساسية أي شيء عن استدعاءات الوحدة النمطية؟ لا أتذكر أنه يفعل ذلك ، ولا أرى ذلك الآن بعد قراءة الأقسام ذات الصلة. لكن ربما فاتني شيء؟

نعم ، تميز المواصفات الأساسية للوظائف بين الوظائف التي تم استيرادها من الوحدات النمطية الأخرى ووظائف المضيف (الفقرة 6.2.4). لا تعتمد دلالات استدعاء الوظيفة (الفقرة 4.4.7) على الوحدة النمطية التي حددت الوظيفة ، وعلى وجه الخصوص ، يتم تحديد استدعاءات الوظائف عبر الوحدة النمطية حاليًا لتتصرف بشكل مماثل لاستدعاءات دالة نفس الوحدة.

إذا تم تعريف await s الموجودة أسفل الاستدعاءات عبر الوحدات على أنها اعتراض ، فسيتطلب ذلك تحديد اجتياز مكدس الاستدعاءات لفحص ما إذا كان هناك استدعاء عبر الوحدة النمطية قبل آخر إطار وهمي تم إنشاؤه بواسطة استدعاء من المضيف (§ 4.5.5). سيكون هذا تعقيدًا مؤسفًا في المواصفات. لكنني أتفق مع روس في أن وجود مصيدة استدعاءات متعددة الوحدات سيكون انتهاكًا للتركيب ، لذلك أفضل الدلالات حيث يتم تجميد المكدس بالكامل مرة أخرى إلى الاستدعاء الأخير من المضيف. إن أبسط طريقة لتحديد ذلك هي جعل await مشابهًا لاستدعاء وظيفة المضيف (§ 4.4.7.3) ، كما تقول ، kripken. لكن استدعاءات وظيفة المضيف غير محددة تمامًا ، لذلك قد يكون الاسم الأفضل للتعليمات من وجهة نظر المواصفات الأساسية هو undefined . وفي هذه المرحلة ، بدأت بالفعل في تفضيل الاستيراد الجوهري الذي سيتم توفيره دائمًا بواسطة منصة الويب (و WASI لقابلية النقل) لأن المواصفات الأساسية ، بمفردها ، لا تستفيد من الحصول على تعليمات IMO undefined .

من الناحية الدلالية ، استدعاء البيئة المضيفة التي تُرجع waitref بالإضافة إلى await هي مجرد مكالمة حظر ، أليس كذلك؟

ما القيمة التي يقدمها هذا إلى عمليات التضمين التي لا تعتمد على الويب والتي لا تحتوي على بيئة غير متزامنة مثل المتصفح ويمكنها في الأصل دعم حظر المكالمات؟

RReverser ، أرى النقطة التي تثيرها حول الجوهرات. هناك نداء حكم متضمن في تحديد متى يجب تحديد العملية من خلال وظائف غير مفسرة مقابل التعليمات. أعتقد أن أحد العوامل في هذا الحكم هو النظر في كيفية تفاعله مع التعليمات الأخرى. يؤثر memory.grow على سلوك تعليمات الذاكرة الأخرى. لم تتح لي الفرصة للاطلاع على اقتراح الخيوط ، لكني أتخيل أن atomic.wait يؤثر أو يتأثر بسلوك إرشادات المزامنة الأخرى. يجب بعد ذلك تحديث المواصفات لإضفاء الطابع الرسمي على هذه التفاعلات.

ولكن مع وجود await بمفرده ، لا توجد أي تفاعلات مع التعليمات الأخرى. التفاعلات الوحيدة هي مع المضيف ، ولهذا السبب حدسي هو أن هذا الاقتراح يجب أن يتم من خلال وظائف مضيف مستوردة.

أعتقد أن الاختلاف الكبير بين atomic.wait وهذا المقترح await هو أنه لا يمكن إعادة إدخال الوحدة باستخدام atomic.wait . تم تعليق الوكيل بالكامل.

@ kripken :

ما فهمته من العرض التقديمي الأخير لـ rossberg هو أنه أراد في البداية السير في هذا الطريق ، ولكن بعد ذلك غير الاتجاه للقيام بمقاربة coroutine. انظر الشريحة بعنوان "مشاكل". بعد هذه الشريحة ، يتم وصف coroutines ، وهي خيار آخر في هذه المساحة. لذلك ربما يكون سؤالك أكثر لـ rossberg الذي ربما يمكنه التوضيح؟

نعم ، لذلك يمكن اعتبار عامل coroutine-ish بمثابة تعميم لتصميم الاستثناءات السابقة القابلة للاستئناف. لا يزال لديه نفس فكرة الأحداث / الاستثناءات القابلة للاستئناف ، ولكن التعليمات try تتحلل إلى بدائل أصغر - مما يجعل الدلالات أبسط ونموذج التكلفة أكثر وضوحًا. كما أنها أكثر تعبيرًا إلى حد ما.

لا يزال القصد هو أن هذا يمكن أن يعبر عن جميع تجريدات التحكم ذات الصلة ، وعدم التزامن هو أحد حالات الاستخدام المحفزة. للتفاعل مع JS غير المتزامن ، يمكن أن توفر واجهة برمجة تطبيقات JS حدثًا محددًا مسبقًا await (يحمل وعد JS كمرجع خارجي) يمكن لوحدة Wasm استيراده و throw للتعليق. بالطبع ، هناك الكثير من التفاصيل التي يجب توضيحها ، ولكن من حيث المبدأ يجب أن يكون ذلك ممكنًا.

بالنسبة للاقتراح الحالي ، ما زلت أحاول الالتفاف حوله. :)

على وجه الخصوص ، يبدو أنه يسمح باستخدام await في أي دالة Wasm قديمة ، فهل أقرأ ذلك بشكل صحيح؟ إذا كان الأمر كذلك ، فهذا يختلف تمامًا عن JS ، الذي يسمح بدوال غير متزامنة await فقط. وهذا قيد مركزي للغاية ، لأنه يمكّن المحركات من تجميع await عن طريق التحويل المحلي_ لوظيفة واحدة (غير متزامنة)!

بدون هذا القيد ، ستحتاج المحركات إما إلى إجراء تحويل برنامج عالمي (كما يفترض أن Asyncify يفعل) ، حيث من المحتمل أن تصبح كل مكالمة أكثر تكلفة (لا يمكنك بشكل عام معرفة ما إذا كانت هناك مكالمة ما قد تصل إلى الانتظار). أو ، بالمقابل ، يجب أن تكون المحركات قادرة على إنشاء مجموعات متعددة والتبديل بينها!

الآن ، هذه هي بالضبط الميزة التي تحاول فكرة معالجات coroutine / التأثير إدخالها في Wasm. لكن من الواضح أنها إضافة غير تافهة إلى حد كبير إلى النظام الأساسي ونموذج تنفيذه ، وهو تعقيد كان JS حريصًا جدًا على تجنبه بسبب تجريداته الرقابية (مثل غير المتزامن والمولدات).

تضمين التغريدة

على وجه الخصوص ، يبدو أنه يسمح بالانتظار في أي وظيفة Wasm قديمة ، هل أقرأ ذلك بشكل صحيح؟ إذا كان الأمر كذلك ، فهذا يختلف تمامًا عن JS ، والذي يسمح بالانتظار فقط في وظائف غير متزامنة.

نعم ، النموذج هنا مختلف تمامًا. JS في انتظار الوظيفة ، في حين أن هذا الاقتراح ينتظر نسخة كاملة من wasm (لأن الهدف هو حل عدم تطابق المزامنة / غير المتزامن بين JS و wasm ، والذي يقع بين JS و wasm). كما تنتظر JS للكود المكتوب بخط اليد ، بينما هذا هو لتمكين نقل الكود المترجم.

وهذا قيد مركزي للغاية ، لأنه يمكّن المحركات من تجميع الانتظار عن طريق التحويل المحلي لوظيفة واحدة (غير متزامنة)! بدون هذا القيد ، ستحتاج المحركات إما إلى إجراء تحويل عالمي للبرنامج (كما يفترض أن Asyncify يفعل) ، حيث من المحتمل أن تصبح كل مكالمة أكثر تكلفة (لا يمكنك بشكل عام معرفة ما إذا كانت بعض المكالمات قد تصل إلى الانتظار). أو ، بالمقابل ، يجب أن تكون المحركات قادرة على إنشاء مجموعات متعددة والتبديل بينها!

بالتأكيد لا يقصد هنا تحويل برنامج عالمي! آسف إذا لم يكن ذلك واضحًا.

كما هو مذكور في الاقتراح ، يعد التبديل بين الحزم أحد خيارات التنفيذ الممكنة ، لكن لاحظ أنه ليس مثل تبديل مكدس نمط coroutine:

  • فقط مثيل wasm بأكمله يمكن أن يتوقف مؤقتًا. هذا ليس للتبديل المكدس داخل الوحدة. (على وجه الخصوص ، هذا هو السبب في أن هذا الاقتراح لا يمكن أن يحتوي على إضافات لمواصفات الصيغ الأساسية ويكون بالكامل على جانب الوسم JS ؛ حتى الآن بعض الناس يفضلون ذلك ، وأعتقد أن كلتا الطريقتين يمكن أن تنجح.)
  • تعلن Coroutines مداخن صراحة ، لا تنتظر.
  • انتظار المكدسات يمكن استئنافه مرة واحدة فقط ، ولا يوجد تفرعات / إعادة أكثر من مرة (لست متأكدًا مما إذا كان لديك ذلك في اقتراحك أم لا؟).
  • نموذج الأداء مختلف جدًا هنا. انتظار الوعد في JS ، والذي يحتوي على الحد الأدنى من النفقات العامة والكمون بالفعل. لذلك لا بأس إذا كان للتطبيق بعض النفقات العامة عندما نتوقف مؤقتًا بالفعل ، ونحن أقل اهتمامًا من coroutines على الأرجح.

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

آسف على المنشور الطويل ، وبعض التكرار من نص الاقتراح نفسه ، لكن آمل أن يساعد هذا في توضيح بعض النقاط التي أشرت إليها؟

أعتقد أن هناك الكثير لمناقشته هنا فيما يتعلق بالتنفيذ. حتى الآن تعليق acfoltzer حول لوسيت مشجع!

فقط لتوضيح بعض الصياغة في تعليقkripken الأخير ، لم يتوقف مثيل wasm بأكمله. إنها فقط أحدث مكالمة من إطار مضيف إلى wasm على المكدس التي تم إيقافها مؤقتًا ، ثم يتم إرجاع إطار المضيف بدلاً من ذلك بوعد مطابق (أو التناظرية المناسبة للمضيف). انظر هنا للتوضيح السابق ذي الصلة.

حسنًا ، لا أرى كيف يحدث هذا فرقًا. عندما تنتظر في مكان ما بعمق داخل Wasm ، ستحتاج إلى التقاط كل مكدس المكالمات من إدخال المضيف على الأقل إلى تلك النقطة. ويمكنك الاحتفاظ بهذا التعليق (أي شريحة المكدس) على قيد الحياة طالما أردت ، أثناء إجراء مكالمات أخرى من الأعلى أو إنشاء المزيد من عمليات التعليق. ويمكنك الاستئناف من مكان آخر (على ما أظن؟). ألا يتطلب ذلك كل آلية التنفيذ للاستمرارية المحددة؟ فقط أن الموجه يتم تعيينه عند إدخال Wasm بدلاً من بناء منفصل.

تضمين التغريدة

قد يكون هذا صحيحًا في بعض الأجهزة الافتراضية ، نعم. إذا كان الانتظار وانتهى الأمر بكوروتينات إلى الحاجة إلى نفس عمل VM بالضبط ، فلا حاجة إلى عمل إضافي على الأقل. في هذه الحالة ، ستكون فائدة الاقتراح المنتظر هي تكامل JS الملائم.

أعتقد أنه يمكنك الحصول على تكامل JS ملائم بدون تحويل البرنامج بالكامل إذا لم تسمح بإعادة إدخال الوحدة.

أعتقد أنه يمكنك الحصول على تكامل JS ملائم بدون تحويل البرنامج بالكامل إذا لم تسمح بإعادة إدخال الوحدة.

تبدو هذه طريقة أسهل لإنجازها ، لكن ذلك يتطلب حظر أي وحدة تمت زيارتها في مكدس المكالمات (أو كخطوة أولى ، جميع وحدات WebAssembly).

تبدو هذه طريقة أسهل لإنجازها ، لكن ذلك يتطلب حظر أي وحدة تمت زيارتها في مكدس المكالمات (أو كخطوة أولى ، جميع وحدات WebAssembly).

صحيح ، تمامًا مثل atomic.wait .

تضمين التغريدة

أعتقد أنه يمكنك الحصول على تكامل JS ملائم بدون تحويل البرنامج بالكامل إذا لم تسمح بإعادة إدخال الوحدة.

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

لذا ، إذا كان التبسيط الخاص بك سيساعد أجهزة VM ، فمن المؤكد أنه يستحق التفكير!

(لاحظ أنه كما تمت مناقشته سابقًا ، لا أعتقد أننا بحاجة إلى تحويل كامل للبرنامج هنا مع أي من الخيارات التي تتم مناقشتها. فأنت بحاجة فقط إلى ذلك إذا كنت في موقف سيئ ، حيث يوجد Asyncify ، حيث يكون كل ما يمكنك القيام به في مستوى سلسلة الأدوات. للانتظار ، في أسوأ الحالات كما تمت مناقشته مع rossberg ، يمكنك أن تفعل ما سيفعله اقتراح coroutines داخليًا. ولكن من المحتمل أن تكون فكرتك مثيرة جدًا إذا كانت تجعل الأمور أبسط من ذلك!)

من ناحية أخرى ، يمكن أن تكون إعادة الدخول مفيدة ، على سبيل المثال ، قد يقوم محرك اللعبة بتنزيل ملف ولا يريد أن يتم إيقاف واجهة المستخدم مؤقتًا تمامًا أثناء القيام بذلك (يسمح Asyncify بذلك اليوم).

لست متأكدًا من أن هذه ميزة صوتية. يبدو لي أن هذا سيقدم _ التزامن غير متوقع_ في التطبيق بالرغم من ذلك. سيستخدم التطبيق الأصلي الذي يقوم بتحميل الأصول أثناء العرض خيطين داخليين ، وسيتم تعيين كل مؤشر ترابط إلى WebWorker + SharedArrayBuffer. إذا كان التطبيق يستخدم مؤشرات الترابط ، فيمكنه أيضًا استخدام أساسيات الويب المتزامنة من WebWorkers (كما هو مسموح به ، على الأقل في بعض الحالات). وإلا فمن الممكن دائمًا تعيين عمليات غير متزامنة في مؤشر الترابط الرئيسي لحظر العمليات في عامل باستخدام Atomics.wait (على سبيل المثال).

أتساءل عما إذا لم يتم حل حالة الاستخدام بالكامل بالفعل عن طريق تعدد مؤشرات الترابط بشكل عام. باستخدام العناصر الأولية للحظر في العامل ، يتم الاحتفاظ بالمكدس بالكامل (JS / Wasm / المتصفح الأصلي) والذي يبدو أنه أبسط وأكثر قوة.

باستخدام العناصر الأولية للحظر في العامل ، يتم الاحتفاظ بالمكدس بالكامل (JS / Wasm / المتصفح الأصلي) والذي يبدو أنه أبسط وأكثر قوة.

هذا في الواقع تطبيق بديل آخر لغلاف Asyncify JS المستقل الذي قمت بتجربته ، ولكن في حين أنه يحل مشكلة حجم الكود ، كان الأداء الإضافي أعلى بكثير من Asyncify الحالي الذي يستخدم تحويل Wasm.

@ alexp-sssup

يبدو لي أن هذا من شأنه أن يقدم تزامنًا غير متوقع في التطبيق بالرغم من ذلك.

بالتأكيد ، نعم - يجب القيام بذلك بحذر شديد ، ويمكن أن يكسر الأشياء. لدينا تجربة مختلطة مع هذا باستخدام Asyncify ، الجيد والسيئ (على سبيل المثال حالة استخدام صالحة: يتم تنزيل ملف في JS ، واستدعاء JS إلى wasm إلى malloc بعض المساحة لنسخه فيها ، قبل الاستئناف). ولكن على أي حال ، فإن إعادة الدخول ليست جزءًا مهمًا من هذا الاقتراح في كلتا الحالتين.

للإضافة إلى ما قاله RReverser ، هناك مشكلة أخرى تتعلق بالخيوط وهي أن دعمها ليس عالميًا ولن يكون كذلك . لكن الانتظار قد يكون في كل مكان.

في اللغات الأخرى حيث تم إدخال عدم التزامن / انتظار ، تعد إعادة الدخول أمرًا أساسيًا تمامًا. إنه نوع من بيت القصيد هو أن الأحداث الأخرى يمكن أن تحدث بينما يكون المرء (أ) ينتظر. يبدو لي أن إعادة الدخول مهمة جدًا.

علاوة على ذلك ، أليس صحيحًا أنه عندما تقوم وحدة ما بإجراء أي استدعاء لوظيفة خارجية ، يجب أن تفترض أنه يمكن إعادة إدخالها عبر أي من صادراتها (في المثال أعلاه ، حتى بدون انتظار ، أي استدعاء أو خارجي الوظيفة مجانية (لا يقصد التورية) لاستدعاء malloc).

يمكن للتطبيق إنشاء مثيلات متعددة من نفس الوحدة النمطية لذلك (جميعها تستورد نفس الذاكرة ، أو غلوبالس قابلة للتغيير ، وما إلى ذلك؟) ، لذا فإن إعادة الدخول ستكون بمثابة استدعاء لمثيل آخر

فقط من أجل ذكريات الوحدة المشتركة. يجب إعادة إنشاء الذكريات الأخرى ، وهو أمر مهم لتجنب إجراء عملية واحدة على عمليات أخرى أثناء التغييرات في الرحلة.

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

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

كما ذكرنا أعلاه ، هذا شيء لعبنا به بالفعل ، ولكن تم تجاهله لأنه يجلب أداءً أسوأ من الحل الحالي ، ولا يتم دعمه عالميًا ، بالإضافة إلى أنه يجعل من الصعب جدًا مشاركة WebAssembly.Global أو WebAssembly.Table مع الخيط الرئيسي دون اختراقات إضافية ، مما يجعله اختيارًا سيئًا لتعبئة شفافة.

الحل الحالي الذي يعيد كتابة وحدة Wasm لا يعاني من هذه المشكلات ، ولكنه بدلاً من ذلك له تكلفة كبيرة في حجم الملف.

على هذا النحو ، لا يعد أيٌّ من هذين الخيارين رائعًا لتطبيقات العالم الحقيقي الكبيرة ، مما يحفزنا على النظر في الدعم المحلي للتكامل غير المتزامن كما هو موضح هنا.

أداء أسوأ

هل لديك نوع من المعايير؟

نعم ، يمكنني مشاركتها عندما أعود إلى العمل يوم الثلاثاء (أو ، على الأرجح ، الأربعاء) ، أو من السهل إلى حد ما رفع صوت واحد يستدعي فقط وظيفة JS غير المتزامنة الفارغة بنفسك.

شكرا. يمكنني إنشاء علامة مصغرة ، لكنها لن تكون مفيدة بشكل رهيب.

أوه نعم ، ملكي هو معيار صغير أيضًا لأننا كنا مهتمين بحتة بمقارنة النفقات العامة.

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

أعتقد أن التركيز على سرعة النهج القائم على الذرة قد يكون مصدر إلهاء. كما ذكرنا سابقًا ، لا تعمل atomics ولن تعمل في كل مكان (بسبب COOP / COEP) وأيضًا يمكن للعامل فقط استخدام نهج atomics لأن الخيط الرئيسي لا يمكن حظره. إنها فكرة رائعة ، ولكن من أجل حل شامل ، نحتاج إلى شيء مثل انتظار.

أنا لا أقترحه كحل طويل الأمد. أقترح استخدام polyfill الذي يستخدمه لمعرفة ما إذا كان الحل غير العائد سيعمل مع الأشخاص.

taralx أوه ، حسنًا ، الآن أرى ، شكرًا.

taralx :

أعتقد أنه يمكنك الحصول على تكامل JS ملائم بدون تحويل البرنامج بالكامل إذا لم تسمح بإعادة إدخال الوحدة.

سيكون ذلك سيئا. هذا يعني أن دمج وحدات متعددة يمكن أن يكسر سلوكهم. سيكون هذا نقيضًا للنمطية.

كمبدأ عام للتصميم ، يجب ألا يعتمد السلوك التشغيلي أبدًا على حدود الوحدة (بخلاف تحديد النطاق البسيط). الوحدات النمطية هي مجرد آلية تجميع وتحديد النطاق في Wasm ، وتريد الحفاظ على القدرة على إعادة تجميع العناصر (ربط / دمج / تقسيم الوحدات النمطية) دون تغيير سلوك البرنامج.

rossberg : هذا قابل للتعميم على أنه منع الوصول إلى أي وحدة Wasm ، على النحو المقترح سابقًا. ولكن بعد ذلك من المحتمل أن يكون مقيدًا للغاية.

سيكون ذلك سيئا. هذا يعني أن دمج وحدات متعددة يمكن أن يكسر سلوكهم. سيكون هذا نقيضًا للنمطية.

كانت هذه وجهة نظري مع وسيطة polyfilling - atomic.wait لا يكسر النمطية ، لذلك لا ينبغي أن يحدث هذا أيضًا.

taralx ، atomic.wait يشير إلى موقع معين في ذاكرة معينة. ما هي الذاكرة والموقع الذي قد يستخدمه حظر await ، وكيف يمكن التحكم في الوحدات النمطية التي تشترك في تلك الذاكرة؟

rossberg هل يمكنك توضيح السيناريو الذي تعتقد أن هذا كسر؟ أظن أن لدينا أفكارًا مختلفة حول كيفية عمل الإصدار غير العائد.

taralx ، ضع في اعتبارك تحميل وحدتين A و B ، كل منهما توفر بعض وظائف التصدير ، على سبيل المثال A.f و B.g . كلاهما قد يؤديان await عند استدعائهما. يتم تمرير جزأين من كود العميل لإحدى هذه الوظائف ، على التوالي ، ويستدعي كل منهما بشكل مستقل. لا يتدخلون ولا يمنعون بعضهم البعض. ثم يقوم شخص ما بدمج A و B أو إعادة معاملتهما في C ، دون تغيير أي شيء بخصوص الكود. فجأة ، يمكن أن يبدأ كلا الجزأين من كود العميل في حظر بعضهما البعض بشكل غير متوقع. عمل مخيف عن بعد من خلال حالة مشتركة مخفية.

منطقي. لكن السماح بإعادة الدخول يتزامن مع المخاطر في الوحدات التي لا تتوقعها ، لذا فهو عمل مخيف على مسافة في كلتا الحالتين.

لكن الوحدات النمطية قابلة لإعادة الدخول بالفعل ، أليس كذلك؟ عندما تقوم إحدى الوحدات بإجراء مكالمة استيراد ، يمكن للكود الخارجي إعادة إدخال الوحدة النمطية والتي يمكن أن تغير الحالة العامة قبل العودة. لا أستطيع أن أرى كيف أن إعادة الدخول أثناء فترة الانتظار المقترحة أكثر مخيفة أو متزامنة من استدعاء وظيفة مستوردة. ربما أفتقد شيئا؟

(معدل)

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

عند تشغيل الكود ، واستدعاء وظيفة ، هناك احتمالان: يعرف أن الوظيفة لن تستدعي أشياء عشوائية ، أو قد تستدعي الوظيفة أشياء عشوائية. في الحالة الأخيرة ، يكون الدخول مرة أخرى ممكنًا دائمًا. تنطبق نفس القواعد على await .

(حررت تعليقي أعلاه)

شكرا للجميع على المناقشة حتى الآن!

للتلخيص ، يبدو أن هناك اهتمامًا عامًا هنا ، ولكن هناك أسئلة مفتوحة كبيرة مثل ما إذا كان يجب أن يكون هذا بنسبة 100٪ على جانب JS أو 99٪ فقط - يبدو أن الأول قد يزيل المخاوف الرئيسية لدى بعض الأشخاص ، وهذا من شأنه سيكون جيدًا بالنسبة لحالة الويب ، لذلك ربما يكون ذلك جيدًا. سؤال كبير آخر مفتوح وهو ما مدى جدوى ذلك في الأجهزة الافتراضية التي نحتاج إلى مزيد من المعلومات عنها.

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

فقط لمعلوماتك
سنقوم بتجميع اقتراح تبديل مكدس كامل في مماثل
الإطار الزمني. أشعر أن هذا قد يجعل متغير حالتك الخاصة موضع نقاش -
ما رأيك؟
فرانسيس

في الخميس ، 28 مايو ، 2020 الساعة 3:51 مساءً ، كتب ألون زاكاي [email protected] :

شكرا للجميع على المناقشة حتى الآن!

للتلخيص ، يبدو أن هناك اهتمامًا عامًا هنا ، ولكن هناك
أسئلة مفتوحة كبيرة مثل ما إذا كان يجب أن يكون هذا بنسبة 100٪ من جانب JS أم فقط
99٪ - يبدو أن السابق قد يزيل المخاوف الرئيسية لدى بعض الناس
لديك ، وسيكون ذلك جيدًا بالنسبة لحالة الويب ، لذلك ربما يكون ذلك جيدًا.
سؤال كبير آخر مفتوح وهو ما مدى جدوى ذلك في الأجهزة الافتراضية التي
نحتاج إلى مزيد من المعلومات حول.

سأقترح بند جدول الأعمال لاجتماع المجموعة التالي خلال أسبوعين للمناقشة
هذا الاقتراح والنظر فيه للمرحلة 1 ، مما يعني فتح الريبو
ومناقشة الأسئلة المفتوحة في قضايا منفصلة بمزيد من التفصيل هناك.
(أعتقد أن هذه هي العملية الصحيحة ، ولكن من فضلك صححني إذا كنت مخطئًا).

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/WebAssembly/design/issues/1345#issuecomment-635649331 ،
أو إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/AAQAXUCLZ4CJVQYEUBK23BLRT3TFLANCNFSM4NEJW2PQ
.

>

فرانسيس مكابي
SWE

تضمين التغريدة

يجب أن نناقش ذلك بالتأكيد.

بشكل عام ، ما لم يركز اقتراحك على جانب JS ، أعتقد أنه لن يجعل هذا الأمر موضع نقاش (وهو 99٪ -100٪ من جانب JS).

الآن بعد انتهاء المناقشة حول تفاصيل التنفيذ ، أود أن أثير قلقًا على مستوى أعلى كنت قد أعربت عنه سابقًا ولكني تخلت عن ذلك من أجل إجراء مناقشة واحدة في كل مرة.

يتكون البرنامج من العديد من المكونات. من منظور هندسة البرمجيات ، من المهم ألا يؤدي تقسيم المكونات إلى أجزاء أو دمج المكونات معًا إلى تغيير سلوك البرنامج بشكل كبير. هذا هو السبب الكامن وراء مبدأ تكوين الوحدة الذي تمت مناقشته في آخر اجتماع شخصي لـ CG ، وهو ضمني في تصميم العديد من اللغات.

في حالة برامج الويب ، الآن مع WebAssembly ، قد تتم كتابة هذه المكونات المختلفة بلغات مختلفة: JS أو wasm. في الواقع ، يمكن أيضًا كتابة العديد من المكونات بأي من اللغتين ؛ سأشير إلى هذه المكونات "المتناقضة". في الوقت الحالي ، تتم كتابة معظم المكونات المتناقضة في JS ، لكنني أتخيل أننا جميعًا نأمل في إعادة كتابة المزيد والمزيد منها في wasm. لتسهيل "ترحيل الكود" ، يجب أن نحاول التأكد من أن إعادة كتابة مكون بهذه الطريقة لا يغير كيفية تفاعله مع البيئة. كمثال على لعبة ، ما إذا كان مكون برنامج "تطبيق" معين (f, x) => f(x) مكتوبًا بلغة JS أو في wasm لا يجب أن يؤثر على سلوك البرنامج ككل. هذا هو مبدأ الهجرة الشفوية.

لسوء الحظ ، يبدو أن جميع متغيرات هذا الاقتراح تنتهك إما برنامج تكوين الوحدة النمطية أو مبدأ ترحيل الكود. يتم انتهاك السابق عندما يلتقط await المكدس إلى حيث تم إدخال وحدة wasm الحالية مؤخرًا ، لأن هذه الحدود تتغير عندما يتم تقسيم الوحدات النمطية أو دمجها معًا. يتم انتهاك الأخير عندما يلتقط await المكدس حتى المكان الذي تم إدخال wasm فيه مؤخرًا ، لأن هذه الحدود تتغير عندما يتم ترحيل الرمز من JS إلى wasm (بحيث يتم ترحيل شيء بسيط مثل (f, x) => f(x) من يمكن لـ JS to wasm تغيير سلوك البرنامج بشكل كبير).

لا أعتقد أن هذه الانتهاكات ناتجة عن خيارات تصميم سيئة لهذا الاقتراح. بدلاً من ذلك ، يبدو أن المشكلة تكمن في أن هذا الاقتراح يحاول تجنب جعل JS بشكل غير مباشر أكثر قوة ، وهذا الهدف هو إجباره على فرض حدود مصطنعة تنتهك هذه المبادئ. أتفهم تمامًا هذا الهدف ، لكنني أظن أن هذه المشكلة ستظهر أكثر فأكثر: إضافة وظائف إلى WebAssembly بطريقة تحترم هذه المبادئ غالبًا ما تتطلب إضافة وظائف بشكل غير مباشر إلى JS نظرًا لأن JS هي لغة التضمين. أفضّل معالجة هذه المشكلة بشكل مباشر (والتي ليس لدي أي فكرة حقًا عن كيفية حلها). إذا لم يكن الأمر كذلك ، فسيكون تفضيلي الثانوي هو إجراء هذا التغيير فقط في JS API ، لأن JS هو العامل المحدد هنا ، بدلاً من إضافة تعليمات إلى WebAssembly التي ليس لها تفسير لها.

لا أعتقد أن هذه الانتهاكات ناتجة عن خيارات تصميم سيئة لهذا الاقتراح. بدلاً من ذلك ، يبدو أن المشكلة هي أن هذا الاقتراح يحاول تجنب جعل JS أكثر قوة بشكل غير مباشر

هذا مهم ، لكنه ليس السبب الرئيسي للتصميم هنا.

السبب الرئيسي لهذا التصميم هو أنه بينما أتفق تمامًا على أن مبدأ التكوين منطقيًا للوسم ، فإن المشكلة الأساسية التي نواجهها على الويب هي أنه في الواقع ، JS و wasm ليسا متكافئين في الممارسة. لقد كتبنا بخط اليد JS غير المتزامن والمنقول الذي هو متزامن. بمعنى آخر ، الحد الفاصل بينهما هو في الواقع المشكلة الدقيقة التي نحاول معالجتها. بشكل عام ، لست متأكدًا من أنني أوافق على ضرورة تطبيق مبدأ التكوين على wasm و JS (ولكن ربما ينبغي أن يكون نقاشًا مثيرًا للاهتمام).

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

لدى Await سلوك يمكن ملاحظته أبسط بكثير من coroutines العامة أو تبديل المكدس ، لكن الأشخاص VM الذين تحدثت معهم يتفقون مع rossberg على أن عمل VM في النهاية من المحتمل أن يكون مشابهًا لكليهما. ويعتقد بعض الأشخاص على الأقل من VM أننا سنحصل على coroutines أو تبديل المكدس بأي طريقة ، وأنه يمكننا دعم حالات استخدام الانتظار باستخدام ذلك. سيعني ذلك إنشاء coroutine / مكدس جديد على كل مكالمة في wasm (على عكس هذا الاقتراح) ، ولكن على الأقل يعتقد بعض الأشخاص VM أن ذلك يمكن أن يتم بالسرعة الكافية.

بالإضافة إلى عدم اهتمام الأشخاص VM ، لدينا بعض الاعتراضات القوية على هذا الاقتراح هنا من fgmccabe و RossTate ، كما تمت مناقشته أعلاه. نحن نختلف في بعض الأمور لكنني أقدر وجهات النظر تلك والوقت الذي تم فيه شرحها.

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

لاحظ أن جزء JS من هذا الاقتراح قد يكون ذا صلة في المستقبل ، مثل JS sugar أساسًا من أجل تكامل الوعد المريح. سنحتاج إلى انتظار تبديل المكدس أو coroutines ومعرفة ما إذا كان هذا يمكن أن يعمل علاوة على ذلك. لكنني لا أعتقد أن الأمر يستحق إبقاء القضية مفتوحة لذلك ، لذا أغلق.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

dpw picture dpw  ·  3تعليقات

ghost picture ghost  ·  7تعليقات

konsoletyper picture konsoletyper  ·  6تعليقات

mfateev picture mfateev  ·  5تعليقات

arunetm picture arunetm  ·  7تعليقات