如何使avr gcc确信全局字节数组的内存位置是常量

如何使avr gcc确信全局字节数组的内存位置是常量,c,gcc,avr-gcc,gcc4,winavr,C,Gcc,Avr Gcc,Gcc4,Winavr,我正在为一个带有处理器的avr项目编写一个快速的“8位反转”例程。 我正在使用 GNU C(20100110)版本4.3.3(avr)/由GNU C版本3.4.5(mingw vista special r3)、GMP版本4.2.3、MPFR版本2.4.1编译 首先,我创建了一个反向字节的全局查找表(大小:0x100): 这正如预期的那样有效。这就是我打算使用的宏,只需花费5个周期: #define BITREVERSE(x) (__extension__({

我正在为一个带有处理器的avr项目编写一个快速的“8位反转”例程。 我正在使用

  • GNU C(20100110)版本4.3.3(avr)/由GNU C版本3.4.5(mingw vista special r3)、GMP版本4.2.3、MPFR版本2.4.1编译
首先,我创建了一个反向字节的全局查找表(大小:0x100):

这正如预期的那样有效。这就是我打算使用的宏,只需花费5个周期:

#define BITREVERSE(x) (__extension__({                                        \
    register uint8_t b=(uint8_t)x;                                            \
    __asm__ __volatile__ (                                                    \
        "ldi r31, hi8(table)"                                          "\n\t" \
        "mov r30, ioRegister"                                          "\n\t" \
        "lpm ioRegister, z"                                            "\n\t" \
        :[ioRegister] "+r" (b)                                                \
        :[table] "g" (BitReverseTable)                                        \
        :"r30", "r31"                                                         \
    );                                                                        \
}))
使其编译(或不编译)的代码

这就是我从编译器中得到的错误:

c:/winavr-20100110/bin/../lib/gcc/avr/4.3.3/../../../../avr/bin/as.exe -mmcu=atmega2560 -o bitreverse.o C:\Users\xxx\AppData\Local\Temp/ccCefE75.s
C:\Users\xxx\AppData\Local\Temp/ccCefE75.s: Assembler messages:
C:\Users\xxx\AppData\Local\Temp/ccCefE75.s:349: Error: constant value required
C:\Users\xxx\AppData\Local\Temp/ccCefE75.s:350: Error: constant value required
我想问题在这里:

    :[table] "g" (BitReverseTable)                                        \
在我看来,BitReverseTable是数组的内存位置,它在编译时是固定的和已知的。因此它是恒定的。 也许我需要将BitReverseTable转换成某种东西(我尝试了我能想到的任何东西)。也许我需要另一个约束(“g”是我最后一次测试)。我肯定我用了任何可能和不可能的东西。 我编写了一个汇编程序版本,它工作得很好,但是它不是内联汇编代码,而是一个适当的函数,它添加了另外6个周期(用于call和ret)

欢迎任何意见或建议

上bitreverse.c的完整源代码。
详细的编译器输出也在avr gcc(gcc)4.8.2上运行,但对我来说,它确实有一种独特的hacky回味

编辑以修复OP(Thomas)在评论中指出的问题:

  • Z
    寄存器的高位字节是
    r31
    (我交换了
    r30
    r31
  • 较新的AVR(如ATmega2560)也支持lpm r,Z(仅限较旧的AVR
    lpm r0,Z
谢谢你的修复,托马斯!我有一个ATmega2560板,但我更喜欢Teensies(部分原因是因为本地USB),所以我只编译测试代码,没有运行它进行验证。我应该提到这一点;抱歉

const unsigned char reverse_bits_table[256] __attribute__((progmem, aligned (256))) = {
      0, 128,  64, 192,  32, 160,  96, 224,  16, 144,  80, 208,  48, 176, 112, 240,
      8, 136,  72, 200,  40, 168, 104, 232,  24, 152,  88, 216,  56, 184, 120, 248,
      4, 132,  68, 196,  36, 164, 100, 228,  20, 148,  84, 212,  52, 180, 116, 244,
     12, 140,  76, 204,  44, 172, 108, 236,  28, 156,  92, 220,  60, 188, 124, 252,
      2, 130,  66, 194,  34, 162,  98, 226,  18, 146,  82, 210,  50, 178, 114, 242,
     10, 138,  74, 202,  42, 170, 106, 234,  26, 154,  90, 218,  58, 186, 122, 250,
      6, 134,  70, 198,  38, 166, 102, 230,  22, 150,  86, 214,  54, 182, 118, 246,
     14, 142,  78, 206,  46, 174, 110, 238,  30, 158,  94, 222,  62, 190, 126, 254,
      1, 129,  65, 193,  33, 161,  97, 225,  17, 145,  81, 209,  49, 177, 113, 241,
      9, 137,  73, 201,  41, 169, 105, 233,  25, 153,  89, 217,  57, 185, 121, 249,
      5, 133,  69, 197,  37, 165, 101, 229,  21, 149,  85, 213,  53, 181, 117, 245,
     13, 141,  77, 205,  45, 173, 109, 237,  29, 157,  93, 221,  61, 189, 125, 253,
      3, 131,  67, 195,  35, 163,  99, 227,  19, 147,  83, 211,  51, 179, 115, 243,
     11, 139,  75, 203,  43, 171, 107, 235,  27, 155,  91, 219,  59, 187, 123, 251,
      7, 135,  71, 199,  39, 167, 103, 231,  23, 151,  87, 215,  55, 183, 119, 247,
     15, 143,  79, 207,  47, 175, 111, 239,  31, 159,  95, 223,  63, 191, 127, 255,
};

#define USING_REVERSE_BITS \
    register unsigned char r31 asm("r31"); \
    asm volatile ( "ldi r31,hi8(reverse_bits_table)\n\t" : [r31] "=d" (r31) )

#define REVERSE_BITS(v) \
    ({ register unsigned char r30 asm("r30") = v; \
       register unsigned char ret; \
       asm volatile ( "lpm %[ret],Z\n\t" : [ret] "=r" (ret) : [r30] "d" (r30), [r31] "d" (r31) ); \
       ret; })

unsigned char reverse_bits(const unsigned char value)
{
    USING_REVERSE_BITS;
    return REVERSE_BITS(value);
}

void reverse_bits_in(unsigned char *string, unsigned char length)
{
    USING_REVERSE_BITS;

    while (length-->0) {
        *string = REVERSE_BITS(*string);
        string++;
    }
}
对于仅支持lpm r0,Z的较旧AVR,请使用

#define REVERSE_BITS(v) \
    ({ register unsigned char r30 asm("r30") = v; \
       register unsigned char ret asm("r0"); \
       asm volatile ( "lpm %[ret],Z\n\t" : [ret] "=t" (ret) : [r30] "d" (r30), [r31] "d" (r31) ); \
       ret; })
我们的想法是使用
r31
,以保持
Z
寄存器对的高位字节。使用反向位的
macro在当前作用域中定义它,使用内联汇编有两个目的:避免不必要地将表地址的低位加载到寄存器中,并确保GCC知道我们已经在其中存储了一个值(因为它是一个输出操作数),而不知道该值应该是什么,因此,希望在整个范围内保留它

REVERSE\u BITS()
宏生成结果,告诉编译器它需要寄存器
r30
中的参数,以及
使用\u REVERSE\u BITS设置的表地址高位字节
r31

听起来有点复杂,但那只是因为我不知道如何更好地解释它。这真的很简单

使用avr-gcc-4.8.2-O2-fomit帧指针-mmcu=atmega2560-S编译上述代码将生成程序集源代码。(我建议使用
-O2-fomit帧指针
) 省略注释和常规指令:

    .text

reverse_bits:
    ldi r31,hi8(reverse_bits_table)
    mov r30,r24
    lpm r24,Z
    ret

reverse_bits_in:
    mov r26,r24
    mov r27,r25
    ldi r31,hi8(reverse_bits_table)
    ldi r24,lo8(-1)
    add r24,r22
    tst r22
    breq .L2
.L8:
    ld r30,X
    lpm r30,Z
    st X+,r30
    subi r24,1
    brcc .L8
.L2:
    ret

    .section    .progmem.data,"a",@progbits
    .p2align    8
reverse_bits_table:
    .byte    0
    .byte    -128
    ; Rest of data omitted for brevity
如果您想知道,在ATmega2560上,GCC将第一个8位参数和8位函数结果都放在寄存器r24中

据我所知,第一个函数是最优的。(在仅支持
lpm r0,Z
的较旧AVR上,您可以将结果从
r0
复制到
r24

对于第二个函数,设置部分可能不是完全最优的(对于第一个函数,您可以首先执行
tst r22
breq.L2
以加快零长度数组检查),但我不确定是否可以自己编写一个更快/更短的函数;我当然可以接受

在我看来,第二个函数中的循环是最优的。它使用
r30
的方式一开始我觉得很奇怪很可怕,但后来我意识到它很有道理——使用的寄存器更少,并且以这种方式重用
r30
也没有什么坏处(即使它也是
Z
寄存器的低位部分),因为它将在下一次迭代开始时从
string
加载一个新值

请注意,在我之前的编辑中,我提到交换函数参数的顺序会产生更好的代码,但随着Thomas的添加,情况不再如此。寄存器改变了,就是这样

如果您确定始终提供大于零的
长度
,请使用

void reverse_bits_in(unsigned char *string, unsigned char length)
{
    USING_REVERSE_BITS;
    do {
        *string = REVERSE_BITS(*string);
        string++;
    } while (--length);
}
屈服

reverse_bits_in:
    mov r26,r24                      ; 1 cycle
    mov r27,r25                      ; 1 cycle
    ldi r31,hi8(reverse_bits_table)  ; 2 cycles
.L4:
    ld r30,X                         ; 2 cycles
    lpm r30,Z                        ; 3 cycles
    st X+,r30                        ; 2 cycles
    subi r22,lo8(-(-1))              ; 1 cycle
    brne .L4                         ; 2 cycles
    ret                              ; 4 cycles
这开始让我印象深刻:每个字节10个周期,设置4个周期,清理3个周期(
brne
如果没有跳转,只需要一个周期)。我在头上列出的周期计数,因此其中可能有小错误(这里或那里的周期)
r26:r27
X
,函数的第一个指针参数在
r24:r25
中提供,长度为
r22

反向位表
位于正确的部分,并正确对齐。(
.p2align 8
不对齐256字节;它指定低8位为零的对齐。)

尽管GCC因多余的寄存器移动而臭名昭著,但我真的很喜欢它上面生成的代码。当然,总有改进的余地;对于重要的代码序列,我建议尝试不同的变体,甚至更改函数参数的顺序(或在局部范围内声明循环变量),等等,然后使用
-S
编译以查看生成的代码。AVR很简单,因此比较代码序列非常容易,以确定其中一个是否明显更好。我想先删除指令和评论;这样可以更容易地阅读部件


令人回味无穷的原因是,明确地说“定义这样一个寄存器变量不会保留寄存器;在流控制确定变量的值不活动的地方,它仍然可以用于其他用途”,我只是
void reverse_bits_in(unsigned char *string, unsigned char length)
{
    USING_REVERSE_BITS;
    do {
        *string = REVERSE_BITS(*string);
        string++;
    } while (--length);
}
reverse_bits_in:
    mov r26,r24                      ; 1 cycle
    mov r27,r25                      ; 1 cycle
    ldi r31,hi8(reverse_bits_table)  ; 2 cycles
.L4:
    ld r30,X                         ; 2 cycles
    lpm r30,Z                        ; 3 cycles
    st X+,r30                        ; 2 cycles
    subi r22,lo8(-(-1))              ; 1 cycle
    brne .L4                         ; 2 cycles
    ret                              ; 4 cycles