Можлива помилка GCC при поверненні структури з функції


133

Я вважаю, що знайшов помилку в GCC під час впровадження програми PCG PRNG O'Neill. ( Початковий код у Провіднику компілятора Godbolt )

Після множення oldstateна MULTIPLIER(результат, збережений у rdi), GCC не додає цього результату INCREMENT, INCREMENTзамість цього, movabs'ing в rdx, який потім використовується як повернене значення rand32_ret.state

Мінімальний приклад відтворення ( Провідник компілятора ):

#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 Система V дійсно повертає структури RDX: RAX, менші ніж 16 байт, коли вони тривіально копіюються. У цьому випадку 2-й член знаходиться в RDX, тому що висока половина RAX є підкладкою для вирівнювання або .bколи .aвужчий тип. ( sizeof(retstruct)це 16 в будь-якому випадку; ми не використовуємо, __attribute__((packed))тому він поважає alignof (uint64_t) = 8.)

Чи містить цей код якусь невизначену поведінку, яка дозволила б GCC випромінювати "неправильну" збірку?

Якщо ні, про це слід повідомити на https://gcc.gnu.org/bugzilla/


Коментарі не для розширеного обговорення; ця розмова була переміщена до чату .
Самуель Liew

Відповіді:


102

Я не бачу тут жодного УБ; ваші типи не підписані, тому UB-переповнення з підписами неможливо, і немає нічого дивного. (І навіть якщо він підписаний, він повинен створити правильні виходи для входів, які не викликають переповнення UB, як-от rdi=1). Він зламаний і з C ++ переднього кінця GCC.

Також GCC8.2 компілює його правильно для AArch64 та RISC-V ( maddінструкція після використання movkдля побудови констант або RISC-V mul та додавання після завантаження констант). Якщо GCC знаходив GCC, ми, як правило, очікуємо, що він знайде його і порушить ваш код для інших ISA, принаймні тих, які мають схожі ширини типу та ширину реєстру.

Кланг також правильно його компілює.

Це, здається, є регресом від GCC 5 до 6; GCC5.4 компілюється правильно, 6.1 та пізніші - ні. ( Годболт ).

Про це можна повідомити про помилку GCC за допомогою MCVE у своєму запитанні.

Це насправді схоже на помилку в x86-64 System V-поверненні, можливо, з конструкціями, що містять прокладки. Це пояснило б, чому він працює під час вбудовування та при розширенні aдо uint64_t (уникаючи прокладки).



11
@vitorhnn Схоже, це було виправлено master.
СС Енн


14

Чи містить цей код якусь невизначену поведінку, яка дозволила б GCC випромінювати "неправильну" збірку?

Поведінка коду, представленого у запитанні, добре визначена щодо мовних стандартів C99 та пізніших C. Зокрема, C дозволяє функціям повертати структурні значення без обмежень.


2
GCC виробляє окреме визначення функції; це те, що ми дивимось, незалежно від того, чи це те, що працює, коли ви компілюєте його в блок перекладу разом з іншими функціями. Ви можете так само легко перевірити його, не використовуючи насправді __attribute__((noinline)), склавши його в блок перекладу сам по собі і пов'язуючи без LTO, або компілюючи, -fPICщо означає, що всі глобальні символи є (за замовчуванням) проміжними, тому їх не можна вводити в абонента. Але насправді проблему можна виявити лише з огляду на сформований asm, незалежно від абонентів.
Пітер Кордес

Досить справедливо, @PeterCordes, хоча я досить впевнений, що ця деталь була змінена з-під мене в Godbolt.
Джон Боллінгер

Версія 1 запитання пов'язана з Godbolt із просто функцією сама по собі в блоці перекладу, як і стан самого запитання, коли ви відповіли. Я не перевірив усі зміни чи коментарі, які ви могли переглядати. Вода під мостом, але я не думаю, що колись було твердження, що автономне визначення асм було порушено лише тоді, коли джерело використано __attribute__((noinline)). (Це було б шокуюче, а не просто дивно, як виявляється помилка коректності GCC). Можливо, це згадувалося лише для тестового абонента, який друкує результат.
Пітер Кордес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.