Оптимизация строгого псевдонима Clang против недостижимого кода, нарушающего строго псевдонимы - PullRequest
1 голос
/ 20 марта 2019

У меня есть вопрос о строгом псевдониме и оптимизации clang для одного примера.

Давайте рассмотрим следующий пример (1):

typedef void (*FTy)(void);
FTy F = 0;
(*F)();

Это неопределенное поведение.

Давайте рассмотрим пример (2) ниже:

typedef void (*FTy)(void);
static const FTy F = 0;

void g( int flag)
{
    if ( flag )
    {
        (*F)();
    }
}

int main( void)
{
    g( 0);
    return (0);
}

Косвенный вызов F по-прежнему «неопределенное поведение», но всегда находится в ложном состоянии. Так что программа должна быть правильной.

Теперь давайте рассмотрим основной пример (3): (второе издание: благодаря упрощенной версии @Antti Haapala) (третье издание: использование always_inline)

#include <stdio.h>

#ifndef BUGGY
#define BUGGY 1
#endif

static inline void __attribute__((always_inline)) longLongAssign( int cond, char *ptr)
{
    if ( cond )
    {
        *((long long *)ptr) = 0;
    }
}

void funcA(int s, int g)
{
    int i, j = 0, k;
    int grp[4] = {-1, -1};
    void *ptr[2] = {(void *)&(grp[0]), 0};

    for (i = 0, k = 0; i < 1; ++i) {
        for (j = 0; j < 1; ++j) {
            if ( grp[g] > 0 )
            {
                if ( g > 5 )
                {
                    continue;

                } else
                {
                    longLongAssign( g > 3, (char *)ptr[0]);
                }
            }
            grp[k++] = 0;
        }
        printf("this should be zero: %d\n", grp[0]);
    }
}

int main(void) {
    funcA(0, 1);
}

Скомпилируйте с помощью gcc и выполните

this should be zero: 0

Скомпилировать с помощью "clang-7.0 -O0" и выполнить

this should be zero: 0

Скомпилируйте с помощью "clang-7.0 -O1 -fno-strict-aliasing" и выполните

this should be zero: 0

Скомпилировать с помощью "clang-7.0 -O1" и выполнить

this should be zero: -1

В основном примере один из магазинов grp формально нарушает строгие псевдонимы

        *((long long *)ptr) = 0;

Но этот магазин всегда находится в ложном состоянии.

Вопрос здесь такой: как работает магазин

  • нарушение нарушающего правила строгого алиасинга
  • но находится в недоступном утверждении

может повлиять на любой способ выполнения программы?

Правильно ли это по языку C?

Является ли приведенный ниже пример (4) правильным, хорошо определенным и не имеет неопределенного поведения?

void assign( int type, char *ptr)
{
    if ( ptr )
    {
        if ( (type == 0) )
        {
            *((int *)ptr) = 1;

        } else if ( (type == 1) )
        {
            *((float *)ptr) = 1;
        } else
        {
            // unknown type
        }
     }
 }

 int main( void)
 {
     int a;
     float b;

     assign( 0, (char *)&a);
     assign( 1, (char *)&b);
     assign( 0, (char *)0);

     return (0);
 }

Встроенная и постоянная оптимизация распространения в функции main дает

    ...
    if ( &a )
    {
        if ( (0 == 0) )
        {
            *((int *)&a) = 1;

        } else if ( (0 == 1) )
        {
            *((float *)&a) = 1;
        } else
        {
            // unknown type
        }
    }
    ...

Операция в одной руке магазина

            *((float *)&a) = 1;

формально нарушает псевдонимы, но стоит в недоступном месте.

По каким причинам пример (4) может быть неверным? Если пример (4) правильный, то почему пример (3) дает компиляцией clang разные результаты?

Ответы [ 2 ]

2 голосов
/ 20 марта 2019

Оператор выражения

                    *(long long *)grp = 0;

имеет неопределенное поведение из-за доступа к объекту типа int[4] через lvalue другого несовместимого типа (long long) -нарушение сглаживания, как вы заметили в вопросе.Но это не обязательно должно быть ограничено поведением runtime .(Потенциальная) проблема, видимая во время перевода, поведение времени перевода тоже не определено, и поэтому таков результат каждого выполнения.

Или, как минимум, это интерпретация стандарта, к которому приПо крайней мере, некоторые разработчики компилятора подписываются.Некоторые люди здесь возражают против таких интерпретаций, но это не меняет того факта, что вам приходится иметь с ними дело.

Что касается обновления

Ваш пример(4) имеет совершенно четкое поведение.Основными соображениями здесь являются

  • . Разрешается явно преобразовывать значение одного типа указателя объекта в другой тип указателя объекта.Существуют предостережения относительно выравнивания результата, но C требует, чтобы он всегда работал для преобразования в char *, и требует обратного преобразования, чтобы воспроизвести исходное значение указателя (которое не имеет проблемы с выравниванием, если оно действительно с самого начала).

  • Доступ к представлению любого объекта разрешен через lvalue символьного типа.Другими словами, char * допускается для псевдонима любой части любого объекта, поэтому даже если вы не не обращаетесь к чему-либо напрямую через значения char *, передаваемые assign(), соответствующий компилятор долженПредположим, что эти указатели могут иметь псевдоним любого объекта в программе.

  • Нулевой указатель любого типа может быть преобразован в другой тип указателя объекта, что приведет к нулевому указателю целевого типа.

  • Используя аргумент type для функции assign() способом, совместимым с реализацией этой функции, программа гарантирует, что каждый задействованный объект в конечном итоге будет доступен (только) через lvalueего правильного типа.

Какие оптимизации может применить компилятор, не имеют отношения к этому анализу.Это код, который вы представляете компилятору, с помощью которого устанавливается поведение, если оно определено.Предполагая, что программа имеет определенное поведение, компилятор несет ответственность за обеспечение того, чтобы программа демонстрировала такое поведение в результате преобразования в исполняемый файл, и она может и использует свои знания о своей собственной реализации для предоставленияза это.

0 голосов
/ 25 марта 2019

Спасибо всем за ваши комментарии! Вы помогли мне лучше понять проблему.

Просто чтобы уточнить, почему я делаю это и чего я действительно хотел: Я портирую Clang на какую-то конкретную платформу. Таким образом, моя цель здесь состояла в том, чтобы понять, содержит ли этот тест (из нашей системы тестирования автогенов) ошибку, или, скорее, это ошибка компиляции clang. По результатам этого обсуждения я отправил ошибку llvm (https://bugs.llvm.org/show_bug.cgi?id=41178).

Еще раз спасибо!

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