Rust: يمكن أن تتسبب النقطة العائمة إلى مجموعات صحيحة في سلوك غير محدد

تم إنشاؤها على ٣١ أكتوبر ٢٠١٣  ·  234تعليقات  ·  مصدر: rust-lang/rust

الحالة اعتبارًا من 2020-04-18

نحن عازمون على تثبيت سلوك التشبع - float-casts مقابل as ، وقمنا بتثبيت وظائف المكتبة غير الآمنة التي تتعامل مع السلوك السابق. راجع # 71269 للحصول على أحدث مناقشة حول عملية التثبيت هذه.

الحالة اعتبارًا من 2018-11-05

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

الخطوات التالية هي اكتشاف كيفية استرداد الأداء لهذه الحالات:

  • أحد الخيارات هو أخذ سلوك اليوم as cast (وهو UB في بعض الحالات) وإضافة وظائف unsafe للأنواع ذات الصلة وما شابه.
  • آخر هو انتظار LLVM لإضافة مفهوم freeze مما يعني أننا نحصل على نمط بت القمامة ، ولكنه على الأقل ليس UB
  • آخر هو تنفيذ المصبوبات عبر التجميع المضمن في LLVM IR ، حيث لا يتم تحسين الكود الحالي بشكل كبير.

الوضع القديم

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


الإصدار الأصلي التالي:

إذا كانت القيمة لا يمكن احتواؤها في ty2 ، فإن النتائج تكون غير محددة.

1.04E+17 as u8
A-LLVM C-bug I-unsound 💥 P-medium T-lang

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

لقد بدأت بعض العمل لتنفيذ العناصر الجوهرية للتشبع بالتعويم إلى int casts في LLVM: https://reviews.llvm.org/D54749

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

ال 234 كومينتر

الترشيح

مقبول لـ P-high ، نفس المنطق مثل # 10183

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

التغيير إلى P-high ، نفس المنطق مثل # 10183

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

يمكننا إضافة عنصر جوهري إلى LLVM يقوم بإجراء "تحويل آمن". zwarich قد يكون لديه أفكار أخرى.

AFAIK الحل الوحيد في الوقت الحالي هو استخدام جوهر الهدف المحدد. هذا ما يفعله JavaScriptCore ، على الأقل وفقًا لشخص سألته.

أوه ، هذا سهل بما فيه الكفاية إذن.

pingpnkfelix هل هذا مغطى

لم يتم فحص هذه القوالب بواسطة rustc مع تأكيدات التصحيح.

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

لاحظ أن هذه المشكلة تتسبب حاليًا في حدوث ICE عند استخدامها في بعض التعبيرات الثابتة.

يسمح هذا بانتهاك سلامة الذاكرة في حالة الصدأ الآمن ، مثال من منشور المنتدى هذا :

Undefs ، هاه؟ Undefs هي متعة. تميل إلى التكاثر. بعد بضع دقائق من الجدل ..

#[inline(never)]
pub fn f(ary: &[u8; 5]) -> &[u8] {
    let idx = 1e100f64 as usize;
    &ary[idx..]
}

fn main() {
    println!("{}", f(&[1; 5])[0xdeadbeef]);
}

segfaults على نظامي (أحدث ليلة) مع -O.

وضع علامة بـ I-unsound نظرًا لانتهاك سلامة الذاكرة في حالة الصدأ الآمن.

bluss ، هذا لا سيحدث بالنسبة لي ، فقط يعطي خطأ في التأكيد. untagging لأنني كنت الشخص الذي أضافه

تنهد ، لقد نسيت - O ، إعادة وضع العلامات.

إعادة الترشيح لـ P-high. من الواضح أن هذا كان في مرحلة ما من P-high لكنه انخفض بمرور الوقت. يبدو هذا مهمًا جدًا للصحة.

تحرير: لم يتفاعل مع تعليق الفرز ، مع إضافة التسمية يدويًا.

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

وفقًا لـ https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls -5.1.3 تضمن Java أيضًا تعيين قيم NaN إلى 0 و ما لا نهاية إلى أدنى / أقصى عدد صحيح يمكن تمثيله. علاوة على ذلك ، فإن قاعدة Java للتحويل أكثر تعقيدًا من مجرد الالتفاف ، ويمكن أن تكون مزيجًا من التشبع (للتحويل إلى int أو long ) والتفاف (للتحويل إلى أنواع متكاملة أصغر ، إذا لزم الأمر). من المؤكد أن تكرار خوارزمية التحويل بالكامل من Java أمر ممكن ، لكنه يتطلب قدرًا معقولًا من العمليات لكل فريق. على وجه الخصوص ، من أجل ضمان أن نتيجة عملية fpto[us]i في LLVM لا تظهر سلوكًا غير محدد ، ستكون هناك حاجة إلى فحص النطاق.

كبديل ، أود أن أقترح أن الطوابع float-> int مضمونة لتكون صالحة فقط إذا كان من الممكن تمثيل اقتطاع القيمة الأصلية كقيمة لنوع الوجهة (أو ربما [iu]size ؟) وإلى تحتوي على تأكيدات على إصدارات تصحيح الأخطاء التي تثير حالة من الذعر عندما لا يتم تمثيل القيمة بأمانة.

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

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

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

تمت مناقشته في اجتماع @ rust-lang / compiler. يبقى مسار العمل الأكثر اتساقًا:

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

المشكلة الرئيسية هي أننا بحاجة إلى اقتراح ملموس للخيار 2.

الفرز: ف متوسط

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

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

اقتراح ملموس: استخراج الأرقام والأس مثل u64 وأرقام bitshift بواسطة الأس.

fn f64_as_u64(f: f64) -> u64 {
    let (mantissa, exponent, _sign) = f.integer_decode();
    mantissa >> ((-exponent) & 63)
}

نعم ، إنها ليست تكلفة صفرية ، ولكنها قابلة للتحسين إلى حد ما (سيكون من الأفضل إذا وضعنا علامة صحيحة_رمز_رمز inline ) وقيمة على الأقل يمكن لتمرير MIR المستقبلي الذي يوسع قالب عائم> int أن يحلل ما إذا كان الطفو مضمونًا ليكون مناسبًا للإلقاء وتخطي هذا التحويل الثقيل.

هل لا تمتلك LLVM أساسيات النظام الأساسي لوظائف التحويل؟

تحرير : zwarich قال (منذ وقت طويل):

AFAIK الحل الوحيد في الوقت الحالي هو استخدام جوهر الهدف المحدد. هذا ما يفعله JavaScriptCore ، على الأقل وفقًا لشخص سألته.

لماذا تهتم حتى بالذعر؟ AFAIK ، glaebhoerl صحيح ، as من المفترض أن يتم اقتطاع / تمديد ، _ لا_تحقق من المعاملات.

يوم السبت ، 05 مارس 2016 الساعة 03:47:55 صباحًا -0800 ، كتب غابور ليهيل:

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

صحيح. أجد أن هذا مقنع.

في الأربعاء ، 09 مارس 2016 الساعة 02:31:05 صباحًا -0800 ، كتب Eduard-Mihai Burtescu:

هل لا تمتلك LLVM أساسيات النظام الأساسي لوظائف التحويل؟

تحرير :

AFAIK الحل الوحيد في الوقت الحالي هو استخدام جوهر الهدف المحدد. هذا ما يفعله JavaScriptCore ، على الأقل وفقًا لشخص سألته.

لماذا تهتم حتى بالذعر؟ AFAIK ، glaebhoerl صحيح ، as من المفترض أن يتم اقتطاع / تمديد ، _ لا_تحقق من المعاملات.

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

nikomatsakis : يبدو أن السلوك لم يتم تحديده بعد؟ هل يمكنك إعطاء تحديث حول التخطيط بخصوص ذلك؟

فقط واجهت هذا بأعداد أصغر بكثير

    let x: f64 = -1.0;
    x as u8

النتائج في 0 ، 16 ، إلخ. اعتمادًا على التحسينات ، كنت آمل أن يتم تعريفها على أنها 255 ، لذلك لا يتعين علي كتابة x as i16 as u8 .

gmorenz هل جربت !0u8 ؟

في سياق غير منطقي ، كنت أحصل على f64 من تحويل على البيانات المرسلة عبر الشبكة ، بنطاق [-255 ، 255]. كنت آمل أن يتم لفها بشكل جيد (بنفس الطريقة التي يلتف بها <i32> as u8 ).

إليك اقتراح LLVM حديثًا لـ "قتل undef" http://lists.llvm.org/pipermail/llvm-dev/2016-October/106182.html ، على الرغم من أنني بالكاد على دراية كافية لمعرفة ما إذا كان هذا سيحل تلقائيًا أم لا هذه المسألة.

إنهم يستبدلون السم ، مع اختلاف الدلالات قليلاً. انها لن تجعل int -> تعويم يلقي السلوك المحدد.

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

يبدو أن هذا يجب أن يتم وضع علامة عليه I-crash ، معطى https://github.com/rust-lang/rust/issues/10184#issuecomment -139858153.

كان لدينا سؤال حول هذا في #rust-beginners اليوم ، صادف شخص ما في البرية.

الكتاب الذي أكتبه مع jimblandy ، _Programming Rust_ ، يذكر هذا الخطأ.

يُسمح بعدة أنواع من القوالب.

  • قد يتم إرسال الأرقام من أي من الأنواع الرقمية المضمنة إلى أي نوع آخر.

    (...)

    ومع ذلك ، حتى كتابة هذه السطور ، فإن إرسال قيمة كبيرة للفاصلة العائمة إلى نوع عدد صحيح أصغر من أن يمثله يمكن أن يؤدي إلى سلوك غير محدد. هذا يمكن أن يسبب حوادث حتى في الصدأ الآمن. إنه خطأ في المترجم ، github.com/rust-lang/rust/issues/10184 .

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

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

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

ربما يكون الأمر كذلك ، لكن هذا يبدو متعامدًا بالنسبة لي.

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

  • نوع من النتيجة المحددة
  • قيمة غير محددة (ليس سلوكًا غير محدد)

    • المؤيد: يتيح لنا هذا فقط استخدام العناصر الجوهرية الخاصة بالنظام الأساسي المتوفرة في كل نظام أساسي.

    • يخدع: إنه خطر قابلية النقل. بشكل عام ، أشعر أننا لم نستخدم النتائج غير المحددة كثيرًا ، على الأقل في اللغة (أنا متأكد من أننا نفعل ذلك في libs في أماكن مختلفة).

ليس من الواضح بالنسبة لي ما إذا كانت هناك سابقة واضحة لما يجب أن تكون النتيجة في الحالة الأولى؟

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

أنا أحب التشبع لأنني أستطيع فهمه ويبدو أنه مفيد ، لكن يبدو أنه يتعارض إلى حد ما مع الطريقة التي يقوم بها u64 as u32 بالاقتطاع. لذلك ربما يكون نوعًا ما من النتائج المستندة إلى الاقتطاع منطقيًا ، وهو ما أعتقد أنه ربما يكون ما اقترحه @ oli-obk - لا أفهم تمامًا ما المقصود من هذا الرمز أن يفعل =)

يعطي الكود الخاص بي القيمة الصحيحة للأشياء في النطاق 0..2 ^ 64 والقيم الحتمية ولكن الزائفة لكل شيء آخر.

يتم تمثيل العوامات بواسطة الأس العشري ^ ، على سبيل المثال ، 1.0 هو (2 << 52) ^ -52 وبما أن bitshifts و الأسس هما نفس الشيء في النظام الثنائي ، يمكننا فقط عكس الإزاحة (وبالتالي نفي الأس و اليمين تحول).

+1 للحتمية.

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

  • التشبع (تصبح القيم خارج النطاق IntType::max_value() / min_value() )
  • Modulo (يتم التعامل مع القيم خارج النطاق كما لو كان التحويل إلى bigint أولاً ، ثم الاقتطاع)

يهدف الجدول أدناه إلى تحديد كلا الخيارين بشكل كامل. T هو أي نوع من أنواع الأعداد الصحيحة للجهاز. Tmin و Tmax هما T::min_value() و T::max_value() . RTZ (v) تعني أخذ القيمة الرياضية لـ v و Round نحو الصفر للحصول على عدد صحيح رياضي.

v | v as T (تشبع) | v as T (modulo)
---- | ---- | ----
في النطاق (Tmin <= v <= Tmax) | RTZ (ت) | RTZ (v)
سالب صفر | 0 | 0
ن | 0 | 0
اللانهاية | Tmax | 0
اللانهاية | Tmin | 0
ت> Tmax | Tmax | RTZ (v) مقطوع لتناسب T.
الخامس <Tmin | Tmin | RTZ (v) مقطوع لتناسب T.

يحدد معيار ECMAScript عمليات ToInt32 و ToUint32 و ToInt16 و ToUint16 و ToInt8 و ToUint8 وهدفتي مع خيار "modulo" أعلاه هو مطابقة تلك العمليات في كل حالة.

كما يحدد ECMAScript ToInt8Clamp التي لا تطابق أي من الحالتين أعلاه: فإنه "نصف مستديرة لحتى" التقريب على القيم كسور بدلا من "جولة الى نقطة الصفر".

اقتراح @ oli-obk هو طريقة ثالثة ، تستحق التفكير فيما إذا كان الحساب أسرع ، للقيم الموجودة في النطاق.

@ oli-obk ماذا عن أنواع الأعداد الصحيحة الموقعة؟

رمي اقتراح آخر في المزيج: مارك u128 يلقي على الطفو على أنه غير آمن ويجبر الناس على اختيار طريقة التعامل معه بشكل صريح. u128 نادر جدًا حاليًا.

Manishearth ، آمل أن يكون هناك أعداد صحيحة من دلالات متشابهة ← عوامات مثل أعداد عائمة ← أعداد صحيحة. نظرًا لأن كلاهما عبارة عن UB-ful ، ولا يمكننا جعل عدد صحيح عائم ← عددًا صحيحًا بعد الآن ، يجب أن نتجنب جعل عدد صحيح ← عائم غير آمن أيضًا.

بالنسبة لـ float → الصحيح ، سيكون تشبع AFAICT أسرع (ينتج عنه تسلسل and ، اختبار + مقارنة قفزة القفز والقفز ، كل ذلك من 0.66 أو 0.5 2-3 دورات على الأقواس الحديثة). أنا شخصياً لا أهتم كثيراً بالسلوك الدقيق الذي نقرره طالما أن القيم ضمن النطاق تكون سريعة بقدر الإمكان.

ألن يكون من المنطقي جعله يتصرف مثل الفائض؟ لذلك في بناء التصحيح ، سيكون من الذعر إذا قمت بعمل طاقم بسلوك غير محدد. ثم يمكن أن يكون لديك طرق لتحديد سلوك الإرسال مثل 1.04E+17.saturating_cast::<u8>() و unsafe { 1.04E+17.unsafe_cast::<u8>() } وربما أخرى.

أوه ، اعتقدت أن المشكلة كانت فقط لـ u128 ، ويمكننا جعل ذلك غير آمن في كلا الاتجاهين.

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

ومع ذلك ، هلع من التصحيح ، وعند الإصدار سيكون رائعًا.

هذا يؤثر على:

  • f32 -> u8, u16, u32, u64, u128, usize ( -1f32 as _ للجميع ، f32::MAX as _ للجميع ما عدا U128)
  • f32 -> i8, i16, i32, i64, i128, isize ( f32::MAX as _ للجميع)
  • f64 -> جميع ints ( f64::MAX as _ للجميع)

f32::INFINITY as u128 هو أيضًا UB

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

ألن يكون من المنطقي جعله يتصرف مثل الفائض؟ لذلك في بناء التصحيح ، سيكون من الذعر إذا قمت بعمل طاقم بسلوك غير محدد.

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

FWIW ، فإن الشيء "kill undef" من شأنه ، في الواقع ، أن يوفر طريقة لإصلاح عدم أمان الذاكرة ، ولكن ترك النتيجة غير حتمية. أحد المكونات الرئيسية هو:

3) أنشئ تعليمة جديدة ، '٪ y = freeze٪ x' ، توقف انتشار
السم. إذا كان الإدخال سامًا ، فإنه يُرجع تعسفيًا ، ولكنه ثابت ،
القيمة. (مثل undef القديم ، لكن كل استخدام له نفس القيمة) ، وإلا فإنه
فقط إرجاع قيمة الإدخال.

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

... لكن ليس حتمية عبر البنى ، إذا كان أي شخص يتساءل. يقوم x86 بإرجاع 0x80000000 لكافة المدخلات التالفة ؛ يشبع ARM للمدخلات خارج النطاق و (إذا كنت أقرأ هذا الرمز الزائف على اليمين) يُرجع 0 لـ NaN. لذلك إذا كان الهدف هو إنتاج نتيجة حتمية ومستقلة عن النظام الأساسي ، فلا يكفي مجرد استخدام fp-to-int الجوهرية للمنصة ؛ على الأقل في ARM ، تحتاج أيضًا إلى التحقق من سجل الحالة للحصول على استثناء. قد يكون لهذا بعض النفقات العامة في حد ذاته ، وبالتأكيد يمنع التحويل الآلي في حالة غير مرجحة أن استخدام الجوهر لم يكن بالفعل. بالتناوب ، أعتقد أنه يمكنك صراحة اختبار القيم داخل النطاق باستخدام عمليات مقارنة منتظمة ثم استخدام تعويم منتظم إلى int. هذا يبدو أجمل كثيرًا في المحسن ...

تحويلات as لا تصيب بالذعر أبدًا في الوقت الحاضر

في مرحلة ما ، قمنا بتغيير + إلى حالة الذعر (في وضع التصحيح). لن أشعر بالصدمة لرؤية حالة الذعر as في الحالات التي كانت في السابق UB.

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

لذلك ، أعتقد في هذه المرحلة أن jorendorff عدّد أساسًا ما يبدو لي أنه أفضل خطة :

  • as سيكون له سلوك حتمي؛
  • سنختار سلوكًا بناءً على مزيج من مدى منطقيته ومدى فعاليته

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

هل هناك أي شخص يشعر بالحافز للقيام بذلك؟ سأقوم بوضع علامة على هذا كـ E-help-wanted أمل جذب شخص ما. (@ oli-obk؟)

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

حاليًا جميع وظائف التقريب / الاقتطاع في Rust بطيئة جدًا (استدعاءات الوظائف مع تطبيقات دقيقة بشق الأنفس) ، لذا فإن as هو الملاذ الأخير لتقريب التعويم السريع.

إذا كنت ستجني as أي شيء أكثر من مجرد cvttss2si ، فالرجاء أيضًا إضافة بديل ثابت هو ذلك تمامًا.

pornel ، هذا ليس مجرد UB من النوع النظري حيث تكون الأشياء على ما يرام إذا تجاهلت أنها ub ، فلها آثار في العالم الحقيقي. لقد استخرجت # 41799 من مثال رمز العالم الحقيقي.

@ est31 أوافق على أن تركه كـ UB خطأ ، لكنني رأيت freeze يُقترح كحل لـ UB. AFAIK التي تجعلها قيمة حتمية محددة ، لا يمكنك تحديد أي منها. هذا السلوك جيد بالنسبة لي.

لذلك سأكون بخير إذا أنتج على سبيل المثال u128::MAX as f32 17.5 على x86 و 999.0 على x86-64 و -555 على ARM.

لن ينتج freeze قيمة محددة وحتمية وغير محددة. لا تزال نتيجتها "أي نمط بت يحبه المترجم" ، وهي متسقة فقط عبر استخدامات نفس العملية. قد يتجنب هذا الأمثلة المنتجة لـ UB التي جمعها الأشخاص أعلاه ، لكنه لن يعطي هذا:

u128 :: MAX مثل f32 أنتج بشكل حتمي 17.5 في x86 و 999.0 على x86-64 و -555 على ARM.

على سبيل المثال ، إذا لاحظت LLVM أن u128::MAX as f32 يتدفق واستبدله بـ freeze poison ، فقد يكون التخفيض الصالح لـ fn foo() -> f32 { u128::MAX as f32 } على x86_64 كما يلي:

foo:
  ret

(أي ، فقط قم بإرجاع كل ما تم تخزينه في سجل الإرجاع)

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

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

هل يتوفر شيء مثل freeze على LLVM؟ اعتقدت أن هذا كان مجرد بناء نظري بحت.

nikomatsakis لم أره يستخدم بهذا poison ) - إنه تجديد مخطط للسموم / undef.

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

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

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

  • خرائط unsafe_trunc(Int64, x) مباشرة إلى LLVM المضمن fptosi (1.5 نانوثانية)
  • يطرح trunc(Int64, x) استثناءً للقيم خارج النطاق (3 نانوثانية)
  • يطرح convert(Int64, x) استثناءً للقيم خارج النطاق أو القيم غير الصحيحة (6 نانوثانية)

أيضًا ، لقد سألت في القائمة البريدية عن جعل السلوك غير المحدد أكثر تحديدًا ، لكنني لم أتلق ردًا واعدًا جدًا.

bstrie أنا بخير مع RFC ، لكنني أعتقد أنه سيكون من المفيد بالتأكيد الحصول على بيانات! ومع ذلك ، فإن تعليق simonbyrne مفيد للغاية في هذا الصدد.

لقد لعبت مع دلالات JS (تم ذكر modulojorendorff ) ودلالات Java التي يبدو أنها عمود "التشبع". في حالة انتهاء صلاحية هذه الروابط ، يكون JS و Java .

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

لست متأكدًا تمامًا من كيفية تنفيذ دلالات "mod" في Rust ...

بالنسبة لي ، على الرغم من ذلك ، يبدو واضحًا أننا سنحتاج إلى عدد كبير من الأساليب f32::as_u32_unchecked() ومثل أولئك الذين يحتاجون إلى الأداء.

يبدو من الواضح أننا سنحتاج إلى عدد كبير من الأساليب f32::as_u32_unchecked() ومثل أولئك الذين يحتاجون إلى الأداء.

هذه مشكلة - أم أنك تعني متغيرًا آمنًا ولكن محددًا بالتنفيذ؟

هل لا يوجد خيار لتنفيذ التقصير السريع المحدد؟

eddyb كنت أفكر أنه سيكون لدينا unsafe fn as_u32_unchecked(self) -> u32 على f32 وهذا هو التناظرية المباشرة لما هو as اليوم.

بالتأكيد لن أدعي أن تطبيق Rust الذي كتبته هو الأمثل ، لكن كان لدي انطباع بأنه عند قراءة هذا الموضوع ، كانت الحتمية والسلامة أكثر أهمية من السرعة في هذا السياق في معظم الأوقات. فتحة الهروب unsafe مخصصة لأولئك الموجودين على الجانب الآخر من السياج.

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

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

لكنني أقدر أن شخصًا ما في هذا الموضوع سيتعين عليه كتابة RFC مقنع (في قائمة llvm-dev) ، والحصول على دعم ، وتنفيذه (في الخلفيات الخلفية التي نهتم بها ، ومع تنفيذ احتياطي للآخرين الأهداف). ربما يكون أسهل من إقناع llvm-dev لجعل الممثلين الحاليين ليسوا UB (لأنها أسئلة ذات خطوات جانبية مثل "هل سيؤدي ذلك إلى جعل أي برامج C و C ++ أبطأ") ، ولكن لا يزال الأمر صعبًا للغاية.

فقط في حالة اختيارك بين هذه:

التشبع (تصبح القيم خارج النطاق IntType :: max_value () / min_value ())
Modulo (يتم التعامل مع القيم خارج النطاق كما لو كان التحويل إلى bigint أولاً ، ثم الاقتطاع)

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

لقد قمت بتمييز هذا كـ E-needs-mentor ووضعت علامة عليه بـ WG-compiler-middle لأنه يبدو أن الفترة الضمنية قد تكون وقتًا رائعًا لمزيد من التحقيق في هذا الأمر! ملاحظاتي الحالية على خطة التسجيل قليلة جدًا ، لذا سيكون رائعًا إذا أراد شخص من @ rust-lang / compiler المساعدة في توضيح تلك

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

تخطط IIRC LLVM لتنفيذ freeze في النهاية ، والذي يجب أن يسمح لنا بالتعامل مع UB عن طريق القيام بـ freeze .

نتائجي حتى الآن: https://gist.github.com/s3bk/4bdfbe2acca30fcf587006ebb4811744

تقوم المتغيرات _array بتشغيل حلقة من 1024 قيمة.
_cast: x as i32
_clip: x.min (MAX) .max (MIN) كـ i32
_الذعر: هلع إذا كان x خارج الحدود
_zero: يعيّن النتيجة على صفر إذا كانت خارج الحدود

test bench_array_cast       ... bench:       1,840 ns/iter (+/- 37)
test bench_array_cast_clip  ... bench:       2,657 ns/iter (+/- 13)
test bench_array_cast_panic ... bench:       2,397 ns/iter (+/- 20)
test bench_array_cast_zero  ... bench:       2,671 ns/iter (+/- 19)
test bench_cast             ... bench:           2 ns/iter (+/- 0)
test bench_cast_clip        ... bench:           2 ns/iter (+/- 0)
test bench_cast_panic       ... bench:           2 ns/iter (+/- 0)
test bench_cast_zero        ... bench:           2 ns/iter (+/- 0)

ربما لا تحتاج إلى تقريب النتائج إلى عدد صحيح للعمليات الفردية. من الواضح أنه يجب أن يكون هناك بعض الاختلاف وراء هذين ns / iter. أم أنها حقًا هكذا ، _ بالضبط 2 نانوثانية لجميع المتغيرات الأربعة؟

@ sp-1234 أتساءل عما إذا كان قد تم تحسينه جزئيًا.

@ sp-1234 إنه سريع جدًا في القياس. المعايير غير المصفوفة غير مجدية بشكل أساسي.
إذا فرضت أن تكون دوال القيمة المفردة دالات عبر #[inline(never)] ، فستحصل على 2ns مقابل 3ns.

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

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

قمت بتشغيل معيار @ s3bk محليًا. يمكنني أن أؤكد أن الإصدارات العددية قد تم تحسينها تمامًا ، كما أن ASM لمتغيرات المصفوفة تبدو محسّنة بشكل جيد بشكل مثير للريبة: على سبيل المثال ، الحلقات متجهية ، وهو أمر رائع ولكنه يجعل من الصعب استقراء الأداء إلى الكود القياسي.

لسوء الحظ ، لا يبدو أن إرسال رسائل غير مرغوب فيها إلى black_box يساعد. أرى أن ASM تقوم بعمل مفيد ، لكن تشغيل المعيار لا يزال يعطي باستمرار 0ns للمعايير القياسية (باستثناء cast_zero ، والذي يظهر 1ns). أرى أن alexcrichton أجرى المقارنة 100 مرة في معاييرهم ، لذلك اعتمدت نفس الاختراق. أرى الآن هذه الأرقام ( شفرة المصدر ):

test bench_cast             ... bench:          53 ns/iter (+/- 0)
test bench_cast_clip        ... bench:         164 ns/iter (+/- 1)
test bench_cast_panic       ... bench:         172 ns/iter (+/- 2)
test bench_cast_zero        ... bench:         100 ns/iter (+/- 0)

تختلف معايير المصفوفة كثيرًا بالنسبة لي لأثق بها. حسنًا ، الحقيقة التي يجب إخبارها ، أنا متشكك في البنية التحتية المعيارية test أي حال ، خاصة بعد رؤية الأرقام المذكورة أعلاه مقارنة بالأرقام المسطحة التي حصلت عليها سابقًا. علاوة على ذلك ، حتى 100 تكرار فقط لـ black_box(x); (كخط أساس) تستغرق 34 ثانية ، مما يجعل من الصعب تفسير هذه الأرقام بشكل موثوق.

نقطتان جديرتان بالملاحظة:

  • على الرغم من عدم التعامل مع NaN على وجه التحديد (يتم إرجاعه -inf بدلاً من 0؟) ، يبدو أن تنفيذ cast_clip أبطأ من فريق alexcrichton المشبع (لاحظ أن as ، 53-54 نانو).
  • بخلاف نتائج مصفوفة @ s3bk ، أرى أن cast_panic أبطأ من عمليات التمثيل الأخرى. أرى أيضًا تباطؤًا أكبر في معايير الصفيف. ربما تعتمد هذه الأشياء بشكل كبير على التفاصيل المعمارية الدقيقة و / أو الحالة المزاجية؟

للسجل ، قمت بالقياس باستخدام rustc 1.21.0-night (d692a91fa 2017-08-04) ، -C opt-level=3 ، على i7-6700K تحت حمل خفيف.


في الختام ، استنتج أنه لا توجد بيانات موثوقة حتى الآن وأن الحصول على بيانات أكثر موثوقية يبدو صعبًا. علاوة على ذلك ، أشك بشدة في أن أي تطبيق حقيقي يقضي حتى 1٪ من وقت ساعة الحائط في هذه العملية. لذلك ، أقترح المضي قدمًا من خلال تنفيذ تشبع as casts في rustc ، خلف علامة -Z ، ثم تشغيل بعض المعايير غير الاصطناعية مع وبدون هذه العلامة لتحديد التأثير على الواقعية التطبيقات.

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

أعترف أنني لست على دراية بالصدأ ، لكنني أعتقد أن هذا السطر غير صحيح تمامًا: std::i32::MAX (2 ^ 31-1) لا يمكن تمثيله تمامًا كـ Float32 ، لذلك std::i32::MAX as f32 سيكون مقربًا إلى أقرب قيمة يمكن تمثيلها (2 ^ 31). إذا تم استخدام هذه القيمة كوسيطة x ، تكون النتيجة غير محددة تقنيًا. الاستبدال بمتباينة صارمة يجب أن يصلح هذه الحالة.

نعم ، كانت لدينا بالضبط هذه المشكلة في Servo من قبل. كان الحل النهائي هو الصب إلى f64 ثم التثبيت.

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

باستخدام 0x7FFF_FF80i32 كحد أعلى و -0x8000_0000i32 يجب أن يحل هذا دون التحويل إلى f64.
تحرير: استخدم القيمة الصحيحة.

أعتقد أنك تقصد 0x7fff_ff80 ، ولكن ببساطة استخدام عدم مساواة صارمة من المحتمل أن يجعل نية الكود أكثر وضوحًا.

كما في x < 0x8000_0000u32 as f32 ؟ من المحتمل أن تكون فكرة جيدة.

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

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

للأسباب المذكورة أعلاه ، لعبت مع إصدار بديل موجه لتدفق البيانات وبدون فروع من فريق alexcrichton مع دلالات التشبع وإصلاح simonbyrne . لقد قمت بتطبيقه لـ u16 و i16 و i32 حيث يتعين عليهم جميعًا تغطية حالات مختلفة قليلاً مما يؤدي إلى اختلاف الأداء.

نتائج:

test i16_bench_array_cast       ... bench:          99 ns/iter (+/- 2)
test i16_bench_array_cast_clip  ... bench:         197 ns/iter (+/- 3)
test i16_bench_array_cast_clip2 ... bench:         113 ns/iter (+/- 3)
test i16_bench_cast             ... bench:          76 ns/iter (+/- 1)
test i16_bench_cast_clip        ... bench:         218 ns/iter (+/- 25)
test i16_bench_cast_clip2       ... bench:         148 ns/iter (+/- 4)
test i16_bench_rng_cast         ... bench:       1,181 ns/iter (+/- 17)
test i16_bench_rng_cast_clip    ... bench:       1,952 ns/iter (+/- 27)
test i16_bench_rng_cast_clip2   ... bench:       1,287 ns/iter (+/- 19)

test i32_bench_array_cast       ... bench:         114 ns/iter (+/- 1)
test i32_bench_array_cast_clip  ... bench:         200 ns/iter (+/- 3)
test i32_bench_array_cast_clip2 ... bench:         128 ns/iter (+/- 3)
test i32_bench_cast             ... bench:          74 ns/iter (+/- 1)
test i32_bench_cast_clip        ... bench:         168 ns/iter (+/- 3)
test i32_bench_cast_clip2       ... bench:         189 ns/iter (+/- 3)
test i32_bench_rng_cast         ... bench:       1,184 ns/iter (+/- 13)
test i32_bench_rng_cast_clip    ... bench:       2,398 ns/iter (+/- 41)
test i32_bench_rng_cast_clip2   ... bench:       1,349 ns/iter (+/- 19)

test u16_bench_array_cast       ... bench:          99 ns/iter (+/- 1)
test u16_bench_array_cast_clip  ... bench:         136 ns/iter (+/- 3)
test u16_bench_array_cast_clip2 ... bench:         105 ns/iter (+/- 3)
test u16_bench_cast             ... bench:          76 ns/iter (+/- 2)
test u16_bench_cast_clip        ... bench:         184 ns/iter (+/- 7)
test u16_bench_cast_clip2       ... bench:         110 ns/iter (+/- 0)
test u16_bench_rng_cast         ... bench:       1,178 ns/iter (+/- 22)
test u16_bench_rng_cast_clip    ... bench:       1,336 ns/iter (+/- 26)
test u16_bench_rng_cast_clip2   ... bench:       1,207 ns/iter (+/- 21)

تم إجراء الاختبار على وحدة المعالجة المركزية Intel Haswell i5-4570 و Rust 1.22.0 ليلاً.
clip2 هو التطبيق الجديد بدون فروع بنكية. توافق مع clip على جميع قيم الإدخال f32 الممكنة 2 ^ 32.

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

مقارنة Assmbly على x86: https://godbolt.org/g/AhdF71
النسخة غير المتفرعة بشكل جيد جدًا تعين تعليمات minss / maxss.

لسوء الحظ ، لم أتمكن من إنشاء godbolt لتجميع ARM من Rust ، ولكن هنا مقارنة ARM للطرق مع Clang: https://godbolt.org/g/s7ronw
بدون القدرة على اختبار الكود ومعرفة الكثير من ARM: يبدو حجم الكود أصغر أيضًا و LLVM ينتج غالبًا vmax / vmin الذي يبدو واعدًا. ربما يمكن تعليم LLVM في النهاية لطي معظم الكود في تعليمة واحدة؟

ActuallyaDeviloper @ ASM والنتائج القياسية تبدو جيدة جدًا! علاوة على ذلك ، من المحتمل أن يكون إنشاء التعليمات البرمجية غير المتفرعة مثل رمزك أسهل في rustc من الشروط المتداخلة للحلول الأخرى (بالنسبة للسجل ، أفترض أننا نريد إنشاء IR مضمن بدلاً من استدعاء دالة عنصر lang). شكرا جزيلا لكتابة هذا لك.

لدي سؤال حول u16_cast_clip2 : لا يبدو أنه يتعامل مع NaN ؟! هناك تعليق يتحدث عن NaN ، لكنني أعتقد أن الوظيفة ستمرر NaN من خلال غير معدلة وستحاول تحويلها إلى f32 (وحتى إذا لم يحدث ذلك ، فستنتج إحدى قيم الحدود بدلاً من 0 ).

ملاحظة: لأكون واضحًا ، لم أكن أحاول الإيحاء بأنه من غير المهم ما إذا كان يمكن توجيه فريق الممثلين. من الواضح أنه من المهم إذا كان الكود المحيط قابلًا للتوجيه. لكن الأداء القياسي مهم أيضًا ، نظرًا لأن التوجيه غير قابل للتطبيق في كثير من الأحيان ، والمعايير التي كنت أعلق عليها لم تقدم أي بيان حول الأداء القياسي. من باب الاهتمام ، هل راجعت ASM لمقاييس الأداء *array* لمعرفة ما إذا كانت لا تزال متجهة مع تنفيذك؟

rkruppe أنت محق ، لقد قمت بالصدفة بتبديل جوانب if ونسيت ذلك. f32 as u16 بالشيء الصحيح عن طريق اقتطاع 0x8000 العلوي بعيدًا ، لذلك لم تكتشفه الاختبارات أيضًا. لقد أصلحت المشكلة الآن عن طريق تبديل الفروع مرة أخرى واختبار جميع الطرق بـ if (y.is_nan()) { panic!("NaN"); } هذه المرة.

لقد قمت بتحديث منشوري السابق. لم يتغير رمز x86 بشكل كبير على الإطلاق ولكن للأسف التغيير يمنع LLVM من توليد vmax في حالة ARM u16 لسبب ما. أفترض أن هذا له علاقة ببعض التفاصيل حول معالجة NaN لتعليمات ARM أو ربما يكون أحد قيود LLVM.

لمعرفة سبب نجاحها ، لاحظ أن قيمة الحد الأدنى هي في الواقع 0 للقيم غير الموقعة. لذلك يمكن التقاط NaN والحد الأدنى في نفس الوقت.

إصدارات المصفوفة متجهية.
Godbolt: https://godbolt.org/g/HnmsSV

رد: ARM asm ، أعتقد أن سبب عدم استخدام vmax هو أنه يُرجع NaN إذا كان أي من المعاملين هو NaN . لا يزال الكود بدون فروع ، على الرغم من أنه يستخدم حركات مخصصة فقط ( vmovgt ، في إشارة إلى نتيجة vcmp مع 0).

لمعرفة سبب نجاحها ، لاحظ أن قيمة الحد الأدنى هي في الواقع 0 للقيم غير الموقعة. لذلك يمكن التقاط NaN والحد الأدنى في نفس الوقت.

أوه ، صحيح. لطيف.

أود أن أقترح المضي قدمًا من خلال تطبيق التشبع كقوالب من الصدأ ، خلف علم Z-

لقد قمت بتنفيذ هذا وسأقوم بتقديم PR بمجرد أن أصلح أيضًا # 41799 ولدي الكثير من الاختبارات.

لقد أشار 45134 إلى مسار الكود الذي فاتني (توليد تعبيرات LLVM الثابتة - وهذا منفصل عن التقييم الثابت الخاص بـ rustc). سوف أقوم بإصلاح ذلك في نفس العلاقات العامة ، لكن الأمر سيستغرق بعض الوقت لفترة أطول.

rkruppe يجب عليك التنسيق مع @ oli-obk حتى ينتهي ميري بنفس التغييرات.

طلب السحب لأعلى: # 45205

تم دمج 45205 ، بحيث يمكن لأي شخص الآن (حسنًا ، بدءًا من الليلة التالية) قياس تأثير التشبع في الأداء عن طريق تمرير -Z saturating-float-casts عبر RUSTFLAGS . [1] قد تكون هذه القياسات ذات قيمة كبيرة لتقرير كيفية المضي قدمًا في هذه المشكلة.

[1] بالمعنى الدقيق للكلمة ، لن يؤثر هذا على الأجزاء غير العامة ، بخلاف #[inline] من المكتبة القياسية ، لذا لكي تكون دقيقًا بنسبة 100٪ ، قد ترغب في إنشاء std محليًا باستخدام Xargo. ومع ذلك ، لا أتوقع أنه سيكون هناك الكثير من التعليمات البرمجية المتأثرة بهذا (على سبيل المثال ، سمات التحويل المختلفة هي #[inline] ، على سبيل المثال).

rkruppe ، أقترح بدء صفحة داخلية / مستخدمين لجمع البيانات ، على نفس المنوال مثل https://internals.rust-lang.org/t/help-us-benchmark-incremental-compilation/6153/ (يمكننا أيضًا بعد ذلك ربط الأشخاص بذلك ، بدلاً من بعض التعليقات العشوائية في أداة تعقب المشكلات لدينا)

rkruppe ، يجب عليك إنشاء مشكلة تتبع. تم تقسيم هذه المناقشة إلى قضيتين بالفعل. هذا ليس جيدا!

Gankro نعم أوافق ، ولكن قد المنشور بشكل صحيح ، لذلك سأطلب تعليقات من الأشخاص المشتركين في هذه المشكلة في الوقت الحالي.

@ est31 هممم . على الرغم من أن العلامة -Z تغطي كلا الاتجاهين (والذي قد يكون خطأ ، في وقت لاحق) ، يبدو من غير المحتمل أننا سنقوم بالتبديل على كلا الاتجاهين في نفس الوقت ، وهناك القليل من التداخل بين الاثنين من حيث ما يجب أن تتم مناقشتها (على سبيل المثال ، تتوقف هذه المشكلة على أداء التشبع ، بينما في # 41799 تم الاتفاق على الحل الصحيح).
ومن سخيفة بعض الشيء أن المعايير المستهدفة في المقام الأول في هذه القضية من شأنه أيضا أن قياس أثر الإصلاح على # 41799، ولكن هذا يمكن في معظم تؤدي إلى overreporting من انحدارات الأداء، لذلك أنا نوع من بخير مع ذلك. (ولكن إذا كان لدى أي شخص الدافع لتقسيم العلم -Z إلى قسمين ، فتابع.)

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

لقد قمت بصياغة منشور داخلي: https://gist.github.com/Gankro/feab9fb0c42881984caf93c7ad494ebd

لا تتردد في نسخ ذلك ، أو أعطني ملاحظات فقط حتى أتمكن من نشرها. (لاحظ أنني مرتبك قليلاً بشأن سلوك const fn )

معلومة إضافية هي أن تكلفة تحويلات float-> int خاصة بالتطبيق الحالي ، وليست أساسية. في x86 ، يُرجع cvtss2si cvttss2si 0x80000000 في الحالات المنخفضة جدًا والعالية جدًا والحالات nan ، لذلك يمكن للمرء تنفيذ -Zsaturating-float-casts مع cvtss2si cvttss2si متبوعًا برمز خاص في حالة 0x80000000 ، لذلك يمكن أن يكون مجرد فرع واحد للمقارنة والتنبؤ في الحالة الشائعة. على ARM، vcvt.s32.f32 لديه -Zsaturating-float-casts دلالات بالفعل. لا تقوم LLVM حاليًا بتحسين عمليات الفحص الإضافية في كلتا الحالتين.

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

رائع ، شكرا جزيلا! لقد تركت بعض الملاحظات على الجوهر. بعد قراءة هذا ، أود أن أحاول فصل u128-> f32 casts عن العلم -Z. فقط من أجل التخلص من التحذير المُشتت حول العلم الذي يغطي ميزتين متعامدتين.

(لقد قدمت # 45900 لإعادة تركيز العلامة -Z بحيث تغطي فقط مشكلة التعويم-> int)

سيكون من الرائع لو تمكنا من الحصول على تطبيقات خاصة بالمنصة مثل lasunfishcode (على الأقل لـ x86) قبل طلب قياس الأداء الشامل. لا ينبغي أن يكون الأمر صعبًا للغاية.

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

لقد قمت بتحديث المسودة لتعكس المناقشة (بشكل أساسي تمزيق أي ذكر مضمن لـ u128 -> f32 إلى قسم إضافي في النهاية).

sunfishcode هل أنت متأكد؟ أليس llvm.x86.sse.cvttss2si هو ما تبحث عنه؟

إليك رابط ملعب يستخدمه:

https://play.rust-lang.org/؟gist=33cf9e0871df2eb2475b845af4f1b574&version=nightly

في وضع الإصدار ، يتم تجميع كل من float_to_int_with_intrinsic و float_to_int_with_as إلى تعليمة واحدة. (في وضع التصحيح ، يهدر float_to_int_with_intrinsic بعض الإرشادات التي تضع الصفر في القمة ، لكنها ليست سيئة للغاية.)

يبدو أنه يقوم بالطي المستمر بشكل صحيح. فمثلا،

float_to_int_with_intrinsic(42.0)

يصبح

movl    $42, %eax

لكن قيمة خارج النطاق ،

float_to_int_with_intrinsic(42.0e33)

لا يتم طيها:

cvttss2si   .LCPI2_0(%rip), %eax

(من الناحية المثالية ، يمكن طيها إلى 0x80000000 الثابت ، ولكن هذا ليس بالأمر المهم. الشيء المهم هو أنه لا ينتج غير مدعوم.)

رائع. يبدو أن هذا سيعمل!

إنه لأمر رائع أن نعرف أننا ، بعد كل شيء ، لدينا طريقة للبناء على cvttss2si . ومع ذلك ، لا أوافق على أنه من الأفضل تغيير التطبيق لاستخدامه قبل أن ندعو إلى المعايير:

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

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

أخيرًا ، لست متأكدًا من أن البناء على cvttss2si سيكون أسرع مما لدينا الآن (على الرغم من أنه في ARM ، من الواضح أن مجرد استخدام التعليمات المناسبة أفضل):

  • تحتاج إلى مقارنة لتلاحظ أن التحويل يرجع 0x80000000 ، وإذا كان هذا هو الحال ، فلا تزال بحاجة إلى مقارنة أخرى (لقيمة الإدخال) لمعرفة ما إذا كان يجب عليك إرجاع int :: MIN أو int :: MAX. وإذا كان نوعًا صحيحًا بعلامة ، فلا أرى كيفية تجنب مقارنة ثالثة لتمييز NaN. لذلك في أسوأ الحالات:

    • لا تحفظ في عدد المقارنات / التحديدات

    • أنت تتداول بمقارنة تعويم لمقارنة int ، وهو ما قد يكون جيدًا بالنسبة لأنوية OoO (إذا كنت تعاني من اختناق على FUs التي يمكن أن تقارن ، والتي تبدو كبيرة نسبيًا إذا) ، ولكن هذه المقارنة تعتمد أيضًا على التعويم -> مقارنة int ، في حين أن المقارنات في التطبيق الحالي كلها مستقلة ، لذا فمن غير الواضح أن هذا هو الفوز.

  • ربما تصبح التوجيهات أكثر صعوبة أو مستحيلة. لا أتوقع أن يتعامل متجه الحلقة مع هذا الجوهر على الإطلاق.
  • وتجدر الإشارة أيضًا إلى أن (AFAIK) هذه الإستراتيجية تنطبق فقط على بعض أنواع الأعداد الصحيحة. f32 -> u8 ، على سبيل المثال ، ستحتاج إلى إصلاحات إضافية للنتيجة ، مما يجعل هذه الإستراتيجية غير مربحة بشكل واضح. لست متأكدًا تمامًا من الأنواع التي تتأثر بهذا (على سبيل المثال ، لا أعرف ما إذا كانت هناك تعليمات لـ f32 -> u32) ، لكن التطبيق الذي يستخدم هذه الأنواع فقط لن يفيد على الإطلاق.
  • يمكنك عمل حل تفريعي بمقارنة واحدة فقط في المسار السعيد (على عكس مقارنات أو ثلاثة ، وبالتالي الفروع ، كما فعلت الحلول السابقة). ومع ذلك ، كما جادل ActuallyaDeviloper سابقًا ، قد لا يكون التفريع مرغوبًا: أصبح الأداء الآن أكثر اعتمادًا على عبء العمل وتعتمد على التنبؤ بالفرع.

هل هي آمنة لنفترض اننا سنحتاج الى عدد كبير من unsafe fn as_u32_unchecked(self) -> u32 والأصدقاء بغض النظر عن ما يظهر القياس؟ ما غيرها من اللجوء المحتمل أن يتلقى أحد ما إذا كانت لم ينتهي مراقبة التباطؤ؟

bstrie أعتقد أنه سيكون من المنطقي ، في مثل هذه الحالة ، القيام بشيء مثل توسيع بناء الجملة إلى as <type> [unchecked] واشتراط وجود unchecked فقط في unsafe السياقات.

كما أراها ، فإن مجموعة من _unchecked تعمل كمتغيرات من as الصب سيكون ثؤلولًا ، سواء فيما يتعلق بالحدس ، أو عندما يتعلق الأمر بإنشاء وثائق نظيفة وقابلة للاستخدام.

ssokolow يجب أن تكون إضافة بناء الجملة دائمًا الملاذ الأخير ، خاصة إذا كان من الممكن التعامل مع كل هذا من خلال عشر وظائف روتينية فقط. حتى الحصول على foo.as_unchecked::<u32>() سيكون أفضل من التغييرات النحوية (وما يصاحب ذلك من bikeshed الذي لا نهاية له) ، خاصةً أننا يجب أن نقلل ، لا نزيد ، عدد الأشياء التي يفتحها unsafe .

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

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

يمكن دعم طريقة عامة من خلال مجموعة جديدة من السمات UncheckedFrom / UncheckedInto مع طرق unsafe fn ، والانضمام إلى From / Into و TryFrom / TryInto .

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

بعض الملاحظات حول التحويلات في مجموعة تعليمات x86:

SSE2 هو في الواقع محدود نسبيًا في عمليات التحويل التي يقدمها لك. عندك:

  • عائلة CVTTSS2SI مع سجل 32 بت: تحويل تعويم واحد إلى i32
  • عائلة CVTTSS2SI بسجل 64 بت: تحويل عدد عشري واحد إلى i64 (x86-64 فقط)
  • عائلة CVTTPS2PI: تقوم بتحويل عوامين إلى اثنين i32s

يحتوي كل من هذه المتغيرات على f32 و f64 (بالإضافة إلى المتغيرات المستديرة بدلاً من الاقتطاع ، لكن هذا غير مفيد هنا).

ولكن لا يوجد شيء للأعداد الصحيحة بدون إشارة ، ولا شيء للأحجام الأصغر من 32 ، وإذا كنت تستخدم 32 بت x86 ، فلا شيء لـ 64 بت. تضيف ملحقات مجموعة التعليمات اللاحقة المزيد من الوظائف ، ولكن يبدو أن أي شخص بالكاد يقوم بتجميعها.

ونتيجة لذلك ، فإن السلوك الحالي ("غير الآمن"):

  • للتحويل إلى u32 ، يقوم المترجمون بالتحويل إلى i64 واقتطاع العدد الصحيح الناتج. (ينتج عن هذا سلوك غريب للقيم خارج النطاق ، ولكن هذا هو UB لذلك من يهتم).
  • للتحويل إلى أي شيء 16 بت أو 8 بت ، يتحول المترجمون إلى i64 أو i32 ويقطعون العدد الصحيح الناتج.
  • للتحويل إلى u64 ، ينشئ المترجمون مجموعة من التعليمات. بالنسبة إلى f32 إلى u64 GCC و LLVM ، يُنشئ ما يعادل:
fn f32_to_u64(f: f32) -> u64 {
    const CUTOFF: f32 = 0x8000000000000000 as f32; // 2^63 exactly
    if !(f >= CUTOFF) { // less, or NaN
        // just use the signed conversion
        f as i64 as u64
    } else {
        0x8000000000000000u64 + ((f - CUTOFF) as i64 as u64)
    }
}

حقيقة مرحة غير ذات صلة: إنشاء الكود "التحويل من الاقتطاع" هو ما يسبب خلل " الأكوان المتوازية " في سوبر ماريو 64. كود اكتشاف التصادم أول تعليمات MIPS لتحويل إحداثيات f32 إلى i32 ، ثم اقتطاعها إلى i16 ؛ وبالتالي ، فإن الإحداثيات التي تتلاءم مع i16 وليس i32 "التفاف" ، على سبيل المثال الذهاب للتنسيق 65536.0 يمنحك اكتشاف التصادم لـ 0.0.

على أي حال ، الاستنتاجات:

  • يعمل "اختبار لـ 0x80000000 ولديك معالج خاص" فقط للتحويلات إلى i32 و i64.
  • بالنسبة للتحويلات إلى u32 و u / i16 و u / i8 ، فإن "اختبار ما إذا كان الإخراج المقطوع / الموسع للإشارة يختلف عن الأصل" مكافئ. (سيؤدي ذلك إلى جمع الأعداد الصحيحة التي كانت في النطاق للتحويل الأصلي ولكن خارج النطاق للنوع النهائي ، و 0x8000000000000000 ، وهو مؤشر على أن العدد كان NaN أو خارج النطاق للتحويل الأصلي.)
  • لكن ربما تكون تكلفة الفرع ومجموعة من التعليمات البرمجية الإضافية لهذه الحالة مبالغًا فيها. قد يكون من الجيد تجنب الفروع.
  • ActuallyaDeviloper الصورة minss / maxss النهج القائم ليس سيئا للغاية! الشكل الأدنى ،
minss %xmm2, %xmm1
maxss %xmm3, %xmm1
cvttss2si %rax, %xmm1

هي ثلاثة تعليمات فقط (التي لها حجم كود لائق وسرعة نقل / كمون) ولا توجد فروع.

ومع ذلك:

  • يحتاج الإصدار النقي الصدأ إلى اختبار إضافي لـ NaN. بالنسبة للتحويلات إلى 32 بت أو أصغر ، يمكن تجنب ذلك باستخدام العناصر المضمنة ، باستخدام cvttss2si 64 بت واقتطاع النتيجة. إذا لم يكن الإدخال NaN ، فإن min / max يضمن عدم تغيير العدد الصحيح بالاقتطاع. إذا كان الإدخال NaN ، فسيكون العدد الصحيح 0x8000000000000000 والذي يتم اقتطاعه إلى 0.
  • لم أقم بتضمين تكلفة تحميل 2147483647.0 و -2148473648.0 في السجلات ، عادةً ما يتم نقل واحد من الذاكرة لكل منهما.
  • بالنسبة إلى f32 ، لا يمكن تمثيل 2147483647.0 تمامًا ، لذلك لا يعمل هذا في الواقع: أنت بحاجة إلى فحص آخر. هذا يجعل الأمور أسوأ بكثير. كما سبق من f64 إلى u / i64 ، لكن f64 إلى u / i32 ليس به هذه المشكلة.

أقترح حل وسط بين النهجين:

  • بالنسبة إلى f32 / f64 إلى u / i16 و u / i8 ، و f64 إلى u / i32 ، انتقل باستخدام min / max + الاقتطاع ، على النحو الوارد أعلاه ، على سبيل المثال:
    let f = if f > 32767.0 { 32767.0 } else { f };
    let f = if f < -32768.0 { -32768.0 } else { f };
    cvttss2si(f) as i16

(بالنسبة إلى u / i16 و u / i8 ، يمكن أن يكون التحويل الأصلي إلى i32 ؛ أما بالنسبة إلى f64 إلى u / i32 ، فيجب أن يكون إلى i64.)

  • من أجل f32 / 64 إلى u32 ،
    let r = cvttss2si64(f) as u32;
    if f >= 4294967296.0 { 4294967295 } else { r }

هي مجرد تعليمات قليلة وليس لها فروع:

    cvttss2si   %xmm0, %rcx
    ucomiss .LCPI0_0(%rip), %xmm0
    movl    $-1, %eax
    cmovbl  %ecx, %eax
  • من أجل f32 / 64 إلى i64 ، ربما
    let r = cvttss2si64(f);
    if f >= 9223372036854775808. {
        9223372036854775807 
    } else if f != f {
        0
    } else {
        r
    }

ينتج عن هذا تسلسل أطول (لا يزال غير متفرع):

    cvttss2si   %xmm0, %rax
    xorl    %ecx, %ecx
    ucomiss %xmm0, %xmm0
    cmovnpq %rax, %rcx
    ucomiss .LCPI0_0(%rip), %xmm0
    movabsq $9223372036854775807, %rax
    cmovbq  %rcx, %rax

… لكننا على الأقل نحفظ مقارنة واحدة مقارنة بالنهج البسيط ، كما لو أن f صغير جدًا ، فإن 0x8000000000000000 هي الإجابة الصحيحة بالفعل (مثل i64 :: MIN).

  • بالنسبة إلى f32 إلى i32 ، لست متأكدًا مما إذا كان من الأفضل أن تفعل الشيء نفسه كما في السابق ، أو مجرد التحويل إلى f64 أولاً ثم القيام بأقصر قيمة min / max.

  • u64 عبارة عن فوضى لا أرغب في التفكير فيها. : ص

في https://internals.rust-lang.org/t/help-us-benchmark-saturating-float-casts/6231/14 أبلغ شخص ما عن تباطؤ كبير وقابل للقياس في ترميز JPEG مع صندوق الصورة. لقد قمت بتصغير البرنامج بحيث يكون مكتفيًا ذاتيًا ويركز في الغالب على الأجزاء المرتبطة بالتباطؤ: https://gist.github.com/rkruppe/4e7972a209f74654ebd872eb4bc57722 (يُظهر هذا البرنامج تباطؤًا بنسبة 15٪ تقريبًا بالنسبة لي مع التشبع يلقي).

لاحظ أن القوالب هي f32-> u8 ( rgb_to_ycbcr ) و f32-> i32 ( encode_rgb ، حلقة "Quantization") بنسب متساوية. يبدو أيضًا أن جميع المدخلات في النطاق ، أي أن التشبع لا يبدأ فعليًا أبدًا ، ولكن في حالة f32-> u8 ، لا يمكن التحقق من ذلك إلا عن طريق حساب الحد الأدنى والحد الأقصى لكثير الحدود ومحاسبة خطأ التقريب أطلب الكثير. من الواضح أن قوالب f32-> i32 في النطاق لـ i32 ، ولكن فقط لأن عناصر self.tables ليست صفرية ، وهو (على ما يبدو؟) ليس من السهل على المُحسِّن إظهاره ، خاصة في البرنامج الأصلي. tl ؛ dr: فحوصات التشبع موجودة لتبقى ، والأمل الوحيد هو جعلها أسرع.

لقد قمت أيضًا بالضغط على LLVM IR البعض - يبدو أن الاختلاف الوحيد هو المقارنات والاختيار من القوالب المشبعة. نظرة سريعة تشير إلى أن ASM لديها تعليمات مقابلة وبالطبع مجموعة من القيم الحية (مما يؤدي إلى المزيد من الانسكابات).

comex هل تعتقد أنه يمكن جعل f32-> u8 و f32-> i32 casts أسرع بشكل ملحوظ باستخدام CVTTSS2SI؟

تحديث طفيف ، اعتبارًا من rustc 1.28.0-nightly (952f344cd 2018-05-18) ، لا يزال العلم -Zsaturating-float-casts يتسبب في أن يكون الرمز في https://github.com/rust-lang/rust/issues/10184#issuecomment -345479698 ~ 20 ٪ أبطأ في x86_64. مما يعني أن LLVM 6 لم تغير أي شيء.

| أعلام | التوقيت |
| ------- | -------: |
| -مستوى الخيار = 3 -Ctarget-cpu = أصلي | 325،699 نانو ثانية / iter (+/- 7،607) |
| -مختبر-المستوى = 3 -Ctarget-cpu = أصلي -Zsaturating-Float-casts | 386،962 نانوثانية / مكرر (+/- 11،601)
(أبطأ بنسبة 19٪) |
| -مستوى الخيار = 3 | 331،521 نانو ثانية / مكرر (+/- 14،096) |
| -مختبر-المستوى = 3-زاتورات-تعويم-يلقي | 413.572 نانو ثانية / مكرر (+/- 19183)
(أبطأ بنسبة 25٪) |

@ kennytm هل توقعنا أن تغير LLVM 6 شيئًا ما؟ هل يناقشون تحسينًا معينًا من شأنه أن يفيد حالة الاستخدام هذه؟ إذا كان الأمر كذلك ، ما هو رقم التذكرة؟

insanitybit ... يبدو أنه لا يزال مفتوحًا ...؟

image

ويلب ، لا يوجد دليل على ما كنت أنظر إليه. شكر!

rkruppe لم نضمن أن الطفو إلى int casts لم يعد UB في LLVM
(عن طريق تغيير المستندات)؟

في 20 تموز (يوليو) 2018 ، الساعة 4:31 صباحًا ، كتب "Colin" [email protected] :

ويلب ، لا يوجد دليل على ما كنت أنظر إليه.

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/10184#issuecomment-406462053 ،
أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AApc0v3rJHhZMD7Kv7RC8xkGOiIhkGB1ks5uITMHgaJpZM4BJ45C
.

nagisa ربما تفكر في f32::from_bits(v: u32) -> f32 (وبالمثل f64 )؟ اعتاد القيام ببعض التطبيع لـ NaNs ولكنه الآن transmute .

تدور هذه المشكلة حول تحويلات as والتي تحاول تقريب القيمة العددية.

nagisa قد تفكر في تعويم -> قوالب تعويم ، راجع # 15536 ​​و https://github.com/rust-lang-nursery/nomicon/pull/65.

آه ، نعم ، كان ذلك يطفو على الماء.

في الجمعة ، 20 يوليو ، 2018 ، 12:24 كتب Robin Kruppe [email protected] :

nagisa https://github.com/nagisa قد تفكر في تعويم-> تعويم
casts ، راجع # 15536 https://github.com/rust-lang/rust/issues/15536 and
حضانة رست لانج / نومكون # 65
https://github.com/rust-lang-nursery/nomicon/pull/65 .

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/10184#issuecomment-406542903 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AApc0gA24Hz8ndnYhRXCyacd3HdUSZjYks5uIaHegaJpZM4BJ45C
.

تذكر ملاحظات إصدار LLVM 7 شيئًا ما:

تم تحسين تحسين يلقي النقطة العائمة. قد يتسبب هذا في نتائج مفاجئة للرمز الذي يعتمد على السلوك غير المحدد للقوالب الفائضة. يمكن تعطيل التحسين من خلال تحديد سمة دالة: "strict-float-cast-overflow" = "false". يمكن إنشاء هذه السمة بواسطة خيار الرنة -fno-Strict-float-cast-overflow. يمكن استخدام معقمات التعليمات البرمجية لاكتشاف الأنماط المتأثرة. خيار الرنة لاكتشاف هذه المشكلة بمفرده هو -fsanitize = float-cast-overflow:

هل هذا له أي تأثير على هذه القضية؟

لا ينبغي أن نهتم بما تفعله LLVM لتفيض القوالب ، طالما أنها ليست سلوكًا غير آمن وغير محدد. يمكن أن تكون النتيجة هراء طالما أنها لا تسبب سلوكًا غير سليم.

هل هذا له أي تأثير على هذه القضية؟

ليس صحيحا. لم يتغير UB ، وأصبح LLVM أكثر عدوانية بشأن استغلاله ، مما يجعل من السهل التأثر به في الممارسة العملية ، لكن مشكلة السلامة لم تتغير. على وجه الخصوص، سمة جديدة لا يزيل UB أو يؤثر على أي تحسينات التي كانت موجودة قبل LLVM 7.

rkruppe بدافع الفضول ، هل سقط هذا النوع على جانب الطريق؟ يبدو أن https://internals.rust-lang.org/t/help-us-benchmark-saturating-float-casts/6231/14 سار بشكل جيد بما فيه الكفاية ولم يكن للتطبيق الكثير من الأخطاء. يبدو أنه كان من المتوقع دائمًا حدوث تراجع طفيف في الأداء ، لكن التجميع الصحيح يبدو وكأنه مقايضة جديرة بالاهتمام.

هل هذا فقط ينتظر أن يتم دفعه عبر خط النهاية؟ أم أن هناك حاصرات أخرى معروفة؟

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

حسنًا ، شكرًا رائعًا على التحديث @ rkruppe! أشعر بالفضول على الرغم من أن هناك بالفعل تطبيقًا لخيار القمامة الآمنة؟ يمكنني أن أتخيل بسهولة أننا نقدم شيئًا مثل unsafe fn i32::unchecked_from_f32(...) وما شابه ، لكن يبدو أنك تفكر في أنه يجب أن تكون وظيفة آمنة . هل هذا ممكن مع LLVM اليوم؟

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

وظيفة unsafe التي تحافظ على UB التي تدور حولها هذه المشكلة (ويتم ترميزها بنفس طريقة as اليوم) هي خيار آخر ، ولكنها أقل جاذبية بكثير ، أنا ' د تفضل وظيفة آمنة إذا كان يمكنها إنجاز المهمة.

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

     cvttsd2si %xmm0, %eax   # x86's cvttsd2si returns 0x80000000 on overflow and invalid cases
     cmp $1, %eax            # a compact way to test whether %eax is equal to 0x80000000
     jno ok
     ...  # slow path: check for and handle overflow and invalid cases
ok:

والذي يجب أن يكون أسرع بكثير مما يفعله rustc حاليًا .

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

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

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

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

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

لقد بدأت بعض العمل لتنفيذ العناصر الجوهرية للتشبع بالتعويم إلى int casts في LLVM: https://reviews.llvm.org/D54749

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

كيف يمكن للمرء إعادة إنتاج هذا السلوك غير المحدد؟ جربت المثال الموجود في التعليق ولكن النتيجة كانت 255 ، والتي تبدو جيدة بالنسبة لي:

println!("{}", 1.04E+17 as u8);

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

باختصار ، يُسمح لمحرك إنشاء الكود (LLVM) الذي نستخدمه بافتراض عدم حدوث ذلك ، وبالتالي قد يُنشئ كودًا سيئًا إذا اعتمد على هذا الافتراض.

@ AaronM04 تم نشر مثال على السلوك غير المحدد على موقع reddit اليوم:

fn main() {
    let a = 360.0f32;
    println!("{}", a as u8);

    let a = 360.0f32 as u8;
    println!("{}", a);

    println!("{}", 360.0f32 as u8);
}

(انظر الملعب )

أفترض أن التعليق الأخير كان مخصصًا لـ تعليقهم السابق .

"أوه ، هذا سهل بما فيه الكفاية إذن."

  • pcwalton ، 2014

عذرًا ، لقد قرأت بعناية كل تاريخ النوايا الحسنة هذا الممتد على مدى 6 سنوات. لكن بجدية ، 6 سنوات طويلة من أصل 10 !!! لو كان منتدى سياسيًا لكان المرء يتوقع بعض التخريب الملتهب هنا.

لذا ، من فضلك ، هل يمكن لأي شخص أن يشرح ، بكلمات بسيطة ، ما الذي يجعل عملية البحث عن حل أكثر إثارة للاهتمام من الحل نفسه؟

لأنه أصعب مما بدا في البداية ويحتاج إلى تغييرات في LLVM.

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

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

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

أعتقد أن افتراض أن هذا يتطلب تغييرات في LLVM سابق لأوانه.

أعتقد أنه يمكننا القيام بذلك باللغة بأقل تكلفة أداء. هل سيكون تغييرًا مفاجئًا * * نعم ولكن يمكن القيام به ، ويجب القيام به.

سيكون الحل الخاص بي هو تحديد float to int casts كـ unsafe ثم تقديم بعض الوظائف المساعدة في lib القياسي لتوفير نتائج مرتبطة بـ Result Types.

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

شكرا لك ، RalfJung ،

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

لا يمكن إضافة شيء أكثر من ذلك إلى هذه المناقشة.

https://reviews.llvm.org/D54749

nikic يبدو أن التقدم على جانب LLVM قد توقف ، هل يمكنك تقديم تحديث موجز إن أمكن؟ شكر.

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

https://github.com/rust-lang/rust/blob/625451e376bb2e5283fc4741caa0a3e8a2ca4d54/src/librustc_codegen_ssa/mir/rvalue.rs#L774 -L901

يمكننا الكشف عن عنصر جوهري يولد LLVM IR للتشبع (سواء كان ذلك IR الحالي مفتوح الترميز أو llvm.fpto[su]i.sat في المستقبل) بشكل مستقل عن العلامة -Z . هذا ليس بالأمر الصعب على الإطلاق.

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

في الوقت نفسه ، من الواضح أن الوضع الحالي أسوأ. إذا كنا نفكر في إضافة واجهات برمجة التطبيقات الخاصة بالمكتبة ، فأنا أحذر أكثر فأكثر من تمكين التشبع افتراضيًا وعرض عناصر مضمنة unsafe تحتوي على UB على NaN وأرقام خارج النطاق (وتقلل إلى عادي fpto[su]i ). سيظل ذلك يقدم نفس الخيار بشكل أساسي ، ولكن التقصير في السلامة ، ومن المحتمل ألا تصبح واجهة برمجة التطبيقات الجديدة زائدة عن الحاجة في المستقبل.

يبدو التبديل إلى الصوت افتراضيًا جيدًا. أعتقد أنه يمكننا تقديم الكسل الجوهري عند الطلب بدلاً من البدء. أيضا ، هل ستحقق قيمة const تشبع في هذه الحالة؟ (ج جRalfJungeddyb @ اولى-obk)

قام Const بتقييمنا بالفعل وهو يقوم بالتشبع وقد قام بذلك على مدى الأعمار ، أعتقد أنه حتى قبل miri (أتذكر بوضوح تغييره في المقيِّم القديم llvm::Constant القائم على التقييم).

تضمين التغريدة نظرًا لأنك معتاد على الكود المعني ، هل ترغب في تبديل الإعدادات الافتراضية؟

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

يمكننا الكشف عن الجوهر الذي يولد LLVM IR للتشبع

قد يحتاج هذا إلى 10 أو 12 جوهرًا منفصلاً لكل مجموعة من نوع المصدر والوجهة.

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

يبدو التبديل إلى الصوت افتراضيًا جيدًا. أعتقد أنه يمكننا تقديم الكسل الجوهري عند الطلب بدلاً من البدء.

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

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

أفضل كثيرًا أن نتبع النمط الذي تم تحديده بالفعل لتحذيرات الإيقاف: فقط قم بإجراء التبديل في Nightly بعد أن يكون البديل متاحًا في Stable لبعض الوقت.

قد يحتاج هذا إلى 10 أو 12 جوهرًا منفصلاً لكل مجموعة من نوع المصدر والوجهة.

حسنًا ، لقد فهمتني ، لكنني لا أرى مدى صلة ذلك؟ فليكن 30 جوهرًا ، فلا يزال من التافه إضافتها. لكن في الواقع ، من الأسهل أن يكون لديك عنصر جوهري عام واحد تستخدمه أغلفة N الرقيقة. لا يتغير الرقم أيضًا إذا اخترنا خيار "جعل الصوت as وتقديم unsafe cast API".

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

+1

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

يمكننا أيضًا ترك -Zsaturating-float-casts قريبًا (فقط تغيير الافتراضي) مما يعني أنه لا يزال بإمكان أي مستخدم ليلاً إلغاء الاشتراك في العلبة لفترة من الوقت.

(نعم ، فإن عدد العناصر الجوهرية هو مجرد تفاصيل تنفيذ ولم يُقصد به أن يكون حجة مع أو ضد أي شيء)

rkruppe أنا لا يمكن أن يدعي أن يهضم كل التعليقات هنا، ولكن أنا تحت انطباع بأن LLVM الآن لديها تعليمات التجميد، الذي كان البند تسد "أقصر طريق" للقضاء على UB هنا، أليس كذلك؟

على الرغم من أنني أعتقد أن freeze جديد جدًا لدرجة أنه قد لا يكون متاحًا في نسختنا الخاصة من LLVM ، أليس كذلك؟ ومع ذلك ، يبدو أنه شيء يجب أن نستكشف التنمية فيه ، ربما خلال النصف الأول من عام 2020؟

الترشيح للمناقشة في اجتماع T-compiler ، لمحاولة الحصول على إجماع تقريبي حول المسار المطلوب في هذه المرحلة.

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

وعلى أي حال ، حتى إعادة القمامة العشوائية يبدو أمرًا سيئًا إلى حد ما بالنسبة إلى فريق as . من المنطقي أن يكون لديك عمليات أسرع للسرعة عند الحاجة ، على غرار unchecked_add ، لكن جعل هذا الإعداد الافتراضي يبدو بقوة ضد روح Rust.

SimonSapin لقد اقترحت النهج المعاكس أولاً (افتراضيًا إلى دلالات غير سليمة / "غريبة" وتقديم طريقة سليمة بشكل واضح) ؛ لا يمكنني القول من تعليقاتك اللاحقة ما إذا كنت تعتقد أن التقصير في السلامة (بعد فترة انتقالية مناسبة) هو أيضًا معقول / أفضل؟

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

لدي انطباع بأن LLVM لديها الآن تعليمات تجميد ، والتي كانت العنصر الذي يحجب "أقصر طريق" للقضاء على UB هنا ، أليس كذلك؟

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

ثانيًا ، بالطبع ، السؤال عما إذا كان "ليس فقط UB" هو كل ما نهتم به أثناء وجودنا فيه. على وجه الخصوص ، أريد أن أبرز مرة أخرى أن freeze(fptosi %x) يتصرف بشكل مضاد للحدس: إنه غير حتمي ويمكن أن يعيد نتيجة مختلفة (حتى واحدة مأخوذة من ذاكرة حساسة كما قال freeze -using) الخيار غير الافتراضي.

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

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

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

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

متفق عليه. لكن هذا لا يساعدنا حقًا في حل هذه المشكلة.

(أيضًا ، أنا سعيد لأنك تعمل على جعل as غير مطلوب. أتطلع إلى ذلك.: D)

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

لذلك يبدو أننا نتفق على أن الحالة النهائية يجب أن تكون تلك القيمة العائمة إلى int as المشبعة؟ أنا سعيد بأي خطة انتقالية طالما أن هذا هو الهدف النهائي الذي نتجه نحوه.

هذا الهدف النهائي يبدو جيدًا بالنسبة لي.

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

من وجهة نظري ، لن تكون نهاية العالم إذا انتظر هؤلاء المستخدمون ترقية rustc الخاصة بهم لتلك الأسابيع 6-12 - فقد لا يحتاجون إلى أي شيء من الإصدارات القادمة في كلتا الحالتين ، أو قد يكون لدى مكتباتهم قيود MSRV التمسك.

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

أفضل كثيرًا أن نتبع النمط الذي تم تحديده بالفعل لتحذيرات الإيقاف: فقط قم بإجراء التبديل في Nightly بعد أن يكون البديل متاحًا في Stable لبعض الوقت.

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

حسنًا ، لقد فهمتني ، لكنني لا أرى مدى صلة ذلك؟ فليكن 30 جوهرًا ، فلا يزال من التافه إضافتها. لكن في الواقع ، من الأسهل أن يكون لديك عنصر جوهري عام واحد تستخدمه أغلفة N الرقيقة. لا يتغير الرقم أيضًا إذا اخترنا خيار "جعل الصوت as وتقديم unsafe cast API".

ألن يتطلب هذا الجوهر العام الواحد تطبيقات منفصلة في المترجم لتلك 12/30 إنشاءات أحادية الشكل محددة؟

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

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

يمكننا أيضًا ترك -Zsaturating-float-casts قريبًا (فقط تغيير الافتراضي) مما يعني أنه لا يزال بإمكان أي مستخدم ليلاً إلغاء الاشتراك في العلبة لفترة من الوقت.

يبدو هذا جيدًا بالنسبة لي ، لكنني أقترح إعادة تسمية العلامة إلى -Zunsaturating-float-casts لتجنب تغيير الدلالات نحو عدم السلامة لأولئك الذين يستخدمون هذه العلامة بالفعل.

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

ألن يتطلب هذا الجوهر العام الواحد تطبيقات منفصلة في المترجم لتلك 12/30 إنشاءات أحادية الشكل محددة؟

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

(تحرير: لكي نكون واضحين ، يمكن أن تحدث هذه المشاركة حتى لو كان هناك جوهر N مميزًا ، لكن جوهر واحد عام يخفض من النموذج المعياري المطلوب لكل جوهر.)

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

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

لدينا بالفعل بعض أرقام المقارنة. نعلم من الدعوة للمعايير منذ فترة طويلة أن ترميز JPEG يصبح أبطأ بشكل ملحوظ على x86_64 مع القوالب المشبعة. يمكن لشخص ما إعادة تشغيلها ولكني أشعر بالثقة في توقع أن هذا لم يتغير (على الرغم من أن الأرقام المحددة لن تكون متطابقة بالطبع) ولا أرى أي سبب لتغييرات مستقبلية في كيفية تنفيذ التشبع (مثل التبديل إلى inline asm أو جوهرات LLVM التي عملت unsafe أو شيء باستخدام freeze .

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

  1. في نفس الوقت:

    • قدم العناصر الجوهرية المعروضة ليلاً من خلال وظائف #[unstable(...)] .

    • أزل -Zsaturating-float-casts وأدخل -Zunsaturating-float-casts .

    • قم بتبديل الإعداد الافتراضي إلى ما يفعله -Zsaturating-float-casts .

  2. نقوم بتثبيت الجوهرات بعد مرور بعض الوقت ؛ يمكننا التعقب قليلاً.
  3. قم بإزالة -Zunsaturating-float-casts بعد فترة.

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

  • طرق السمة العامة (مع معلمة لنوع الإرجاع الصحيح للتحويل) ، اختياريًا في المقدمة
  • طرق متأصلة ذات سمة داعمة (مثل str::parse و FromStr ) من أجل دعم أنواع الإرجاع المختلفة
  • طرق متأصلة متعددة غير عامة مع نوع الهدف في الاسم

نعم ، قصدت فضح الجوهرات عبر طرق أو بعض هذه الطرق.

طرق متأصلة متعددة غير عامة مع نوع الهدف في الاسم

يبدو هذا وكأنه الشيء المعتاد الذي نقوم به - أي اعتراضات على هذا الخيار؟

غير أنه على الرغم من؟ أشعر أنه عندما يكون هناك اسم لنوع (من التوقيع) كجزء من اسم طريقة ، فإنها تحويلات مخصصة "فريدة من نوعها" (مثل Vec::as_slice و [T]::to_vec ) ، أو سلسلة من التحويلات لا يكون الاختلاف فيها من النوع (مثل to_ne_bytes ، to_be_bytes ، to_le_bytes ). ولكن جزءًا من الدافع وراء سمات std::convert هو تجنب عشرات الطرق المنفصلة مثل u8::to_u16 ، u8::to_u32 ، u8::to_u64 ، إلخ.

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

يبدو غريباً بالنسبة لي أن أضيف سمات للتحويلات غير الآمنة ، لكن أعتقد أن سيمون ربما يفكر في حقيقة أننا ربما نحتاج إلى طريقة مختلفة لكل مجموعة من الفاصلة العائمة ونوع العدد الصحيح (على سبيل المثال f32::to_u8_unsaturated ، f32::to_u16_unsaturated ، إلخ).

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

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

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

لاحظ أنه حتى استبعاد الأعداد الصحيحة 128 بت والتركيز على الأعداد الصحيحة 64 بت سيظل سيئًا لأهداف 32 بت الشائعة.

أنا جديد في هذه المحادثة ولكن ليس في البرمجة. أشعر بالفضول لمعرفة سبب اعتقاد الناس بأن تشبع التحويلات وتحويل NaN إلى صفر سلوك افتراضي معقول. أتفهم أن Java تقوم بذلك (على الرغم من أن الالتفاف يبدو أكثر شيوعًا) ، ولكن لا توجد قيمة عددية يمكن القول أن NaN لها تحويل صحيح. وبالمثل ، فإن تحويل 1000000.0 إلى 65535 (u16) ، على سبيل المثال ، يبدو خطأ. ببساطة لا يوجد u16 هذا هو الجواب الصحيح بوضوح. على الأقل ، لا أرى أنه أفضل من السلوك الحالي لتحويله إلى 16960 ، وهو على الأقل سلوك مشترك مع C / C ++ و C # و go وغيرها ، وبالتالي على الأقل غير مفاجئ إلى حد ما.

علق العديد من الأشخاص على التشابه مع فحص الفائض ، وأنا أتفق معهم. إنه مشابه أيضًا لقسمة الأعداد الصحيحة على صفر. أعتقد أن التحويلات غير الصالحة يجب أن تنذر بالذعر مثل الحسابات غير الصحيحة. يبدو الاعتماد على NaN -> 0 و 1000000.0 -> 65535 (أو 16960) عرضة للخطأ تمامًا مثل الاعتماد على تجاوز عدد صحيح أو افتراضي n / 0 == 0. إنه نوع الشيء الذي يجب أن ينتج عنه خطأ افتراضيًا. (في تصميمات الإصدار ، يمكن أن يتجاهل الصدأ التحقق من الأخطاء ، تمامًا كما هو الحال مع الحساب الصحيح.) وفي الحالات النادرة عندما تريد _ تحويل NaN إلى صفر أو تشبع النقطة العائمة ، يجب عليك الاشتراك فيه ، تمامًا مثل يجب عليك الاشتراك في تجاوز عدد صحيح.

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

admilazz نحن مقيدون بما يمكن أن تفعله LLVM ، ولا تعرض LLVM حاليًا طريقة لتحويل العوامات بكفاءة إلى أعداد صحيحة دون التعرض لخطر السلوك غير المحدد.

سبب التشبع هو أن اللغة تحدد as casts لتحقيق النجاح دائمًا ، وبالتالي لا يمكننا تغيير عامل التشغيل إلى الذعر بدلاً من ذلك.

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

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

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

fn main() {
    dbg!(1000000.0 as u16);
}

ملعب . مثال الإخراج:

[src/main.rs:2] 1000000.0 as u16 = 49072

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

أعتقد أنه يجب علينا أيضًا إضافة واجهات برمجة تطبيقات تحويل غير معصومة تعيد Result ، لكن ما زلت بحاجة إلى إنهاء كتابة هذه المسودة قبل RFC :)

و"تحويل إلى عدد صحيح الرياضي، ثم باقتطاع لعرض الهدف" أو دلالات "ملفوف" وقد اقترحت من قبل في هذا الموضوع (https://github.com/rust-lang/rust/issues/10184#issuecomment-299229143). لا أحبه بشكل خاص:

  • أعتقد أنه أقل منطقية من التشبع. لا يعطي التشبع بشكل عام نتائج معقولة للأرقام البعيدة عن النطاق ، ولكن:

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

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

  • AFAIK السبب الوحيد لتفضيل الدلالات الملفوفة هو فعالية محاكاة البرامج ، لكن هذا يبدو وكأنه افتراض غير مثبت بالنسبة لي. سأكون سعيدًا لأن أكون مخطئًا ولكن في لمحة خاطفة يبدو أن الملف الملتف يتطلب سلسلة طويلة من تعليمات ALU (بالإضافة إلى الفروع للتعامل مع اللانهايات و NaNs بشكل منفصل) لدرجة أنني لا أشعر أنه من الواضح أن المرء سيكون أفضل من الواضح أداء من الآخر.
  • في حين أن السؤال عما يجب فعله مقابل NaN يمثل مشكلة قبيحة لأي تحويل إلى عدد صحيح ، فإن التشبع على الأقل لا يتطلب أي غلاف خاص (لا في الدلالات ولا في معظم التطبيقات) لما لا نهاية. ولكن في حالة الالتفاف ، ما هو العدد الصحيح المكافئ الذي يفترض أن يكون +/- ما لا نهاية؟ تقول JavaScript إنها 0 ، وأعتقد أنه إذا كنا قد تسببنا في ذعر as على NaN ، فقد يؤدي ذلك أيضًا إلى الذعر اللانهائي ، ولكن في كلتا الحالتين يبدو أنه سيجعل الالتفاف أكثر صعوبة من النظر إلى الأرقام العادية وغير العادية وحده سيقترح.

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

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

يضيف https://github.com/rust-lang/rust/pull/66841 طرق unsafe fn التي يتم تحويلها باستخدام LLVM fptoui و fptosi ، للحالات التي تُعرف فيها القيم أن تكون في النطاق والتشبع هو تراجع أداء قابل للقياس

بعد ذلك ، أعتقد أنه من الجيد تبديل الإعداد الافتراضي لـ as (وربما إضافة علامة -Z لإلغاء الاشتراك؟) ، على الرغم من أن هذا ربما يكون قرارًا رسميًا لفريق Lang.

بعد ذلك ، أعتقد أنه من الجيد تبديل الإعداد الافتراضي لـ as (وربما إضافة علامة -Z لإلغاء الاشتراك؟) ، على الرغم من أن هذا ربما يكون قرارًا رسميًا لفريق Lang.

لذلك نحن (فريق اللغة ، مع الأشخاص الموجودين هناك على الأقل) ناقشنا هذا في https://github.com/rust-lang/lang-team/blob/master/minutes/2019-11-21.md وفكرنا ستكون إضافة عناصر داخلية جديدة + إضافة -Zunsaturated-float-casts خطوات أولى جيدة.

أعتقد أنه سيكون من الجيد تبديل الإعداد الافتراضي كجزء من ذلك أو بعد فترة وجيزة ، ربما باستخدام FCP إذا لزم الأمر.

أفترض أنه من خلال عناصر داخلية جديدة ، فإنك تعني شيئًا مثل https://github.com/rust-lang/rust/pull/66841

ماذا يعني إضافة -Z unsaturated-float-casts بدون تغيير الافتراضي؟ هل تقبلها على أنها no-op بدلاً من إرسال "خطأ: خيار تصحيح أخطاء غير معروف"؟

أفترض أنه من خلال العناصر الداخلية الجديدة ، فإنك تعني شيئًا مثل # 66841

نعم 👍 - شكرا لقيادة ذلك.

ماذا يعني إضافة -Z unsaturated-float-casts بدون تغيير الافتراضي؟ هل تقبلها على أنها no-op بدلاً من إرسال "خطأ: خيار تصحيح أخطاء غير معروف"؟

نعم في الأساس. بدلاً من ذلك ، قمنا بإزالة -Z saturated-float-casts لصالح -Z unsaturated-float-casts وقمنا بتبديل الافتراضي مباشرةً ، ولكن يجب أن يؤدي ذلك إلى نفس النتيجة على عدد أقل من العلاقات العامة.

أنا حقًا لا أفهم الاقتراح "غير المشبع". إذا كان الهدف هو مجرد توفير مفتاح لإلغاء الاشتراك في الإعداد الافتراضي الجديد ، فمن الأسهل تغيير الإعداد الافتراضي للعلامة الحالية وعدم القيام بأي شيء آخر. إذا كان الهدف هو اختيار اسم جديد يكون أكثر وضوحًا بشأن المقايضة (عدم السلامة) ، فإن كلمة "غير مشبعة" تكون سيئة في ذلك - سأقترح بدلاً من ذلك اسمًا يتضمن "غير آمن" أو "UB" أو اسم مشابه كلمة مخيفة ، على سبيل المثال -Z fix-float-cast-ub .

unchecked هو المصطلح الذي له سابقة في أسماء API.

admilazz نحن مقيدون بما يمكن أن تفعله LLVM ، ولا تعرض LLVM حاليًا طريقة لتحويل العوامات بكفاءة إلى أعداد صحيحة دون التعرض لخطر السلوك غير المحدد.

ولكن من المفترض أنه يمكنك إضافة فحوصات وقت التشغيل فقط في عمليات إنشاء التصحيح ، كما تفعل مع تجاوز عدد صحيح.

AFAIK السبب الوحيد لتفضيل الدلالات الملفوفة هو فعالية محاكاة البرامج

لا أعتقد أننا يجب أن نفضل إما الالتفاف أو التشبع ، لأن كلاهما خاطئ ، لكن الالتفاف على الأقل له فائدة كونه الطريقة المستخدمة من قبل العديد من اللغات المشابهة للصدأ: C / C ++ ، C # ، go ، ربما D ، وبالتأكيد أكثر ، وكذلك سلوك الصدأ الحالي (على الأقل في بعض الأحيان). ومع ذلك ، أعتقد أن "الذعر من التحويلات غير الصالحة (ربما في عمليات إنشاء التصحيح فقط)" مثالي ، تمامًا كما نفعل مع تجاوز عدد صحيح وحساب غير صالح مثل القسمة على صفر.

(من المثير للاهتمام ، أنني حصلت على 16960 في الملعب . لكني أرى من الأمثلة الأخرى المنشورة أن الصدأ أحيانًا يفعل ذلك بشكل مختلف ...)

يرجع التشبع إلى أن اللغة تُعرّف على أنها قوالب للنجاح دائمًا ، وبالتالي لا يمكننا تغيير المشغل إلى الذعر بدلاً من ذلك.

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

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

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

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

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

ومن المثير للاهتمام أنني حصلت على 16960 في الملعب.

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

الملفوف على الأقل له فائدة كونه الطريقة المستخدمة من قبل العديد من اللغات المشابهة للصدأ: C / C ++ ، C # ، go ، ربما D ، وبالتأكيد أكثر ،

هل هو حقا؟ على الأقل ليس في C و C ++ ، لديهم نفس السلوك غير المحدد مثل Rust. هذه ليست مصادفة ، نحن نستخدم LLVM المصمم بشكل أساسي لتطبيق clang لتطبيق C و C ++. هل أنت متأكد من C # وتذهب؟

معيار C11 https://port70.net/~nsz/c/c11/n1570.html#6.3.1.4

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

لا يلزم تنفيذ العملية المتبقية التي يتم إجراؤها عندما يتم تحويل قيمة نوع عدد صحيح إلى نوع غير موقعة عندما يتم تحويل قيمة من نوع عائم حقيقي إلى نوع غير موقع. وبالتالي ، فإن نطاق القيم العائمة الحقيقية المحمولة هو (-1 ، Utype_MAX + 1).

معيار C ++ 17 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf#section.7.10

يمكن تحويل prvalue من نوع النقطة العائمة إلى prvalue من نوع عدد صحيح. يتم اقتطاع التحويل أي يتم تجاهل الجزء الكسري. يتم تعريف السلوك إذا تعذر تمثيل القيمة المقتطعة في نوع الوجهة.

C # مرجع https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/numeric-conversions

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

لذلك فهي ليست UB ، فقط "قيمة غير محددة".

admilazz هناك فرق كبير بين هذا وتجاوز عدد صحيح: تجاوز عدد صحيح غير مرغوب فيه ولكنه محدد جيدًا . يلقي النقطة العائمة سلوك غير محدد .

ما تطلبه مشابه لإيقاف تشغيل فحص الحدود Vec في وضع الإصدار ، ولكن هذا سيكون خطأ لأن ذلك سيسمح بسلوك غير محدد.

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

بالطبع من الممكن أن يكون لديك إصلاح أكثر تقييدًا في وضع التصحيح ، ولكن يجب أن يظل الإصلاح الخاص بوضع الإصدار محددًا جيدًا.

admilazz هناك فرق كبير بين هذا وتجاوز عدد صحيح: تجاوز عدد صحيح غير مرغوب فيه ولكنه محدد جيدًا. يلقي النقطة العائمة سلوك غير محدد.

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

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

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

هل هو حقا؟ على الأقل ليس في C و C ++ ، لديهم نفس السلوك غير المحدد مثل Rust ... هل أنت متأكد من C # وتذهب؟

عادل بما يكفي. أنا لم أقرأ كل المواصفات الخاصة بهم. لقد اختبرت للتو مجمعين مختلفين. أنت محق في أن قول "كل المترجمين الذين حاولت القيام بذلك بهذه الطريقة" يختلف عن قول "مواصفات اللغة تحددها على هذا النحو". لكنني لا أجادل لصالح الفائض على أي حال ، فقط أشير إلى أنه يبدو الأكثر شيوعًا. أنا أتجادل حقًا لصالح إجراء تحويل 1) يحمي من النتائج "الخاطئة" مثل 1000000.0 ليصبح 65535 أو 16960 لنفس السبب الذي نحمي به من تجاوز عدد صحيح - وهو على الأرجح خطأ لذا يجب على المستخدمين الاشتراك فيه ، و 2) يسمح بأقصى أداء في تصميمات الإصدار.

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

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

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

يهمني القول أن كل سلوك غير معرف هو على حد سواء على هذا الأساس.

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

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

إذا كانت الوسيطة undef أو سمًا ، فإن "التجميد" ترجع قيمة عشوائية ، ولكنها ثابتة ، من النوع "ty". خلاف ذلك ، هذه التعليمات هي no-op وتعيد وسيطة الإدخال. جميع استخدامات القيمة التي يتم إرجاعها من خلال تعليمات "التجميد" نفسها مضمونة لمراقبة نفس القيمة دائمًا ، بينما قد تؤدي تعليمات "التجميد" المختلفة إلى قيم مختلفة.

لا أعرف ماذا يقصدون ب "القيمة الثابتة" أو "نفس تعليمات" التجميد ". أعتقد أنه من الناحية المثالية سيتم تجميعه إلى no-op وإعطاء عدد صحيح غير متوقع ، ولكن يبدو أنه قد يؤدي إلى شيء مكلف. هل جرب أحد عملية التجميد هذه؟

حسنًا ، من المؤسف أن تصطدم LLVM بتصميم الصدأ بهذه الطريقة

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

لن أختلف معك هناك. :-) آمل أن توفر تعليمات LLVM "التجميد" هذه طريقة بدون تكلفة لتجنب هذا السلوك غير المحدد.

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

IMO مثل هذه الدلالات ستكون تصميم لغة سيئة نفضل تجنبها.

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

انتهيت من هذه المسودة! https://internals.rust-lang.org/t/pre-rfc-add-explicitly- named-numeric-conversion-apis/11395

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

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

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

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

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

نحن لا نتحدث عن الضرب ، لكننا نتحدث عن القوالب. ونعم ، الأمر نفسه ينطبق على تجاوز عدد صحيح: يلقي int-to-int لا داعي للذعر ، حتى عندما يفيض. هذا لأن as ، حسب التصميم ، لا داعي للذعر ، ولا حتى في تصميمات التصحيح. يعد الانحراف عن هذا بالنسبة لقوالب النقطة العائمة مفاجئًا في أحسن الأحوال وخطيرًا في أسوأ الأحوال ، حيث يمكن أن تعتمد الصحة والسلامة على الكود غير الآمن على عمليات معينة لا تسبب الذعر.

إذا كنت تريد أن تجادل بأن تصميم as معيب لأنه يوفر تحويلًا معصومًا بين الأنواع حيث لا يكون التحويل الصحيح ممكنًا دائمًا ، أعتقد أن معظمنا سيوافق. لكن هذا خارج نطاق هذا الموضوع تمامًا ، والذي يتعلق بإصلاح التحويلات العائمة إلى int داخل الإطار الحالي لـ as casts . يجب أن تكون هذه معصومة من الخطأ ، ولا يجب أن تصاب بالذعر ، ولا حتى في عمليات إنشاء التصحيح. لذا يرجى إما اقتراح بعض المعاني المعقولة (لا تتضمن freeze ) ، دلالات غير مقلقة للقوالب العائمة إلى int ، أو محاولة بدء مناقشة جديدة حول إعادة تصميم as للسماح بالذعر عندما يكون طاقم الممثلين مع فقدان (وقم بذلك بشكل متسق مع القوالب int-to-int و float-to-int) - لكن الأخير خارج الموضوع في هذه المشكلة ، لذا يرجى فتح سلسلة محادثات جديدة (نمط ما قبل RFC) من أجل هذا.

ماذا لو نبدأ بمجرد تنفيذ دلالات freeze الآن لإصلاح UB ، وبعد ذلك يمكننا أن نحظى طوال الوقت في العالم للاتفاق على الدلالات التي نريدها بالفعل لأن أي دلالات نختارها ستكون متوافقة مع freeze دلالات.

ماذا لو نبدأ بمجرد تنفيذ دلالات freeze _now_ لإصلاح UB ، وبعد ذلك يمكننا أن نحظى طوال الوقت في العالم للاتفاق على الدلالات التي نريدها بالفعل لأن أي دلالات نختارها ستكون متوافقة مع freeze دلالات.

  1. الذعر لا يتوافق مع التجميد ، لذلك سنحتاج على الأقل إلى رفض جميع المقترحات التي تنطوي على الذعر. من الواضح أن الانتقال من UB إلى الذعر أقل تعارضًا ، على الرغم من أنه كما تمت مناقشته أعلاه ، هناك بعض الأسباب الأخرى لعدم جعل as ذعرًا.
  2. كما كتبت من قبل ،
    > نحن ندعم العديد من الإصدارات القديمة (عودة إلى LLVM 6 في الوقت الحالي) وسنحتاج إلى بعض التنفيذ الاحتياطي لهؤلاء للتخلص فعليًا من UB لجميع المستخدمين.

وأنا أتفق معRalfJung أن جعل فقط بعض as يلقي هلع غير مرغوب فيه للغاية، ولكن إلى جانب ذلك أنا لا أعتقد أن هذاadmilazz النقطة التي هو الصحيح الواضح:

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

بالنسبة إلى f32-> u16 ، قد يكون صحيحًا أنك تحتاج إلى خطأ تقريب كبير بشكل غير عادي للتخلي عن نطاق u16 من خطأ التقريب فقط ، ولكن للتحويلات من f32 إلى 32 بت الأعداد الصحيحة التي ليست صحيحة بشكل واضح. i32::MAX غير قابل للتمثيل بالضبط في f32 ، أقرب رقم يمكن تمثيله هو 47 من i32::MAX . لذلك إذا كان لديك عملية حسابية يجب أن ينتج عنها رقمًا يصل إلى i32::MAX ، فإن أي خطأ> = 1 ULP بعيدًا عن الصفر سيخرجك من الحدود. ويزداد الأمر سوءًا عندما نفكر في تعويم أقل دقة (IEEE 754 binary16 ، أو bfloat16 غير القياسي).

نحن لا نتحدث عن الضرب ، لكننا نتحدث عن القوالب

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

ونعم ، الأمر نفسه ينطبق على تجاوز عدد صحيح: لا تصاب بالذعر من int-to-int ، حتى عندما تفيض ... الانحراف عن هذا لإلقاء النقطة العائمة أمر مفاجئ في أحسن الأحوال وخطير في أسوأ الأحوال ، حيث أن الصحة والسلامة على الكود غير الآمن يمكن أن تعتمد على عمليات معينة لا الذعر.

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

من فضلك ... اقترح بعض الدلالات المعقولة (لا تتضمن التجميد) وغير المقلقة للقوالب العائمة إلى int

حسنًا ، اقتراحي هو:

  1. لا تستخدم مفاتيح التحويل البرمجي العالمية التي تؤثر على تغييرات كبيرة في الدلالات. (أفترض أن -Zsaturating-float-casts هي معلمة سطر أوامر أو ما شابه ذلك.) الكود الذي يعتمد على سلوك التشبع ، على سبيل المثال ، سيتم كسره إذا تم تجميعه بدونه. من المفترض أنه لا يمكن دمج التعليمات البرمجية ذات التوقعات المختلفة معًا في نفس المشروع. يجب أن تكون هناك طريقة محلية لعملية حسابية لتحديد الدلالات المرغوبة ، ربما شيء مثل هذا قبل RFC .
  2. اجعل من الممثلين as لديهم أقصى أداء افتراضيًا ، كما هو متوقع من فريق التمثيل.

    • أعتقد أن هذا يجب أن يتم عن طريق التجميد على إصدارات LLVM التي تدعمها وأي دلالات تحويل أخرى على إصدارات LLVM التي لا (مثل الاقتطاع والتشبع ، إلخ). أتوقع أن "التجميد يمكن أن يتسرب القيم من الذاكرة الحساسة" هو ادعاء افتراضي بحت (أو ، إذا ترك y = freeze(fptosi(x)) y دون تغيير ، مما أدى إلى تسرب الذاكرة غير المهيأة ، فيمكن إصلاح ذلك بمسح y أولاً.)

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

  1. لا تستخدم مفاتيح التحويل البرمجي العالمية التي تؤثر على تغييرات كبيرة في الدلالات. (أفترض -Zsaturating-float-casts هي معلمة سطر أوامر أو ما شابه ذلك.)

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

بالنسبة إلى f32-> u16 ، قد يكون صحيحًا أنك تحتاج إلى خطأ تقريب كبير بشكل غير عادي للتخلي عن نطاق u16 من خطأ التقريب فقط ، ولكن للتحويلات من f32 إلى 32 بت الأعداد الصحيحة التي ليست صحيحة بشكل واضح. i32 :: MAX لا يمكن تمثيله بالضبط في f32 ، أقرب رقم يمكن تمثيله هو 47 off من i32 :: MAX. لذلك إذا كان لديك عملية حسابية يجب أن تؤدي رياضيًا إلى رقم يصل إلى i32 :: MAX ، فإن أي خطأ> = 1 ULP بعيدًا عن الصفر سيخرجك من الحدود

لقد أصبح هذا خارج الموضوع قليلاً ، ولكن لنفترض أن لديك هذه الخوارزمية الافتراضية التي من المفترض أن تنتج رياضياً f32s حتى 2 ^ 31-1 (ولكن لا يجب أن تنتج 2 ^ 31 أو أعلى ، باستثناء ربما بسبب خطأ التقريب). يبدو أنه معيب بالفعل.

  1. أعتقد أن أقرب i32 قابل للتمثيل هو في الواقع 127 أقل من i32 :: MAX ، لذلك حتى في عالم مثالي بدون عدم دقة النقطة العائمة ، فإن الخوارزمية التي تتوقع أن تنتج قيمًا تصل إلى 2 ^ 31-1 يمكنها في الواقع إنتاج فقط (قانوني ) قيم تصل إلى 2 ^ 31-128. ربما هذا خطأ بالفعل. لست متأكدًا من أنه من المنطقي التحدث عن الخطأ المقاس من 2 ^ 31-1 عندما يتعذر تمثيل هذا الرقم. يجب أن تكون بعيدًا عن أقرب رقم يمكن تمثيله بمقدار 64 (مع مراعاة التقريب) للخروج من الحدود. من المؤكد أن هذا ليس كثيرًا من حيث النسبة المئوية عندما تكون بالقرب من 2 ^ 32.
  2. لا يجب أن تتوقع تمييزًا للقيم التي تفصل بينها 1 (أي 2 ^ 31-1 ولكن ليس 2 ^ 31) عندما تكون أقرب القيم القابلة للتمثيل 128 متباعدة. علاوة على ذلك ، يمكن تمثيل 3.5٪ فقط من i32s كـ f32 (و <2٪ من u32s). لا يمكنك الحصول على هذا النوع من النطاق بينما تتمتع أيضًا بهذا النوع من الدقة باستخدام f32. تبدو الخوارزمية وكأنها تستخدم الأداة الخاطئة للوظيفة.

أفترض أن أي خوارزمية عملية تقوم بما تصفه سوف ترتبط ارتباطًا وثيقًا بالأعداد الصحيحة بطريقة ما. على سبيل المثال ، إذا قمت بتحويل i32 عشوائيًا إلى f32 والعكس ، فقد يفشل إذا كان أعلى من i32 :: MAX-64. لكن هذا يحط من دقتك بشدة ولا أعرف لماذا تفعل مثل هذا الشيء. إلى حد كبير ، يمكن التعبير عن أي حساب i32 -> f32 -> i32 ينتج عنه نطاق i32 الكامل بشكل أسرع وأكثر دقة باستخدام الرياضيات الصحيحة ، وإذا لم يكن هناك f64.

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

PS الشكر المتأخر سعيد للجميع.

لأكون واضحًا ، لا أعتقد أن أي شخص يختلف. تم اقتراح هذا العلم فقط كأداة قصيرة المدى ...

كنت أشير في المقام الأول إلى الاقتراح باستبدال قوالب -Zsaturated-float-casts -Zunsaturated-float-casts. حتى إذا أصبح التشبع هو الوضع الافتراضي ، فإن العلامات مثل -Zunsaturated-float-casts تبدو سيئة للتوافق ، ولكن إذا كان المقصود أيضًا أن يكون مؤقتًا ، فلا بأس ، لا تهتم. :-)

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

افترضت أن -Zunsaturated-float-casts سيكون موجودًا مؤقتًا فقط ، وسيتم إزالته في وقت ما. أنه خيار -Z (متاح فقط في Nightly) بدلاً من -C يقترحه ، على الأقل.

لما يستحق ، فإن التشبع و UB ليسا الخيارين الوحيدين. الاحتمال الآخر هو تغيير LLVM لإضافة متغير fptosi يستخدم سلوك تجاوز السعة الأصلي لوحدة المعالجة المركزية - أي أن السلوك على الفائض لن يكون قابلاً للنقل عبر البنى ، ولكنه سيكون محددًا جيدًا في أي بنية معينة ( على سبيل المثال ، إرجاع 0x80000000 إلى x86) ، ولن يُعيد الذاكرة السامة أو غير المهيأة أبدًا. حتى إذا أصبح الخيار الافتراضي مشبعًا ، فسيكون من الجيد أن يكون ذلك خيارًا. بعد كل شيء ، في حين أن القوالب المشبعة لها عبء متأصل في البنى حيث لا تكون السلوك الافتراضي ، فإن "القيام بما تفعله وحدة المعالجة المركزية" لا يكون له سوى عبء إضافي إذا كان يمنع بعض تحسين المترجم المحدد. لست متأكدًا ، لكنني أظن أن أي تحسينات تم تمكينها من خلال معالجة تجاوز float-to-int مثل UB هي مكان مناسب ولا تنطبق على معظم الأكواد.

ومع ذلك ، قد تكون إحدى المشكلات هي ما إذا كانت البنية تحتوي على عدة تعليمات من نوع float-to-int تُرجع قيمًا مختلفة عند تجاوز السعة. في هذه الحالة ، سيؤثر اختيار المترجم على أحدهما أو الآخر على السلوك الذي يمكن ملاحظته ، وهي ليست مشكلة في حد ذاتها ، ولكنها قد تصبح مشكلة إذا تم تكرار fptosi وانتهى الأمر بالنسختين بشكل مختلف. لكنني لست متأكدًا مما إذا كان هذا النوع من الاختلاف موجودًا بالفعل في أي معماريات شائعة. وتنطبق نفس المشكلة على تحسينات النقطة العائمة الأخرى ، بما في ذلك انكماش النقطة العائمة ...

اختار const fn (miri) بالفعل السلوك المشبع المصبوب منذ Rust 1.26 (على افتراض أننا نريد أن تكون نتيجة CTFE و RTFE متسقة) (قبل 1.26 ، يعود وقت الترجمة المتدفق 0)

const fn g(a: f32) -> i32 {
    a as i32
}

const Q: i32 = g(1e+12);

fn main() {
    println!("{}", Q); // always 2147483647
    println!("{}", g(1e+12)); // unspecified value, but always 2147483647 in miri
}

يستخدم Miri / CTFE طرق apfloat to_u128 / to_i128 لإجراء التحويل. لكنني لست متأكدًا مما إذا كان هذا ضمانًا مستقرًا - نظرًا لأنه يبدو أنه قد تغير من قبل (وهو ما لم نكن على علم به عند تنفيذ تلك الأشياء في ميري).

أعتقد أنه يمكننا تعديل هذا إلى أي نوع ينتهي به الأمر في اختيار الكود. لكن حقيقة أن استخدام LLVM's apfloat (والذي يعد إصدار Rust منه منفذًا مباشرًا) يستخدم التشبع مؤشر جيد على أن هذا نوع من "الافتراضي المعقول".

يمكن أن يكون أحد الحلول للسلوك الذي يمكن ملاحظته هو الاختيار العشوائي لإحدى الطرق المتاحة في وقت إنشاء المترجم أو الثنائي الناتج.
ثم لديك وظائف مثل a.saturating_cast::<i32>() للمستخدمين الذين يتطلبون سلوكًا معينًا.

@ dns2utf8

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

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

ومع ذلك ، قد تكون إحدى المشكلات هي ما إذا كانت البنية تحتوي على عدة تعليمات من نوع float-to-int تُرجع قيمًا مختلفة عند تجاوز السعة. في هذه الحالة ، سيؤثر اختيار المترجم على أحدهما أو الآخر على السلوك الذي يمكن ملاحظته ، وهي ليست مشكلة في حد ذاتها ، ولكنها قد تصبح مشكلة إذا تم تكرار fptosi وانتهى الأمر بالنسختين بشكل مختلف.

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

يكون هذا أسهل إذا لم نفوض هذا القرار لـ LLVM ولكننا نفذنا العملية باستخدام asm inline بدلاً من ذلك. والذي سيكون أيضًا أسهل بكثير من تغيير LLVM لإضافة عناصر جوهرية جديدة وخفضها في كل خلفية.

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

[...] والتي ستكون أيضًا أسهل بكثير من تغيير LLVM لإضافة عناصر جوهرية جديدة وخفضها في كل خلفية.

بالإضافة إلى ذلك ، LLVM ليست سعيدة تمامًا بالجوهر مع دلالات تعتمد على الهدف:

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

https://groups.google.com/forum/m/#!msg/llvm -dev / cgDFaBmCnDQ / CZAIMj4IBAA

انا ذاهب ريتاج # 10184 كما فقط T لانغ: أعتقد أن القضايا التي يتعين حلها وهناك خيارات الدلالات حول ما float as int الوسائل

(على سبيل المثال ، ما إذا كنا على استعداد للسماح لها بالحصول على دلالات مرعبة أم لا ، وما إذا كنا على استعداد للسماح لها بالحصول على تحديد غير محدد يستند إلى freeze أم لا ، إلخ)

هذه أسئلة موجهة بشكل أفضل لفريق T-lang ، وليس T-compiler ، على الأقل للمناقشة الأولية ، IMO

واجهت هذه المشكلة للتو مما أدى إلى إنتاج نتائج _irreproducible بين عمليات التشغيل_ حتى بدون إعادة التحويل البرمجي. يبدو أن عامل التشغيل as يجلب بعض القمامة من الذاكرة في مثل هذه الحالات.

أقترح فقط عدم السماح تمامًا باستخدام as لـ "float as int" والاعتماد على طرق التقريب المحددة بدلاً من ذلك. الاستدلال: as ليس ضائعًا للأنواع الأخرى.

الاستدلال: كما هو غير ضياع للأنواع الأخرى.

فعلا؟

استنادًا إلى كتاب Rust ، قد أفترض أنه بدون خسارة إلا في حالات معينة (أي في الحالات التي يتم فيها تحديد From<X> للنوع Y) ، أي يمكنك إرسال u8 إلى u32 باستخدام From ، لكن ليس العكس.

أعني بعبارة "عدم فقدان البيانات" طرح قيم صغيرة بما يكفي لتناسبها. مثال: 1_u64 as u8 ليس ضياعًا ، وبالتالي فإن u8 as u64 as u8 ليس ضائعًا. بالنسبة للعوامات ، لا يوجد تعريف بسيط لـ "النوبات" نظرًا لأن 20000000000000000000000000000_u128 as f32 ليس ضياعًا بينما 20000001_u32 as f32 ، لذلك لا float as int ولا int as float بلا خسارة.

256u64 as u8 الرغم من فقدان

لكن <anything>_u8 as u64 as u8 ليس كذلك.

أعتقد أن الضياع أمر طبيعي ومتوقع مع القوالب ، وليس مشكلة. يعد اقتطاع الأعداد الصحيحة باستخدام القوالب (على سبيل المثال u32 as u8 ) عملية شائعة ذات معنى مفهوم جيدًا ومتسق عبر جميع اللغات المشابهة لـ C التي أعرفها (على الأقل في البنى التي تستخدم تمثيلات الأعداد الصحيحة المكملة للاثنين ، والتي هو في الأساس كل منهم هذه الأيام). تحويلات الفاصلة العائمة الصالحة (أي حيث يتناسب الجزء المتكامل مع الوجهة) لها أيضًا دلالات مفهومة ومتفق عليها. 1.6 as u32 ضياع ، لكن جميع اللغات المشابهة للغة C التي أعرف أنها توافق على أن النتيجة يجب أن تكون 1. تنبع كلتا الحالتين من الإجماع بين مصنعي الأجهزة حول كيفية عمل هذه التحويلات والاتفاقية في C -مثل اللغات التي يتم عرضها يجب أن تكون عالية الأداء ، أنواع المشغلين "أعرف ما أفعله".

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

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

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

وليس له أي آثار جانبية غير إنتاج قيمة غير صحيحة

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

تحويلات الفاصلة العائمة الصالحة (أي حيث يتناسب الجزء المتكامل مع الوجهة) لها أيضًا دلالات مفهومة ومتفق عليها.

هل هناك أي حالات استخدام للتحويلات العائمة إلى int غير مصحوبة بـ trunc() أو round() أو floor() أو ceil() ؟ إستراتيجية التقريب الحالية لـ as "غير محددة" ، مما يجعل as بالكاد قابل للاستخدام مع الأرقام غير المقربة. أعتقد أنه في معظم الحالات ، الشخص الذي يكتب x as u32 يريد بالفعل x.round() as u32 .

أعتقد أن الضياع أمر طبيعي ومتوقع مع القوالب ، وليس مشكلة.

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

التأثير الجانبي هو أن قيمة البيانات المهملة تنشأ في مكان ما في الذاكرة وتكشف عن بعض البيانات (ربما تكون حساسة).

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

هل توجد أي حالات استخدام للتحويلات من نوع float-to-int غير مصحوبة بـ trunc () أو round () أو floor () أو ceil ()؟ استراتيجية التقريب الحالية لـ as "غير محددة" ، مما يجعلها بالكاد قابلة للاستخدام مع الأرقام غير المقربة.

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

أعتقد أنه في معظم الحالات ، الشخص الذي يكتب x as u32 يريد بالفعل x.round() as u32 .

أعتقد أن ذلك يعتمد على المجال ، لكنني أتوقع أن يكون x.trunc() as u32 مرغوبًا أيضًا بشكل شائع.

أوافق ، ولكن فقط إذا كان من السهل التنبؤ بالفقد.

أنا أوافق بالتأكيد. ما إذا كان 1.6 as u32 يصبح 1 أو 2 لا يجب عدم تحديده ، على سبيل المثال.

https://doc.rust-lang.org/nightly/reference/expressions/operator-expr.html#type -cast-expressions

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

المذكرة الروابط هنا.

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

ما يتبقى هو تحديد كيفية تحديد f as $Int في الحالات التالية:

  • f.trunc() > $Int::MAX (بما في ذلك اللانهاية الموجبة)
  • f.trunc() < $Int::MIN (بما في ذلك اللانهاية السلبية)
  • f.is_nan()

أحد الخيارات التي تم تنفيذها بالفعل والمتوفر في Nightly مع علامة المترجم -Z saturating-casts هو تحديدها لإرجاعها على التوالي: $Int::MAX ، $Int::MIN ، وصفر. لكن لا يزال من الممكن اختيار سلوك آخر.

وجهة نظري هي أن السلوك يجب أن يكون حتميًا ويعيد بعض القيمة الصحيحة (بدلاً من الذعر على سبيل المثال) ، ولكن القيمة الدقيقة ليست مهمة جدًا ويجب على المستخدمين الذين يهتمون بهذه الحالات بدلاً من ذلك استخدام طرق التحويل التي أقترحها بشكل منفصل add: https://internals.rust-lang.org/t/pre-rfc-add-explicitly-igned-numeric-conversion-apis/11395

أعتقد أن ذلك يعتمد على المجال ، لكنني أتوقع أن يكون x.trunc() as u32 مرغوبًا أيضًا بشكل شائع.

صيح. بشكل عام ، x.anything() as u32 ، على الأرجح round() ، ولكن يمكن أيضًا أن يكون trunc() ، floor() ، ceil() . فقط x as u32 بدون تحديد إجراء تقريب ملموس هو على الأرجح خطأ.

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

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

أحد الخيارات التي تم تنفيذها بالفعل والمتاح في Nightly مع علامة المترجم -Z saturating-casts هو تحديدها لإرجاعها على التوالي: $Int::MAX ، $Int::MIN ، وصفر. لكن لا يزال من الممكن اختيار سلوك آخر.

السلوك الذي أتوقع أن أحصل عليه مقابل f.trunc() > $Int::MAX و f.trunc() < $Int::MIN هو نفسه عندما يتم تحويل تخيل رقم النقطة العائمة إلى عدد صحيح غير محدود الحجم ثم يتم إرجاع أقل البتات المهمة ( كما في تحويل أنواع الأعداد الصحيحة). من الناحية الفنية ، قد يكون هذا عبارة عن بعض البتات المهمة التي تم تحويلها إلى اليسار اعتمادًا على الأس (بالنسبة للأرقام الموجبة ، تحتاج الأرقام السالبة إلى الانعكاس وفقًا لمكمل الاثنين).

على سبيل المثال ، أتوقع تحويل الأرقام الكبيرة حقًا إلى 0 .

يبدو أنه من الصعب / الأكثر اعتباطية تحديد ما يتحول إليه اللانهاية و NaN.

WebAssembly فقط استقرت القوالب المشبعة بالدلالات التالية:

https://github.com/WebAssembly/nontrapping-float-to-int-conversions/blob/master/proposals/nontrapping-float-to-int-conversion/Overview.md#design

CryZe لذا إذا قرأت ذلك بشكل صحيح ، فهذا يطابق -Z saturating-casts (وما الذي تنفذه Miri بالفعل)؟

RalfJung هذا صحيح.

رائع ، سأقوم بنسخ https://github.com/WebAssembly/testsuite/blob/master/conversions.wast (مع استبدال الفخاخ بالنتائج المحددة) إلى مجموعة اختبار Miri بعد ذلك. :)

RalfJung يُرجى التحديث إلى أحدث إصدار من

sunfishcode شكرا على التحديث! لا بد لي من ترجمة الاختبارات إلى Rust على أي حال ، لذلك لا يزال يتعين علي استبدال أشياء كثيرة. ؛)

هل تختلف اختبارات _sat من حيث القيم التي يتم اختبارها؟ (تحرير: هناك تعليق يقول أن القيم هي نفسها.) بالنسبة إلى مجموعات Rust المشبعة ، أخذت العديد من هذه القيم وأضفتها في https://github.com/rust-lang/miri/pull/1321. كنت كسولًا جدًا للقيام بذلك من أجلهم جميعًا ... لكن أعتقد أن هذا يعني أنه لا يوجد شيء لتغييره الآن بالملف المحدث.

بالنسبة إلى UB الجوهري ، يجب أن تصبح الفخاخ الموجودة على جانب الوسم اختبارات فشل التجميع في ميري على ما أعتقد.

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

تمت إضافة اختبارات Miri (وبالتالي محرك Rust CTFE) في https://github.com/rust-lang/miri/pull/1321. لقد تحققت محليًا من أن rustc -Zmir-opt-level=0 -Zsaturating-float-casts يجتاز أيضًا الاختبارات في هذا الملف.
لقد قمت الآن أيضًا بتنفيذ الجوهر غير المحدد في Miri ، راجع https://github.com/rust-lang/miri/pull/1325.

لقد قمت بنشر https://github.com/rust-lang/rust/pull/71269#issuecomment -615537137 والذي يوثق الحالة الحالية كما فهمتها وأن العلاقات العامة تتحرك أيضًا لتحقيق الاستقرار في سلوك التشبع- Z flag.

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

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

هل هناك خطط للتحقق من التحويلات؟ شيء من هذا القبيل fn i32::checked_from(f64) -> Result<i32, DoesntFit> ؟

ستحتاج إلى التفكير فيما يجب أن يعود i32::checked_from(4.5) .

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