Rust: Wrong codegen for wasm32 with opt-level >= 2

Created on 14 May 2018  Â·  11Comments  Â·  Source: rust-lang/rust

I've tried to create a small wasm wrapper around the aes-soft crate and I realized that it gives wrong results when compiled on opt-level >= 2.

Minimized test case is uploaded to https://github.com/ngg/wasm-codegen-bug.

I'm not sure if it's a bug in Rust or in LLVM.

A-LLVM C-bug O-wasm

Most helpful comment

This has now been fixed in upstream LLVM and I've backported it to our fork with https://github.com/rust-lang/llvm/pull/116, so all that's needed now is a PR to this repo to update the LLVM submodule!

All 11 comments

It looks like this is indeed a codegen bug and it didn't use to happen (doesn't happen on webassembly.studio). It's also not related to wasm-bindgen.

I've bisected which nightly caused the regression.
The last nightly it was working is rustc 1.26.0-nightly (e026b59cf 2018-03-03).
The next nightly already has this issue: rustc 1.26.0-nightly (259e4a678 2018-03-04).
The diff between these is https://github.com/rust-lang/rust/compare/e026b59cf...259e4a678, which contains https://github.com/rust-lang/rust/pull/48125, the other merged PRs seem irrelevant to this.
So it might actually be a bug in LLD.

cc @alexcrichton #48125 Potential LLD codegen bug ↑

Thanks for the report @ngg! To confirm the bug here is that the browser shows 65530 while the answer is supposed to be 65525, right?

Yeah

Yes, it gives 65525 on a Linux host target with all opt-levels, it also gives 65525 on wasm32 with opt-level = 0,1,"s","z" but gives 65530 with opt-level = 2,3 independently from the lto setting. And it seems that before switching to LLD on wasm32 it used to give 65525 with all opt-levels as well.

Somewhat minimized, removing the wasm-bindgen dependency

#![feature(lang_items)]
#![no_std]

#[lang = "panic_fmt"]
fn panic_fmt() {}

type V2 = (u16, u16);
type V4 = (u16, u16, u16, u16);
type V8 = (u16, u16, u16, u16, u16, u16, u16, u16);

fn xor2(lhs: V2, rhs: V2) -> V2 {
    let (a0, a1) = lhs;
    let (b0, b1) = rhs;
    (a0 ^ b0, a1 ^ b1)
}

fn mul2(x: V2, y: V2) -> V2 {
    let (b, a) = x;
    let (d, c) = y;
    let e = (a ^ b) & (c ^ d);
    ((b & d) ^ e, (a & c) ^ e)
}

fn split4(v: V4) -> (V2, V2) {
    let (x0, x1, x2, x3) = v;
    ((x0, x1), (x2, x3))
}

fn mul4(x: V4, y: V4) -> V4 {
    let (b, a) = split4(x);
    let (d, c) = split4(y);
    let e = xor2(a, b);
    let (x2, x3) = xor2(mul2(a, c), e);
    let (x0, x1) = xor2(mul2(b, d), e);
    (x0, x1, x2, x3)
}

fn inv4(v: V4) -> V4 {
    let a = (v.2, v.3);
    let b = (v.0, v.1);
    let d = mul2(a, b);
    let (x2, x3) = mul2(d, b);
    let (x0, x1) = mul2(d, a);
    (x0, x1, x2, x3)
}

fn split8(v: V8) -> (V4, V4) {
    let (x0, x1, x2, x3, x4, x5, x6, x7) = v;
    ((x0, x1, x2, x3), (x4, x5, x6, x7))
}

fn inv8(v: V8) -> V8 {
    let (b, a) = split8(v);
    let (x0, x1, x2, x3) = mul4(b, a);
    let (x4, x5, x6, x7) = mul4(inv4(a), b);
    (0, !x1, x2, x3, x4, !x5, !x6, x7)
}

#[no_mangle]
pub fn test() -> u16 {
    let tmp0 = (1, 1, 1, 1, 1, 1, 1, 1);
    inv8(tmp0); // command out to get correct value.

    let tmp4 = (65525, 65530, 15, 0, 15, 65520, 5, 10);
    let tmp5 = inv8(tmp4);
    tmp5.5
}

Test shim:

const fs = require('fs');
let buf = fs.readFileSync('lib.wasm');
let m = new WebAssembly.Module(buf);
let inst = new WebAssembly.Instance(m, {});
console.log(inst.exports.test());

Execute with:

rustc --target=wasm32-unknown-unknown --crate-type=cdylib -Copt-level=1 src/lib.rs
node 1.js

With opt-level ≥ 2, the output would be 15 (wrong). With opt-level ≤ 1, the output would be 65525 (correct).

I believe this is an LLVM bug after reduction, and I've opened an upstream bug report

For posterity the smallest reduction I've got so far (IR-wise) is:

#![crate_type = "cdylib"]

pub struct V4(u16, u16, u16, u16);
pub struct V8(u16, u16, u16, u16, u16, u16, u16, u16);

#[inline(never)]
fn inv4(v: &V4) -> u16 {
    v.2 ^ (v.2 & v.0)
}

fn split8(a: u16, b: u16, c: u16) -> (V4, V4) {
    (V4(0, 0, 0, a), V4(b, c, 0, 0))
}

#[inline(never)]
fn inv8(v: V8) -> u16 {
    let (_b, a) = split8(v.3, v.4, v.5);
    inv4(&a)
}

#[no_mangle]
pub extern fn test() -> u16 {
    inv8(V8(0, 0, 0, 0, 1, 65520, 0, 2))
}

This has now been fixed in upstream LLVM and I've backported it to our fork with https://github.com/rust-lang/llvm/pull/116, so all that's needed now is a PR to this repo to update the LLVM submodule!

Thanks! This is now working, even for the original aes-soft wrapper.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cuviper picture cuviper  Â·  3Comments

tikue picture tikue  Â·  3Comments

modsec picture modsec  Â·  3Comments

behnam picture behnam  Â·  3Comments

mcarton picture mcarton  Â·  3Comments