ข้อผิดพลาด GCC ที่เป็นไปได้เมื่อส่งคืน struct จากฟังก์ชัน


133

ฉันเชื่อว่าฉันพบข้อผิดพลาดใน GCC ในขณะที่ใช้ PCG PRNG ของ O'Neill ( รหัสเริ่มต้นในคอมไพเลอร์ Explorer ของ Godbolt )

หลังจากการคูณoldstateด้วยMULTIPLIER(ผลลัพธ์ที่เก็บไว้ใน rdi) GCC จะไม่เพิ่มผลลัพธ์INCREMENTนั้นย้ายINCREMENTไปยัง rdx แทนซึ่งจะถูกใช้เป็นค่าส่งคืนของ rand32_ret.state

ตัวอย่างที่ทำซ้ำได้ขั้นต่ำ ( Compiler Explorer ):

#include <stdint.h>

struct retstruct {
    uint32_t a;
    uint64_t b;
};

struct retstruct fn(uint64_t input)
{
    struct retstruct ret;

    ret.a = 0;
    ret.b = input * 11111111111 + 111111111111;

    return ret;
}

ชุดประกอบที่สร้างขึ้น (GCC 9.2, x86_64, -O3):

fn:
  movabs rdx, 11111111111     # multiplier constant (doesn't fit in imm32)
  xor eax, eax                # ret.a = 0
  imul rdi, rdx
  movabs rdx, 111111111111    # add constant; one more 1 than multiplier
     # missing   add rdx, rdi   # ret.b=... that we get with clang or older gcc
  ret
# returns RDX:RAX = constant 111111111111 : 0
# independent of input RDI, and not using the imul result it just computed

ที่น่าสนใจคือการปรับเปลี่ยนโครงสร้างเพื่อให้ uint64_t เป็นสมาชิกคนแรกสร้างรหัสที่ถูกต้องเช่นเดียวกับการเปลี่ยนสมาชิกทั้งสองให้เป็น uint64_t

x86-64 System V ส่งคืนโครงสร้างที่เล็กกว่า 16 ไบต์ใน RDX: RAX เมื่อคัดลอกได้เล็กน้อย ในกรณีนี้สมาชิกที่ 2 อยู่ใน RDX เพราะครึ่งหนึ่งของ RAX ที่สูงคือ padding สำหรับการจัดตำแหน่งหรือ.bเมื่อ.aเป็นชนิดที่แคบกว่า ( sizeof(retstruct)เป็น 16 ทางใดทางหนึ่งเราไม่ได้ใช้งาน__attribute__((packed))เพื่อให้สอดคล้องกับ alignof (uint64_t) = 8. )

รหัสนี้มีพฤติกรรมที่ไม่ได้กำหนดซึ่งจะทำให้ GCC ปล่อยแอสเซมบลี "ไม่ถูกต้อง" หรือไม่?

หากไม่เป็นเช่นนี้ควรรับรายงานในhttps://gcc.gnu.org/bugzilla/


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Samuel Liew

คำตอบ:


102

ฉันไม่เห็น UB ใด ๆ ที่นี่; ประเภทของคุณไม่ได้ลงชื่อดังนั้น UB ที่ลงชื่อเข้าใช้จึงเป็นไปไม่ได้และไม่มีอะไรแปลก (และแม้ว่าจะลงชื่อแล้วมันก็จะต้องสร้างเอาท์พุทที่ถูกต้องสำหรับอินพุตที่ไม่ทำให้เกิดโอเวอร์โฟลว์ UB เช่นrdi=1) มันหักด้วยส่วนหน้าของ C ++ ของ GCC เช่นกัน

นอกจากนี้ GCC8.2 จะรวบรวมอย่างถูกต้องสำหรับ AArch64 และ RISC-V (ไปยังmaddคำสั่งหลังจากใช้movkเพื่อสร้างค่าคงที่หรือ RISC-V mul และเพิ่มหลังจากโหลดค่าคงที่) หากเป็น UB ที่ GCC พบเรามักคาดหวังให้พบและทำลายรหัสของคุณสำหรับ ISAs อื่นเช่นกันอย่างน้อยก็มีความกว้างของประเภทและความกว้างของการลงทะเบียนที่คล้ายกัน

เสียงดังกราวยังรวบรวมได้อย่างถูกต้อง

สิ่งนี้ดูเหมือนจะเป็นการถดถอยจาก GCC 5 ถึง 6 การคอมไพล์ GCC5.4 นั้นถูกต้อง 6.1 และใหม่กว่าไม่ได้ ( Godbolt )

คุณสามารถรายงานสิ่งนี้ในBugzilla ของ GCCโดยใช้ MCVE จากคำถามของคุณ

ดูเหมือนว่าเป็นข้อบกพร่องใน x86-64 System V struct-return handling บางทีอาจเป็น struct ที่มีการแพ็ด นั่นจะอธิบายว่าทำไมมันจึงทำงานเมื่ออินไลน์และเมื่อขยายaเป็น uint64_t (หลีกเลี่ยงการขยาย)



11
@ vitorhnn ดูเหมือนว่าจะได้รับการแก้ไขmasterแล้ว
SS Anne

19

นี้ได้รับการแก้ไขใน/trunkmaster

นี่คือที่เกี่ยวข้องกระทำ

และนี่คือ แพทช์เพื่อแก้ไขปัญหา

อยู่บนพื้นฐานของความคิดเห็นในแพทช์ที่reload_combine_recognize_patternฟังก์ชั่นได้พยายามที่จะปรับinsns ใช้


14

รหัสนี้มีพฤติกรรมที่ไม่ได้กำหนดซึ่งจะทำให้ GCC ปล่อยแอสเซมบลี "ไม่ถูกต้อง" หรือไม่?

พฤติกรรมของรหัสที่แสดงในคำถามนั้นได้รับการกำหนดอย่างดีตามมาตรฐาน C99 และภาษา C ในภายหลัง โดยเฉพาะอย่างยิ่ง C อนุญาตให้ฟังก์ชันคืนค่าโครงสร้างโดยไม่มีข้อ จำกัด


2
GCC สร้างนิยามของฟังก์ชันแบบสแตนด์อะโลน นั่นคือสิ่งที่เรากำลังดูอยู่ไม่ว่าจะเป็นสิ่งที่ทำงานหรือไม่เมื่อคุณรวบรวมในหน่วยการแปลพร้อมกับฟังก์ชั่นอื่น ๆ คุณสามารถทดสอบได้อย่างง่ายดายโดยไม่ต้องใช้จริง__attribute__((noinline))โดยรวบรวมมันในหน่วยการแปลด้วยตัวเองและเชื่อมโยงโดยไม่มี LTO หรือการคอมไพล์ด้วย-fPICซึ่งหมายถึงสัญลักษณ์ทั่วโลกทั้งหมด (โดยค่าเริ่มต้น) interposable ดังนั้นจึงไม่สามารถ inlined เป็นผู้โทร แต่จริงๆปัญหาสามารถตรวจพบได้เพียงแค่ดูจาก asm ที่สร้างขึ้นโดยไม่คำนึงถึงผู้โทร
Peter Cordes

ยุติธรรมเพียงพอ @PeterCordes แม้ว่าฉันจะมั่นใจอย่างมีเหตุผลว่ารายละเอียดนั้นเปลี่ยนจากฉันใน Godbolt
John Bollinger

คำถามรุ่นที่ 1 เชื่อมโยงกับ Godbolt ด้วยฟังก์ชั่นของตัวเองในหน่วยการแปลเช่นสถานะของคำถามเมื่อคุณตอบ ฉันไม่ได้ตรวจสอบการแก้ไขหรือความคิดเห็นทั้งหมดที่คุณได้ดู น้ำใต้สะพาน แต่ฉันไม่คิดว่าจะมีเคยอ้างว่าคำนิยาม asm __attribute__((noinline))แบบสแตนด์อะโลนเสียเฉพาะเมื่อแหล่งที่มาใช้ (นั่นจะน่าตกใจไม่ใช่แค่แปลกใจที่ข้อบกพร่องของความถูกต้องของ GCC คือ) อาจเป็นไปได้ว่ามีการกล่าวถึงเฉพาะสำหรับการโทรทดสอบที่พิมพ์ผล
Peter Cordes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.