是什么阻止了此 constexpr 函数的编译时评估?
What is preventing compile time evaluation of this constexpr function?
我正在研究 class 来表示微控制器 (STM32) 的一组硬件引脚。所选引脚在端口上可能是不连续的,但假定它们是有序的。例如,如果创建此 PortSegment
对象来表示 PA2、PA3 和 PA6 引脚,我希望能够进行类似 segment = 0b101u
的分配,它设置 PA2 和 PA6 并重置 PA3。
目前我还没有为不连续的引脚实现 ctor。当前的只允许表示连续的引脚,如 PA2、P3 和 PA4。然而,将压缩位(如上例中的0b101u
)映射到实际硬件位的逻辑是针对不连续的情况实现的。
我认为像 segment = 0b101u
这样的赋值主要可以在编译时计算,并且只能加载实际的硬件寄存器(BSRR
对于 STM32,它处理硬件引脚的原子设置和重置)使用预先计算的值在 运行 时间内发生。不幸的是,情况并非如此,要加载到 BSRR
中的值也在 运行 时间内计算。
这是我正在测试的代码的稍微简化和半生不熟的版本。省略端口选择(GPIOA、GPIOB 等)代码。
#include <cstdint>
volatile uint32_t BSRR {0}; // Assume it's a HW register for atomic pin access.
class PortSegment {
public:
constexpr PortSegment(uint8_t start, uint8_t end)
: selection{calculateSelection(start, end)} {}
uint16_t operator=(uint16_t setVal) const;
// operator uint16_t() const; // to be implemented later
private:
static constexpr uint16_t calculateSelection(uint8_t start, uint8_t end);
static constexpr uint16_t mapBits(uint16_t val, uint16_t selection);
uint16_t selection; // Table of used bits in the port
};
// Used in ctor
constexpr uint16_t PortSegment::calculateSelection(uint8_t start, uint8_t end)
{
uint16_t result {0};
for (unsigned i = start; i <= end; ++i) result |= (1u << i);
return result;
}
// static function
constexpr uint16_t PortSegment::mapBits(uint16_t val, uint16_t selection)
{
uint16_t result {0};
for (unsigned i = 0; i < 16; ++i) {
if (selection & 1u) {
if (val & (1u << i)) {
result |= (1u << i);
}
}
else {
val <<= 1;
}
selection >>= 1;
}
return result;
}
inline uint16_t PortSegment::operator=(uint16_t setVal) const
{
uint32_t mapped {mapBits(setVal, selection)};
BSRR = ((~mapped << 16) | mapped)
& ((static_cast<uint32_t>(selection) << 16) | selection);
return setVal;
}
int main()
{
constexpr PortSegment segment {2,5}; // Use port pins 2,3,4,5
segment = 0b1010u;
}
selection
成员变量表示端口中使用的管脚。例如,0b111100
表示使用PA2、PA3、PA4、PA5。问题在于,mapBits()
函数在编译期间未被评估。我也试图使它成为非静态成员函数,但没有任何改变。按照我的逻辑,PortSegment
class的segment
对象创建的时候,编译时就已经知道了,要加载到BSRR
的值也可以知道.但是我好像漏掉了什么。
另一个奇怪的事情我发现,如果我把mapBits()
函数中的selection >>= 1;
改成selection <<= 1;
(这对算法没有意义),mapBits()
可以计算编译时间。
这里是 code in Godbolt.
您已在 Godbolt 中将优化设置为 1 级!尝试 -O3
而不是 -O1
.
我正在研究 class 来表示微控制器 (STM32) 的一组硬件引脚。所选引脚在端口上可能是不连续的,但假定它们是有序的。例如,如果创建此 PortSegment
对象来表示 PA2、PA3 和 PA6 引脚,我希望能够进行类似 segment = 0b101u
的分配,它设置 PA2 和 PA6 并重置 PA3。
目前我还没有为不连续的引脚实现 ctor。当前的只允许表示连续的引脚,如 PA2、P3 和 PA4。然而,将压缩位(如上例中的0b101u
)映射到实际硬件位的逻辑是针对不连续的情况实现的。
我认为像 segment = 0b101u
这样的赋值主要可以在编译时计算,并且只能加载实际的硬件寄存器(BSRR
对于 STM32,它处理硬件引脚的原子设置和重置)使用预先计算的值在 运行 时间内发生。不幸的是,情况并非如此,要加载到 BSRR
中的值也在 运行 时间内计算。
这是我正在测试的代码的稍微简化和半生不熟的版本。省略端口选择(GPIOA、GPIOB 等)代码。
#include <cstdint>
volatile uint32_t BSRR {0}; // Assume it's a HW register for atomic pin access.
class PortSegment {
public:
constexpr PortSegment(uint8_t start, uint8_t end)
: selection{calculateSelection(start, end)} {}
uint16_t operator=(uint16_t setVal) const;
// operator uint16_t() const; // to be implemented later
private:
static constexpr uint16_t calculateSelection(uint8_t start, uint8_t end);
static constexpr uint16_t mapBits(uint16_t val, uint16_t selection);
uint16_t selection; // Table of used bits in the port
};
// Used in ctor
constexpr uint16_t PortSegment::calculateSelection(uint8_t start, uint8_t end)
{
uint16_t result {0};
for (unsigned i = start; i <= end; ++i) result |= (1u << i);
return result;
}
// static function
constexpr uint16_t PortSegment::mapBits(uint16_t val, uint16_t selection)
{
uint16_t result {0};
for (unsigned i = 0; i < 16; ++i) {
if (selection & 1u) {
if (val & (1u << i)) {
result |= (1u << i);
}
}
else {
val <<= 1;
}
selection >>= 1;
}
return result;
}
inline uint16_t PortSegment::operator=(uint16_t setVal) const
{
uint32_t mapped {mapBits(setVal, selection)};
BSRR = ((~mapped << 16) | mapped)
& ((static_cast<uint32_t>(selection) << 16) | selection);
return setVal;
}
int main()
{
constexpr PortSegment segment {2,5}; // Use port pins 2,3,4,5
segment = 0b1010u;
}
selection
成员变量表示端口中使用的管脚。例如,0b111100
表示使用PA2、PA3、PA4、PA5。问题在于,mapBits()
函数在编译期间未被评估。我也试图使它成为非静态成员函数,但没有任何改变。按照我的逻辑,PortSegment
class的segment
对象创建的时候,编译时就已经知道了,要加载到BSRR
的值也可以知道.但是我好像漏掉了什么。
另一个奇怪的事情我发现,如果我把mapBits()
函数中的selection >>= 1;
改成selection <<= 1;
(这对算法没有意义),mapBits()
可以计算编译时间。
这里是 code in Godbolt.
您已在 Godbolt 中将优化设置为 1 级!尝试 -O3
而不是 -O1
.