ทำไมคอมไพเลอร์ Rust ไม่ปรับโค้ดให้เหมาะสมโดยสมมติว่าการอ้างอิงที่ไม่แน่นอนทั้งสองไม่สามารถใช้นามแฝงได้


301

เท่าที่ฉันรู้การอ้างอิงนามแฝงตัวชี้ / สามารถขัดขวางความสามารถของคอมไพเลอร์ในการสร้างรหัสที่ดีที่สุดเพราะพวกเขาจะต้องตรวจสอบให้แน่ใจว่าการสร้างไบนารีทำงานอย่างถูกต้องในกรณีที่ทั้งสองอ้างอิง / พอยน์เตอร์นามแฝงแน่นอน ตัวอย่างเช่นในรหัส C ต่อไปนี้

void adds(int  *a, int *b) {
    *a += *b;
    *a += *b;
}

เมื่อคอมไพล์clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)ด้วย-O3แฟล็ก

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)  # The first time
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)  # The second time
   a:    c3                       retq

ที่นี่รหัสเก็บกลับไป(%rdi)สองครั้งในกรณีint *aและint *bนามแฝง

เมื่อเราบอกคอมไพเลอร์อย่างชัดเจนว่าตัวชี้ทั้งสองนี้ไม่สามารถใช้นามแฝงกับrestrictคำหลักได้:

void adds(int * restrict a, int * restrict b) {
    *a += *b;
    *a += *b;
}

จากนั้นเสียงดังกราวจะเปล่งรหัสไบนารี่เวอร์ชั่นที่ดีที่สุด

0000000000000000 <adds>:
   0:    8b 06                    mov    (%rsi),%eax
   2:    01 c0                    add    %eax,%eax
   4:    01 07                    add    %eax,(%rdi)
   6:    c3                       retq

เนื่องจากสนิมทำให้แน่ใจ (ยกเว้นในรหัสที่ไม่ปลอดภัย) ว่าการอ้างอิงที่ไม่แน่นอนทั้งสองไม่สามารถใช้นามแฝงได้ฉันจึงคิดว่าคอมไพเลอร์ควรจะปล่อยโค้ดที่ได้รับการปรับให้เหมาะสมที่สุด

เมื่อผมทดสอบด้วยโค้ดด้านล่างและเรียบเรียงด้วยrustc 1.35.0กับ-C opt-level=3 --emit obj,

#![crate_type = "staticlib"]
#[no_mangle]
fn adds(a: &mut i32, b: &mut i32) {
    *a += *b;
    *a += *b;
}

มันสร้าง:

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)
   a:    c3                       retq

สิ่งนี้ไม่ได้ใช้ประโยชน์จากการรับประกันaและbไม่สามารถใช้นามแฝงได้

นี่เป็นเพราะคอมไพเลอร์ปัจจุบันยังอยู่ในระหว่างการพัฒนาและยังไม่ได้รวมการวิเคราะห์นามแฝงเพื่อเพิ่มประสิทธิภาพ

นี่เป็นเพราะยังมีโอกาสaและbนามแฝงแม้ใน Rust ที่ปลอดภัยหรือไม่


3

76
หมายเหตุด้านข้าง: " เนื่องจาก Rust ทำให้แน่ใจ (ยกเว้นในรหัสที่ไม่ปลอดภัย) ว่าการอ้างอิงที่ไม่แน่นอนทั้งสองไม่สามารถใช้นามแฝงได้ " - มีค่ากล่าวถึงว่าแม้ในunsafeรหัสการอ้างอิงนามแฝงที่ไม่แน่นอนไม่ได้รับอนุญาตและทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด คุณสามารถใช้นามแฝงตัวชี้แบบ raw ได้ แต่unsafeรหัสไม่อนุญาตให้คุณละเว้นกฎมาตรฐานของสนิม เป็นเพียงความเข้าใจผิดที่พบบ่อยและคุ้มค่าที่จะชี้ให้เห็น
Lukas Kalbertodt

6
ฉันใช้เวลาสักครู่กว่าจะเข้าใจว่าตัวอย่างคืออะไรเพราะฉันไม่มีทักษะในการอ่าน asm ดังนั้นในกรณีที่มันช่วยคนอื่นได้: มันเดือดลงไปว่าการ+=ปฏิบัติการทั้งสองในร่างกายของaddsสามารถตีความได้ว่าเป็น*a = *a + *b + *bอย่างไร หากตัวชี้ไม่นามแฝงพวกเขาสามารถคุณยังสามารถมองเห็นสิ่งที่จำนวนเงินb* + *bใน asm 2: 01 c0 add %eax,%eaxสองรายการ: แต่ถ้าพวกเขาทำนามแฝงพวกเขาไม่สามารถทำได้เพราะเมื่อคุณเพิ่ม*bเป็นครั้งที่สองมันจะมีค่าที่แตกต่างจากครั้งแรกที่อยู่รอบ ๆ (อันที่คุณเก็บไว้ใน4:รายการแรกของ asm)
dlukes

คำตอบ:


364

แต่เดิม Rust ได้เปิดใช้งานnoaliasแอตทริบิวต์ของ LLVM แต่สิ่งนี้ทำให้รหัสที่แปลผิด เมื่อทุกรุ่นที่รองรับ LLVM ไม่ miscompile รหัสที่มันจะเป็นอีกครั้งที่เปิดใช้งาน

ถ้าคุณเพิ่ม-Zmutable-noalias=yesตัวเลือกคอมไพเลอร์คุณจะได้รับแอสเซมบลีที่คาดไว้:

adds:
        mov     eax, dword ptr [rsi]
        add     eax, eax
        add     dword ptr [rdi], eax
        ret

ใส่เพียงแค่สนิมใส่เทียบเท่าของซีrestrictคำหลักทุกที่ไกลมากขึ้นแพร่หลายกว่าโปรแกรมใด ๆ C ปกติ กรณีมุมการออกกำลังกายนี้ของ LLVM มากกว่าที่มันสามารถจัดการได้อย่างถูกต้อง ปรากฎว่าโปรแกรมเมอร์ C และ C ++ ไม่ได้ใช้restrictบ่อยเท่าที่&mutใช้ใน Rust

นี้ได้เกิดขึ้นหลายครั้ง

  • สนิม 1.0 ถึง 1.7 - noaliasเปิดใช้งาน
  • สนิม 1.8 ถึง 1.27 - noaliasปิดการใช้งาน
  • ขึ้นสนิม 1.28 ถึง 1.29 - noaliasเปิดใช้งาน
  • ขึ้นสนิม 1.30 ถึง ??? - noaliasปิดการใช้งาน

ปัญหาสนิมที่เกี่ยวข้อง


12
มันไม่น่าแปลกใจ การเรียกร้องขอบเขตกว้างของความเป็นมิตรกับหลายภาษาแม้ LLVM ได้รับการออกแบบโดยเฉพาะเป็นแบ็กเอนด์ C ++ และมันมักจะมีแนวโน้มที่จะทำให้หายใจไม่ออกในสิ่งที่ดูไม่เหมือน C ++
Mason Wheeler

47
@MasonWheeler ถ้าคุณคลิกผ่านบางประเด็นคุณสามารถค้นหาตัวอย่างรหัส C ที่ใช้restrictและคอมไพล์ใน Clang และ GCC มันไม่ จำกัด เฉพาะภาษาที่“ C ++ ไม่เพียงพอ” ยกเว้นว่าคุณจะนับ C ++ เองในกลุ่มนั้น
Shepmaster

6
@MasonWheeler: ฉันไม่คิดว่า LLVM ได้รับการออกแบบมาจริงๆเกี่ยวกับกฎของ C หรือ C ++ แต่จะเป็นกฎของ LLVM มันทำให้สมมติฐานที่มักจะถือเป็นจริงสำหรับรหัส C หรือ C ++ แต่จากสิ่งที่ฉันสามารถบอกได้ว่าการออกแบบนั้นขึ้นอยู่กับแบบจำลองการพึ่งพาข้อมูลแบบคงที่ซึ่งไม่สามารถจัดการกรณีมุมที่ยุ่งยาก มันจะไม่เป็นไรถ้ามันคิดว่าการพึ่งพาข้อมูลในแง่ร้ายที่ไม่สามารถพิสูจน์ได้ แต่ในทางกลับกันมันจะถือว่าเป็นการกระทำที่ไม่มี ops ซึ่งจะเขียนที่เก็บข้อมูลด้วยรูปแบบบิตเดียวกันกับที่จัดขึ้นและที่มีศักยภาพ อ่านและเขียน
supercat

8
@supercat ฉันได้อ่านความคิดเห็นของคุณสองสามครั้ง แต่ฉันยอมรับว่าฉันนิ่งงัน - ฉันไม่รู้ว่าพวกเขาต้องทำอะไรกับคำถามหรือคำตอบนี้ พฤติกรรมที่ไม่ได้กำหนดไม่ได้มาที่นี่นี่คือ "เพียงแค่" กรณีของการเพิ่มประสิทธิภาพหลายรายการผ่านการโต้ตอบที่ไม่ดีซึ่งกันและกัน
Shepmaster

2
@avl_sweden จะย้ำมันเป็นเพียงแค่ข้อผิดพลาด ขั้นตอนการเพิ่มประสิทธิภาพการยกเลิกการวนลูปทำ (ไม่?) ไม่ได้นำพnoaliasอยน์เตอร์เข้าสู่บัญชีอย่างเต็มที่เมื่อดำเนินการ มันสร้างพอยน์เตอร์ใหม่โดยอิงจากพอยน์เตอร์พอยน์เตอร์, การคัดลอกแอnoaliasททริบิวต์อย่างไม่ถูกต้องแม้ว่าพอยน์เตอร์ใหม่จะใช้นามแฝง
Shepmaster
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.