Возможная ошибка GCC при возврате структуры из функции


133

Я полагаю, что обнаружил ошибку в GCC при реализации PCG PRNG О'Нила. ( Исходный код в проводнике компилятора Годболта )

После умножения 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 является отступом для выравнивания или .bкогда .aиспользуется более узкий тип. ( sizeof(retstruct)16 в любом случае; мы не используем, __attribute__((packed))поэтому он учитывает alignof (uint64_t) = 8.)

Содержит ли этот код какое-либо неопределенное поведение, которое позволило бы GCC выдавать «неправильную» сборку?

Если нет, об этом следует сообщить на https://gcc.gnu.org/bugzilla/


Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Самуэль Лев

Ответы:


102

Я не вижу здесь никакого UB; ваши типы не подписаны, поэтому UB с переполнением со знаком невозможен, и в этом нет ничего странного. (И даже если он подписан, он должен будет производить правильные выходные данные для входов, которые не вызывают переполнение UB, например rdi=1). Это сломано и с GCC-интерфейсом C ++.

Кроме того, GCC8.2 правильно компилирует его для AArch64 и RISC-Vmaddинструкцию после использования movkдля построения констант или в RISC-V mul и добавляет после загрузки констант). Если бы это был UB, который GCC находил, мы, как правило, ожидали, что он найдет его и сломает ваш код также для других ISA, по крайней мере для тех, которые имеют одинаковую ширину типов и ширину регистров.

Clang также правильно его компилирует.

Похоже, это регрессия от GCC 5 до 6; GCC5.4 компилируется правильно, 6.1 и позже - нет. ( Godbolt ).

Вы можете сообщить об этом на bugzilla GCC, используя MCVE из вашего вопроса.

Похоже, что это ошибка в обработке возврата структуры x86-64 System V, возможно, структур, содержащих отступы. Это объясняет, почему это работает при вставке и при расширении aдо uint64_t (избегая заполнения).



11
@vitorhnn Похоже, это было исправлено master.
SS Anne


14

Содержит ли этот код какое-либо неопределенное поведение, которое позволило бы GCC выдавать «неправильную» сборку?

Поведение кода, представленного в вопросе, четко определено в отношении стандартов языка C99 и более поздних. В частности, 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.