restrict
在下面的代码中,我看到没有隐式指针说明符,clang 无法执行更好的优化:
#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
添加:
在对 Lundin 的回答的评论中与 Lundin 进行了一些讨论后,我得到的印象是可能会导致重新读取,因为编译器会假设这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
指针以防止我不理解的重新读取(除非它是编译器优化“错误”)。
附言
我也找不到任何指向文件/报告不会导致编译器崩溃的问题的链接...