`asm("" : "+r" (myVar));` 在 AVR-GCC 中是什么意思?

问题描述 投票:0回答:1

我在别人的代码中发现了以下几行:

uint16_t someConstantFactorVariable = ...;
asm("" : "+r" (someConstantFactorVariable));

someConstantFactorVariable
位于一个经常使用的函数中,我猜测代码的作者希望它将变量永久加载到寄存器中。我对吗?使用这种方法代替变量定义中的
register
关键字有什么好处?

inline-assembly avr-gcc atmega
1个回答
0
投票

asm("" : "+r" (var))
在GCC中意味着什么?

它剥夺了关于

var
中包含什么价值的知识。

例如,采用 avr-gcc 的以下代码,该代码在静态存储中递增 64 位整数变量:

uint64_t var;

void inc_var (void)
{
    ++var;
}

使用

-Os
编译为

00000010 <inc_var>:
  10:   20 91 00 01     lds r18, 0x0100 ; 0x800100 <var>
  14:   30 91 01 01     lds r19, 0x0101 ; 0x800101 <var+0x1>
  18:   40 91 02 01     lds r20, 0x0102 ; 0x800102 <var+0x2>
  1c:   50 91 03 01     lds r21, 0x0103 ; 0x800103 <var+0x3>
  20:   60 91 04 01     lds r22, 0x0104 ; 0x800104 <var+0x4>
  24:   70 91 05 01     lds r23, 0x0105 ; 0x800105 <var+0x5>
  28:   80 91 06 01     lds r24, 0x0106 ; 0x800106 <var+0x6>
  2c:   90 91 07 01     lds r25, 0x0107 ; 0x800107 <var+0x7>
  30:   a1 e0           ldi r26, 0x01   ; 1
  32:   49 d0           rcall   .+146       ; 0xc6 <__adddi3_s8>
  34:   20 93 00 01     sts 0x0100, r18 ; 0x800100 <var>
  38:   30 93 01 01     sts 0x0101, r19 ; 0x800101 <var+0x1>
  3c:   40 93 02 01     sts 0x0102, r20 ; 0x800102 <var+0x2>
  40:   50 93 03 01     sts 0x0103, r21 ; 0x800103 <var+0x3>
  44:   60 93 04 01     sts 0x0104, r22 ; 0x800104 <var+0x4>
  48:   70 93 05 01     sts 0x0105, r23 ; 0x800105 <var+0x5>
  4c:   80 93 06 01     sts 0x0106, r24 ; 0x800106 <var+0x6>
  50:   90 93 07 01     sts 0x0107, r25 ; 0x800107 <var+0x7>
  54:   08 95           ret

由于使用直接寻址,该代码消耗 70 个字节。间接寻址会产生更紧凑的代码(为什么 avr-gcc 不首先使用间接寻址超出了本答案的范围)。

所以让我们在 C 级别使用间接寻址:

void inc_var2 (void)
{
    uint64_t *pvar = &var;
    ++ *pvar;
}

结果是生成的代码与

inc_var
中的代码相同。 编译器知道
var
正在被访问,并且仍然使用冗长的直接寻址。 我们必须使用 asm 才能从编译器中获取这些知识:

void inc_var3 (void)
{
    uint64_t *pvar = &var;
    __asm ("" : "+r" (pvar));
    ++ *pvar;
}

现在代码符合预期,只消耗 42 个字节:

0000009c <inc_var3>:
  9c:   e0 e0           ldi r30, 0x00   ; 0
  9e:   f1 e0           ldi r31, 0x01   ; 1
  a0:   20 81           ld  r18, Z
  a2:   31 81           ldd r19, Z+1    ; 0x01
  a4:   42 81           ldd r20, Z+2    ; 0x02
  a6:   53 81           ldd r21, Z+3    ; 0x03
  a8:   64 81           ldd r22, Z+4    ; 0x04
  aa:   75 81           ldd r23, Z+5    ; 0x05
  ac:   86 81           ldd r24, Z+6    ; 0x06
  ae:   97 81           ldd r25, Z+7    ; 0x07
  b0:   a1 e0           ldi r26, 0x01   ; 1
  b2:   09 d0           rcall   .+18        ; 0xc6 <__adddi3_s8>
  b4:   20 83           st  Z, r18
  b6:   31 83           std Z+1, r19    ; 0x01
  b8:   42 83           std Z+2, r20    ; 0x02
  ba:   53 83           std Z+3, r21    ; 0x03
  bc:   64 83           std Z+4, r22    ; 0x04
  be:   75 83           std Z+5, r23    ; 0x05
  c0:   86 83           std Z+6, r24    ; 0x06
  c2:   97 83           std Z+7, r25    ; 0x07
  c4:   08 95           ret

顺便说一句,GCC 知道尽管存在 ABI,但

__adddi3_s8
不会破坏 Z 寄存器,因此 Z 在函数调用中保持不变。

代码的作者希望将变量永久加载到寄存器中

事实并非如此。扩展上面的例子;当编译器了解变量的内容时,它可能会使用它。 没有任何要求该值始终保存在寄存器中:

void inc_var4 (void)
{
    uint64_t *pvar = &var;
    __asm ("" : "+r" (pvar));
    pvar = (uint64_t*) 42;
    ++ *pvar;
}

编译器将使用指针指向地址 42 的知识(在具有

__AVR_SFR_OFFSET__=0x20
的设备上):

inc_var4:
    in r18,0xa
    in r19,0xb
    in r20,0xc
    ...
    ldi r26,lo8(1)
    call __adddi3_s8
    out 0xa,r18
    out 0xb,r19
    out 0xc,r20
    ...
© www.soinside.com 2019 - 2024. All rights reserved.