Возможная ошибка G CC при возврате структуры из функции - PullRequest
133 голосов
/ 23 января 2020

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

После умножения oldstate на MULTIPLIER (результат сохраняется в rdi), G CC не добавляет этот результат к INCREMENT, вместо * movabs'ing INCREMENT в 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;
}

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

Содержит ли этот код какое-либо неопределенное поведение, которое позволило бы G CC чтобы испустить "неправильную" сборку?

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

Ответы [ 3 ]

102 голосов
/ 23 января 2020

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

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

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

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

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

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

19 голосов
/ 25 января 2020

Это было исправлено на trunk / master.

Вот соответствующий коммит .

И это патч чтобы исправить проблему.

На основании комментария в патче функция reload_combine_recognize_pattern пыталась настроить ИСПОЛЬЗОВАТЬ insns .

14 голосов
/ 23 января 2020

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

Поведение кода, представленного в вопросе, хорошо определяется с помощью соблюдение стандартов языка C99 и более поздних C. В частности, C разрешает функциям возвращать структурные значения без ограничений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...