禁止在 LLVM 中评估“未定义的结果”

val*_*ldo 7 c c++ clang webassembly

以下代码由clang编译到wasm平台:

uint64_t Get() const
{
    uint32_t ord = -m_Order;
    if (ord >= 64)
        return 0;

    return m_Num >> ord;
}
Run Code Online (Sandbox Code Playgroud)

编译后的 wasm 代码(启用优化)如下所示:

        i64.const 0

        local.get $l4
        i32.const 0
        local.get $p2
        i32.sub
        local.tee $l5
        i64.extend_i32_u
        i64.shr_u

        local.get $l5
        i32.const 63
        i32.gt_u
        select
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,表达式m_Num >> ord总是被求值,但最终结果将是 it 或0(第一个命令将其放在操作数堆栈上)。

我们在虚拟机上运行生成的 wasm 代码。问题是:当-m_Order大于63时,程序崩溃(即陷入困境,虚拟机不会崩溃),因为移位操作比操作数大小更多的位被认为是未定义的行为。

根据 LLVM 文档,这不是未定义的行为,它只是未定义的结果(所谓的毒害值)。

所以,我的问题是:是否有可能阻止 LLVM 进行这种优化?即不允许优化器产生未定义的结果,这些结果不应该由原始代码产生?

更新:

澄清:正如我们所发现的,WebAssembly 标准明确允许这样做。对于移位运算符,移位计数可以大于最大位数,在这种情况下,移位计数必须以最大位数为模进行处理(类似于 x86 处理器处理此问题的方式)。所以它甚至不是未定义的结果。

所以这是我们虚拟机的问题,它与标准有偏差。解决这个问题不是问题,但升级整个系统就有问题,所以我们更愿意保留当前的行为。我们需要一种解决该问题的方法。

我发现的一种解决方案是在块内调用一些第三方虚拟函数if,这将强制编译器生成正确的分支,而不是计算所有变体并通过select. 我的意思是:

uint64_t Get() const
{
    uint32_t ord = -m_Order;
    if (ord >= 64)
    {
        DummyFunction(); // this is the workaround
        return 0;
    }

    return m_Num >> ord;
}
Run Code Online (Sandbox Code Playgroud)

但我想要一个更优雅的解决方案。毕竟,LLVM 在这方面应该非常灵活。它在某处包含目标平台的确切定义,其中包括它如何处理位移位的定义。也许可以在那里进行修改。