我想使用 volatile 位域結構來設定硬體暫存器,如下面的代碼
union foo {
uint32_t value;
struct {
uint32_t x : 1;
uint32_t y : 3;
uint32_t z : 28;
};
};
union foo f = {0};
int main()
{
volatile union foo *f_ptr = &f;
//union foo tmp;
*f_ptr = (union foo) {
.x = 1,
.y = 7,
.z = 10,
};
//*f_ptr = tmp;
return 0;
}
但是,編譯器會將其多次寫入STR、LDR HW 暫存器。寫暫存器時它會立即觸發硬體作業,這是一件可怕的事情。
main:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
movw r3, #:lower16:.LANCHOR0
movs r0, #0
movt r3, #:upper16:.LANCHOR0
ldr r2, [r3]
orr r2, r2, #1
str r2, [r3]
ldr r2, [r3]
orr r2, r2, #14
str r2, [r3]
ldr r2, [r3]
and r2, r2, #15
orr r2, r2, #160
str r2, [r3]
bx lr
.size main, .-main
.global f
.bss
.align 2
我的 gcc 版本是:arm-linux-gnueabi-gcc (Linaro GCC 4.9-2017.01) 4.9.4 并使用 -O2 優化構建
我試圖使用區域變數來解決這個問題
union foo {
uint32_t value;
struct {
uint32_t x : 1;
uint32_t y : 3;
uint32_t z : 28;
};
};
union foo f = {0};
int main()
{
volatile union foo *f_ptr = &f;
union foo tmp;
tmp = (union foo) {
.x = 1,
.y = 7,
.z = 10,
};
*f_ptr = tmp;
return 0;
}
好吧,它不會STR到 HW 注冊幾次
main:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
movs r1, #10
movs r2, #15
movw r3, #:lower16:.LANCHOR0
bfi r2, r1, #4, #28
movt r3, #:upper16:.LANCHOR0
movs r0, #0
str r2, [r3]
bx lr
.size main, .-main
.global f
.bss
.align 2
考慮到嵌入式系統二進制大小的限制,我認為使用區域變數仍然不是一個好主意。
有沒有辦法在不使用區域變數的情況下處理這個問題?
uj5u.com熱心網友回復:
我認為這是 GCC 中的一個錯誤。根據下面的討論,您可以考慮使用:
f_ptr->value = (union foo) {
.x = 1,
.y = 7,
.z = 10,
} .value;
根據 C 標準,當原始 C 代碼名義上不訪問易失性物件時,編譯器為程式生成的代碼可能不會訪問易失性物件。該代碼*f_ptr = (union foo) { .x = 1, .y = 7, .z = 10, };是對*f_ptr. 所以我們希望這會生成一個單獨的商店*f_ptr;生成兩個商店違反了標準的要求。
我們可以認為對此的解釋是 GCC 將聚合(聯合和/或其中的結構)視為多個物件,每個物件都是單獨的 volatile,而不是一個聚合的 volatile 物件。1但是,如果是這樣,那么它應該strh為這些部分生成單獨的 16 位指令(根據原始示例代碼,它有 16 位部分),而不是str我們看到的 32 位指令。
雖然使用區域變數似乎可以解決這個問題,但我不會依賴它,因為上面的復合文字的賦值在語意上是等效的,所以 GCC 為一個代碼序列而不是另一個代碼序列生成損壞的匯編代碼的原因不清楚。在不同的情況下(例如函式中的附加或修改代碼或可能影響優化的其他變體),GCC 也可能使用區域變數生成損壞的代碼。
我要做的是避免對 volatile 物件使用聚合。據推測,硬體暫存器在物理上更像是一個 32 位無符號整數,而不是位域結構(即使在語意上它是用位域定義的)。因此,我將暫存器定義為volatile uint32_t并在為其賦值時使用該型別。這些值可以通過位移或具有位域的結構或您喜歡的任何其他方法來準備。
不必避免使用區域變數,因為優化器應該有效地消除它們。但是,如果您既不希望更改暫存器定義也不希望使用區域變數,另一種方法是我打開的代碼:
f_ptr->value = (union foo) {
.x = 1,
.y = 7,
.z = 10,
} .value;
這準備了要存盤的值,然后使用uint32_t聯合成員而不是使用整個聯合來分配它,并在編譯器資源管理器上使用 ARM GCC 4.6.4 進行測驗(我可以在編譯器資源管理器上找到與您正在使用的最接近的匹配) ) 建議它使用最少的代碼生成單個商店:
主要的:
ldr r3, .L2
移動 r2, #175
str r2, [r3, #0]
移動 r0, #0
bx lr
.L2:
.word .LANCHOR0
.LANCHOR0 = . 0
F:
腳注
1我也認為這是一個錯誤,因為 C 標準沒有規定volatile在聯合或結構宣告上應用限定符作為應用于成員而不是整個聚合。對于陣列,它確實說限定符適用于元素,而不是整個陣列(C 2018 6.7.3 10)。對于工會或結構,它沒有這樣的措辭。
uj5u.com熱心網友回復:
您可以強制一次性寫入聚合聯合
f_ptr->value = (union foo) {
.x = 10,
.y = 20,
}.value;
// produced asm
mov r1, #10
orr r1, r1, #1310720
str r1, [r0]
bx lr
uj5u.com熱心網友回復:
您的程式中似乎不需要位域:使用uint16_t型別應該使其更簡單并生成更好的代碼:
#include <stdint.h>
union foo {
uint32_t value;
struct {
uint16_t x;
uint16_t y;
};
};
extern union foo f;
int main() {
volatile union foo *f_ptr = &f;
*f_ptr = (union foo) {
.x = 10,
.y = 20,
};
return 0;
}
arm gcc 4.6.4 linux生成的代碼,由 Godbolt Compiler Explorer 生成:
main:
ldr r3, .L2
mov r0, #0
mov r2, #10
str r0, [r3, #0]
strh r2, [r3, #0] @ movhi
mov r2, #20
strh r2, [r3, #2] @ movhi
bx lr
.L2:
.word f
代碼要簡單得多,但仍然對 32 位值執行冗余存盤:str r0, [r3, #0]一次性存盤聯合時。
對此進行調查,我嘗試了不同的方法并得到了令人驚訝的結果:使用 struct 或 union assignment 生成的代碼可能不適合記憶體映射的硬體暫存器,按元素存盤欄位似乎需要生成正確的代碼:
#include <stdint.h>
union foo {
uint32_t value;
struct {
uint16_t x;
uint16_t y;
};
};
extern union foo f;
void store_1(void) {
volatile union foo *f_ptr = &f;
*f_ptr = (union foo) {
.x = 10,
.y = 20,
};
}
void store_2(void) {
volatile union foo *f_ptr = &f;
union foo bar = { .x = 10, .y = 20, };
*f_ptr = bar;
}
void store_3(void) {
volatile union foo *f_ptr = &f;
f_ptr->x = 10;
f_ptr->y = 20;
}
int main() {
return 0;
}
此外,洗掉結構分配版本的uint32_t value;生成呼叫memcpy。
arm gcc 4.6.4 linux生成的代碼:
store_1:
ldr r3, .L2
mov r2, #0
str r2, [r3, #0]
mov r2, #10
strh r2, [r3, #0] @ movhi
mov r2, #20
strh r2, [r3, #2] @ movhi
bx lr
.L2:
.word f
store_2:
ldr r3, .L5
ldr r2, .L5 4
str r2, [r3, #0]
bx lr
.L5:
.word f
.word 1310730
store_3:
ldr r3, .L8
mov r2, #10
strh r2, [r3, #0] @ movhi
mov r2, #20
strh r2, [r3, #2] @ movhi
bx lr
.L8:
.word f
main:
mov r0, #0
bx lr
進一步的調查似乎將此問題與使用volatile union foo *f_ptr = &f;而不是將工會成員標記為volatile:
#include <stdint.h>
union foo {
uint32_t value;
struct {
volatile uint16_t x;
volatile uint16_t y;
};
};
extern union foo f;
void store_1(void) {
union foo *f_ptr = &f;
*f_ptr = (union foo) {
.x = 10,
.y = 20,
};
*f_ptr = (union foo) {
.x = 10,
.y = 20,
};
}
void store_2(void) {
union foo *f_ptr = &f;
union foo bar = { .x = 10, .y = 20, };
*f_ptr = bar;
*f_ptr = bar;
}
void store_3(void) {
union foo *f_ptr = &f;
f_ptr->x = 10;
f_ptr->y = 20;
f_ptr->x = 10;
f_ptr->y = 20;
}
生成的代碼:
store_1:
ldr r3, .L2
mov r1, #10
mov r2, #20
strh r1, [r3, #0] @ movhi
strh r2, [r3, #2] @ movhi
strh r1, [r3, #0] @ movhi
strh r2, [r3, #2] @ movhi
bx lr
.L2:
.word f
store_2:
ldr r3, .L5
ldr r2, .L5 4
str r2, [r3, #0]
bx lr
.L5:
.word f
.word 1310730
store_3:
ldr r3, .L8
mov r1, #10
mov r2, #20
strh r1, [r3, #0] @ movhi
strh r2, [r3, #2] @ movhi
strh r1, [r3, #0] @ movhi
strh r2, [r3, #2] @ movhi
bx lr
.L8:
.word f
如您所見,分配聯合不會在 中生成適當的代碼store_2,即使對value成員也進行了限定也是volatile如此。
Using the C99 compound literals seems to work correctly in store_1, generating redundant stores when the fields are qualified as volatile.
Yet I would recommend assigning the fields explicitly as in store_3, making the assignment order explicit too. If instead you want to generate a single 32-bit store, assuming it is correct for your hardware, Aki Suihkonen suggested an interesting approach.
最初的問題是編譯器如何生成代碼以將復合文字分配給結構和聯合的副作用:它首先將目標初始化為所有位為零,然后顯式存盤復合文字中指定的成員。除非目的地是volatile合格的,否則消除冗余商店。我不相信這種行為是 C 標準規定的,所以它很可能是特定于編譯器的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/406275.html
標籤:
