是什么阻止了这个 constexpr 函数的编译时评估?

时间:2021-06-21 08:46:15

标签: c++ c++17 stm32 constexpr constexpr-function

我正在研究一个用于表示微控制器 (STM32) 的一组硬件引脚的类。选定的引脚在端口上可能是不连续的,但假定它们是有序的。例如,如果创建此 PortSegment 对象来表示 PA2、PA3 和 PA6 引脚,我希望能够进行类似 segment = 0b101u 的分配,它设置 PA2 和 PA6 并重置 PA3。

目前我还没有为不连续的引脚实现ctor。当前的只允许表示连续的引脚,如 PA2、P3 和 PA4。但是,将压缩位(如上面示例中的 0b101u)映射到实际硬件位的逻辑是针对不连续情况实现的。

我认为像 segment = 0b101u 这样的赋值主要可以在编译时计算,并且只加载实际的硬件寄存器(STM32 的 BSRR,它处理硬件引脚的原子设置和重置)在运行时,使用预先计算的值。不幸的是,这不是发生的事情,要加载到 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() 函数在编译时没有被评估。我也试图使它成为非静态成员函数,但没有任何改变。按照我的逻辑,当segment类的PortSegment对象创建时,编译时一切都已经知道了,加载到BSRR中的值也是可以知道的。但似乎我错过了一些东西。

我发现的另一个奇怪的事情是,如果我将selection >>= 1;函数中的mapBits()改为selection <<= 1;(这对算法没有意义),可以计算出mapBits()编译时间。

这是code in Godbolt

1 个答案:

答案 0 :(得分:2)

您已在 Godbolt 中将优化设置为 1 级!尝试 -O3 而不是 -O1