В приведенном ниже коде я увидел, что clang не может выполнить лучшую оптимизацию без неявного спецификатора указателя restrict
:
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct {
uint32_t event_type;
uintptr_t param;
} event_t;
typedef struct
{
event_t *queue;
size_t size;
uint16_t num_of_items;
uint8_t rd_idx;
uint8_t wr_idx;
} queue_t;
static bool queue_is_full(const queue_t *const queue_ptr)
{
return queue_ptr->num_of_items == queue_ptr->size;
}
static size_t queue_get_size_mask(const queue_t *const queue_ptr)
{
return queue_ptr->size - 1;
}
int queue_enqueue(queue_t *const queue_ptr, const event_t *const event_ptr)
{
if(queue_is_full(queue_ptr))
{
return 1;
}
queue_ptr->queue[queue_ptr->wr_idx++] = *event_ptr;
queue_ptr->num_of_items++;
queue_ptr->wr_idx &= queue_get_size_mask(queue_ptr);
return 0;
}
Я скомпилировал этот код с помощью clang версии 11.0.0 (clang-1100.0.32.5)
clang -O2 -arch armv7m -S test.c -o test.s
В дизассемблированном файле увидел, что сгенерированный код перечитывает память:
_queue_enqueue:
.cfi_startproc
@ %bb.0:
ldrh r2, [r0, #8] ---> reads the queue_ptr->num_of_items
ldr r3, [r0, #4] ---> reads the queue_ptr->size
cmp r3, r2
itt eq
moveq r0, #1
bxeq lr
ldrb r2, [r0, #11] ---> reads the queue_ptr->wr_idx
adds r3, r2, #1
strb r3, [r0, #11] ---> stores the queue_ptr->wr_idx + 1
ldr.w r12, [r1]
ldr r3, [r0]
ldr r1, [r1, #4]
str.w r12, [r3, r2, lsl #3]
add.w r2, r3, r2, lsl #3
str r1, [r2, #4]
ldrh r1, [r0, #8] ---> !!! re-reads the queue_ptr->num_of_items
adds r1, #1
strh r1, [r0, #8]
ldrb r1, [r0, #4] ---> !!! re-reads the queue_ptr->size (only the first byte)
ldrb r2, [r0, #11] ---> !!! re-reads the queue_ptr->wr_idx
subs r1, #1
ands r1, r2
strb r1, [r0, #11] ---> !!! stores the updated queue_ptr->wr_idx once again after applying the mask
movs r0, #0
bx lr
.cfi_endproc
@ -- End function
После добавления ключевого слова restrict
к указателям эти ненужные повторные чтения просто исчезли:
int queue_enqueue(queue_t * restrict const queue_ptr, const event_t * restrict const event_ptr)
Я знаю, что в clang по умолчанию строгие псевдонимы отключены. Но в этом случае указатель event_ptr
определяется как const
, поэтому содержимое его объекта не может быть изменено этим указателем, следовательно, он не может влиять на содержимое, на которое указывает queue_ptr
(при условии, что объекты перекрываются в памяти), верно?
Так это ошибка оптимизации компилятора или действительно есть какой-то странный случай, когда объект, указанный queue_ptr
, может быть затронут event_ptr
, предполагающим это объявление:
int queue_enqueue(queue_t *const queue_ptr, const event_t *const event_ptr)
Кстати, я попытался скомпилировать тот же код для цели x86 и проверил аналогичную проблему оптимизации.
Сгенерированная сборка с ключевым словом restrict
не содержит повторов:
_queue_enqueue:
.cfi_startproc
@ %bb.0:
ldr r3, [r0, #4]
ldrh r2, [r0, #8]
cmp r3, r2
itt eq
moveq r0, #1
bxeq lr
push {r4, r6, r7, lr}
.cfi_def_cfa_offset 16
.cfi_offset lr, -4
.cfi_offset r7, -8
.cfi_offset r6, -12
.cfi_offset r4, -16
add r7, sp, #8
.cfi_def_cfa r7, 8
ldr.w r12, [r1]
ldr.w lr, [r1, #4]
ldrb r1, [r0, #11]
ldr r4, [r0]
subs r3, #1
str.w r12, [r4, r1, lsl #3]
add.w r4, r4, r1, lsl #3
adds r1, #1
ands r1, r3
str.w lr, [r4, #4]
strb r1, [r0, #11]
adds r1, r2, #1
strh r1, [r0, #8]
movs r0, #0
pop {r4, r6, r7, pc}
.cfi_endproc
@ -- End function
Дополнение:
После некоторого обсуждения с Лундином в комментариях к его ответу у меня сложилось впечатление, что повторные чтения могут быть вызваны тем, что компилятор предположил бы, что queue_ptr->queue
потенциально может указывать на сам *queue_ptr
. Поэтому я изменил структуру queue_t
, чтобы она содержала массив вместо указателя:
typedef struct
{
event_t queue[256]; // changed from pointer to array with max size
size_t size;
uint16_t num_of_items;
uint8_t rd_idx;
uint8_t wr_idx;
} queue_t;
Однако повторные чтения остались прежними. Я до сих пор не могу понять, что могло заставить компилятор думать, что поля queue_t
могут быть изменены и, следовательно, требуют повторного чтения... Следующее объявление исключает повторное чтение:
int queue_enqueue(queue_t * restrict const queue_ptr, const event_t *const event_ptr)
Но почему queue_ptr
должен быть объявлен как указатель restrict
, чтобы предотвратить повторное чтение, я не понимаю (если только это не "ошибка" оптимизации компилятора).
P.S.
Я также не смог найти ссылку на файл/отчет о проблеме в clang, которая не приводит к сбою компилятора...
const
не означает, что значение не может измениться; это означает, что значение нельзя изменить с помощью идентификатораconst
.int foo; int *a = &foo; int const *b = &foo; *a = 42 /*ok*/; *b = -1 /*nope*/;
- person pmg   schedule 16.10.2019const event_t *
. Я добавлю это уточнение - person Alex Lop.   schedule 16.10.2019!tbaa X
). Я проверил это и увидел, что Clang копирует события без аннотации TBAA, которая может объяснить сброс значения, но в то же время я видел, что разные конфигурации компилятора могут генерироватьllvm.memcpy
вызовы WITH!tbaa
метаданных. Я мог бы попытаться проверить это позже, не могу написать ответ от этого. - person StaceyGirl   schedule 25.10.2019