【问题标题】:"Invalid symbol redefinition" in inline ASM on LLVMLLVM 上的内联 ASM 中的“无效符号重新定义”
【发布时间】:2013-01-08 11:49:38
【问题描述】:

我在 Xcode (4.5.2) 中有一个使用 Debug 配置构建良好的项目。然而,既然我已经切换到构建发布配置,我遇到了一个问题:我的一个内联汇编函数得到了错误Invalid symbol redefinition。谷歌搜索该错误消息会发现一些人遇到了该编译器错误,但没有关于其含义的信息。这是函数,错误行注释:

inline int MulDivAdd(int nNumber,
                int nNumerator,
                int nDenominator,
                int nToAdd)
{
    int nRet;

    __asm__ __volatile__ (
        "mov    %4,     %%ecx   \n"
        "mov    %1,     %%eax   \n"
        "mull   %2              \n"
        "cmp    $0,     %%ecx   \n"
        "jl     __sub           \n"
        "addl   %%ecx,  %%eax   \n"
        "adc    $0,     %%edx   \n"
        "jmp    __div           \n"
    "__sub:                     \n"    // "Invalid symbol redefinition"
        "neg    %%ecx           \n"
        "subl   %%ecx,  %%eax   \n"
        "sbb    $0,     %%edx   \n"
    "__div:                     \n"    // "Invalid symbol redefinition"
        "divl   %3              \n"
        "mov    %%eax,  %0      \n"

        :   "=m"    (nRet)
        :   "m"     (nNumber),
            "m"     (nNumerator),
            "m"     (nDenominator),
            "m"     (nToAdd)
        :   "eax", "ecx", "edx"

    );

    return nRet;
}

我尝试将__sub 替换为__sbt,因为我认为__sub 可能是受保护的名称,但事实并非如此。我不明白为什么这只发生在 Release 中 - 可能是由于优化吗?

【问题讨论】:

  • 如果你用 C 写这个(使用int64_t)你会得到更好的代码生成。该分支是完全没有必要的(它应该是一个符号扩展和添加)。我还对使用带符号数据的无符号乘法指令 (mull) 感到困扰。虽然对于相关程序可能无关紧要,但它可能是一个错误。
  • @StephenCanon 那么这个俏皮话和这个问题有什么关系呢?
  • 这不需要是volatile。它只需要运行以产生输出。 (并且volatile 并没有阻止它内联到多个地方,从而导致问题)。但是斯蒂芬是对的,这对于强制内存操作数的效率来说看起来很糟糕。在这里使用内联汇编的唯一原因是如果您知道 64b/32b => 32b div 不会溢出商和错误。 gccl/clang 仍然不知道如何利用它并使用单个 idiv 而不是为 64b/64b 划分调用 libgcc 辅助函数。

标签: c++ assembly llvm clang gnu-assembler


【解决方案1】:

使用本地标签,例如1:2:,以及jxx 1fjxx 1b。需要指定跳跃的方向(f 用于向前或b 用于向后)。所以你的代码应该是这样的:

__asm__ __volatile__ (
    "mov    %4,     %%ecx   \n"
    "mov    %1,     %%eax   \n"
    "mull   %2              \n"
    "cmp    $0,     %%ecx   \n"
    "jl     1f              \n"
    "addl   %%ecx,  %%eax   \n"
    "adc    $0,     %%edx   \n"
    "jmp    2f              \n"
"1:                         \n"   
    "neg    %%ecx           \n"
    "subl   %%ecx,  %%eax   \n"
    "sbb    $0,     %%edx   \n"
"2:                         \n"   
    "divl   %3              \n"
    "mov    %%eax,  %0      \n"
)

纯粹由数字组成的符号是“函数的局部”。由于“内联”意味着代码在物理上是重复的,因此您获得多个符号定义的原因是您的符号确实以“全局”方式定义了多次。

当然,如果你有一个调试版本,它通常意味着“没有内联”,所以内联函数不是内联的,符号只声明一次,它“工作”。

[我对这与编译器自己会做什么的效率有点怀疑——我认为至少考虑对某些输入使用寄存器会提高效率]。

【讨论】:

  • 感谢您的快速回答!至于效率,这是我一直在移植的东西,所以我只是将原始的 Intel 语法 ASM 转换为 GAS。不过我以后可能会尝试优化它。
  • 我很想先将它重写为 C,然后看看情况如何。 [我不能 100% 确定它的作用,但它看起来像是大量数学函数的一部分——但这可能是完全错误的]
  • 您可以解释一下跳转目标上的fb 后缀是什么意思 =)
  • @Mats- “使用数字标签...” - 我认为这些被称为 本地标签。见Labels in GCC inline assembly
  • 它们实际上并不是函数的本地。如果您重新排列代码而不更正标签引用中的forward 和back 方向,则可以跳转到inline-asm 块的前一个副本中的1:。并且 gas 不支持 x86 目标的 1$: 有限范围本地标签名称。供参考:the gas manual.
猜你喜欢
  • 2021-10-10
  • 2020-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-27
  • 1970-01-01
  • 2015-05-24
相关资源
最近更新 更多