【问题标题】:Is this an optimisation bug?这是一个优化错误吗?
【发布时间】:2011-01-22 21:47:13
【问题描述】:

这是我的编译器在汇编器中的一些输出。它是基于 GCC v3.23 的 MPLAB C30 C 编译器,用于 dsPIC33FJ128GP802,一个 16 位中等高速 DSP/MCU。

212:               inline uint16_t ror_16(uint16_t word, int num)
213:               {
 078C4  608270     and.w w1,#16,w4
 078C6  DE0204     lsr w0,w4,w4
 078C8  780101     mov.w w1,w2
 078CA  EA8102     com.w w2,w2
 078CC  EA8183     com.w w3,w3
 078CE  610170     and.w w2,#16,w2
 078D0  DD0002     sl w0,w2,w0
 078D2  700004     ior.w w0,w4,w0
214:                num &= 16; // limit to 16 shifts
215:                return (word >> num) | (word << (16 - num));
216:               }
 078D4  060000     return

我对以下内容特别感兴趣:

and.w w1,#16,w4         AND W1 with 16, storing result in W4
lsr w0,w4,w4            Logical shift right W0 by W4 times storing result in W4
mov.w w1,w2             Move W1 to W2
com.w w2,w2             Logical complement of W2 stored in W2
com.w w3,w3             Logical complement of W3 stored in W3   <-- This line is confusing me
and.w w2,#16,w2         AND W2 with 16, storing result in W2
sl w0,w2,w0             (Logical) shift left W0 left by W2 times storing result in W0
ior.w w0,w4,w0          Inclusive OR of W0 and W4 stored in W0
return                  Return from function

W0..W15 是一个由 16 个片上 16 位寄存器组成的数组。

实际上这简化为(在原始 RTL 中):

W4 := W1 & 16
W4 := W0 LSR W4
W1 := W2
W2 := COM W2
W3 := COM W3
W2 := W2 & 16
W0 := W0 SL W2
W0 := W0 | W4
return

现在我很困惑为什么当只有两个传递的参数时它会计算 W3 的补码(W0 和 W1 - 它使用 W 数组将参数传递给具有较小参数的函数的函数。)从未使用过 W3在计算中,并且永远不会返回。事实上,它甚至似乎没有数据:函数没有存储任何东西,只有被调用者才会有一些数据(尽管函数不需要保留 W0..W7 所以被调用者不应该依赖它。)为什么它包含在代码中?这只是编译器故障或错误,还是我遗漏了什么?

而且不仅仅是这段代码——我在代码的其他部分也看到了同样的奇怪之处。甚至设计用于计算诸如 16 位变量的补码之类的代码似乎也总是使用两个寄存器。它让我迷失了!

【问题讨论】:

  • 这可能是指令集设计中的一个怪癖。也许管道的工作方式是,在使用前一条指令的结果之前,您应该始终执行另一条 COM 指令。
  • @Pascal Cuoq 此处理器上没有这样的管道。也许它有一个两级流水线(因此它可以操作 fetch-decode-execute-write RISC 周期),但它不依赖于先前的指令。它唯一的延迟来自分支和指令跳过。
  • 能贴出函数的原C代码吗?将 16 位寄存器移位 16 位看起来很奇怪。

标签: gcc compiler-construction assembly


【解决方案1】:

该函数未编码为将计数限制为 16(我怀疑您的意思是 0 到 16),但将其限制为 0 或 16。

而不是

num &= 16

你可能想要

num > 16 ? (num & 15) : num

Re: 问题,既然函数是内联的,只能看用在什么地方来回答。也许 W3 用于周围代码中的某些内容。或者它可能是一个“错误”,但它只具有性能,而不是正确性和影响。

如果 num 只能是 0 或 16(如您的代码中所示),则 (16 - num) 也只能是 16 或 0,这就是 C30 可以使用补码和掩码进行“减法”的原因。

仅供参考,当我不内联时,在 C30 中我得到:

34:                uint16_t ror_16(uint16_t word, int num)
35:                {
 05AF4  608170     and.w 0x0002,#16,0x0004
 05AF6  DE0102     lsr 0x0000,0x0004,0x0004
 05AF8  EA8081     com.w 0x0002,0x0002
 05AFA  6080F0     and.w 0x0002,#16,0x0002
 05AFC  DD0001     sl 0x0000,0x0002,0x0000
 05AFE  700002     ior.w 0x0000,0x0004,0x0000
36:                    num &= 16; // limit to 16 shifts
37:                    return (word >> num) | (word << (16 - num));
38:                }
 05B00  060000     return

我可能会将其编码为

34:                uint16_t ror_16(uint16_t word, int num)
35:                {
 05AF4  780100     mov.w 0x0000,0x0004
36:                    num &= 15; // mod 16
 05AF6  60806F     and.w 0x0002,#15,0x0000
37:                    return (num == 0) ? word : ((word >> num) | (word << (16 - num)));
 05AF8  320004     bra z, 0x005b02
 05AFA  DE1080     lsr 0x0004,0x0000,0x0002
 05AFC  100070     subr.w 0x0000,#16,0x0000
 05AFE  DD1000     sl 0x0004,0x0000,0x0000
 05B00  708100     ior.w 0x0002,0x0000,0x0004
38:                }
 05B02  780002     mov.w 0x0004,0x0000
 05B04  060000     return

【讨论】:

  • 不,这是一种计算模数的快速方法。因为我认为如果数字大于 16,处理器会重置。虽然这可能会导致不正确的数据,但它不会重置/崩溃处理器。 W3用于周边代码,但没有固定用途;有关详细信息,请参阅我的问题。而且我担心编译器会忽略内联指令。有没有它的代码都是一样的。
  • 你可以用 &= 15 得到一个快速的模 16,但是你想要模 17 不能用 &;但是,由于旋转 16 与旋转 0 相同,因此 &= 15 实际上是您可能想要的。
  • 也许您的编译器较新并且有错误修复。你用的是哪个版本?
  • 语言工具版本:pic30-as.exe v3.24、pic30-gcc.exe v3.24、pic30-ld.exe v3.24、pic30-ar.exe v3.24
  • @Doug Currie,我有 v3.23,所以我似乎落后了一个版本。可能想尽快升级。
猜你喜欢
  • 2018-09-20
  • 1970-01-01
  • 2017-08-31
  • 2013-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-18
  • 2012-03-25
相关资源
最近更新 更多