В приведенном ниже коде я увидел, что 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)
Я знаю, что в лязг , по умолчанию строгий алиасинг отключен . Но в этом случае указатель 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
, чтобы предотвратить повторные чтения, которые я не понимаю (если это не «ошибка» оптимизации компилятора).
PS
Мне также не удалось найти ссылку на файл / сообщить о проблеме на clang , которая не вызывает сбой компилятора ...