There seems to be a issue with optimization to wasm

I found a micompiled bug.

When different optimization options are given to two LLVM IRs, the results of the program are different.

So i’ve tested a few things.

with below two files, These are ir files that call foo from main and 1 should be zero extended and return -1 ( 0-1 ).

define i32 @foo(i1 signext noundef %cond, i32 noundef %y) {
  %e = zext i1 %cond to i32
  %r = sub i32 %y, %e
  ret i32 %r
}
declare i32 @foo(i1 signext noundef, i32 noundef)

define i32 @main() #0 {
start:
    %0 = call i32 @foo(i1 1, i32 0)
    ret i32 %0
}

compiling with below options.

llc --march=wasm32 --filetype=obj temp/foo.ll -o temp/foo.o
llc --march=wasm32 --filetype=obj -O0 temp/main.ll -o temp/main.o

and linking these two object files to one binary

wasm-ld --no-entry --export-all --allow-undefined temp/foo.o temp/main.o -o temp/poc.wasm

the result is below.

(module
  (type (;0;) (func))
  (type (;1;) (func (param i32 i32) (result i32)))
  (type (;2;) (func (result i32)))
  (func $__wasm_call_ctors (type 0))
  (func $foo (type 1) (param i32 i32) (result i32)
    local.get 1
    local.get 0
    i32.add)
  (func $__original_main (type 2) (result i32)
    (local i32 i32 i32)
    i32.const 1
    local.set 0
    i32.const 0
    local.set 1
    local.get 0
    local.get 1
    call $foo
    local.set 2
    local.get 2
    return)
  (func $main (type 1) (param i32 i32) (result i32)
    (local i32)
    call $__original_main
    local.set 2
    local.get 2
    return)
  (memory (;0;) 2)
  (global $__stack_pointer (mut i32) (i32.const 66560))
  (global (;1;) i32 (i32.const 1024))
  (global (;2;) i32 (i32.const 1024))
  (global (;3;) i32 (i32.const 1024))
  (global (;4;) i32 (i32.const 66560))
  (global (;5;) i32 (i32.const 1024))
  (global (;6;) i32 (i32.const 66560))
  (global (;7;) i32 (i32.const 131072))
  (global (;8;) i32 (i32.const 0))
  (global (;9;) i32 (i32.const 1))
  (export "memory" (memory 0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "foo" (func $foo))
  (export "__original_main" (func $__original_main))
  (export "main" (func $main))
  (export "__dso_handle" (global 1))
  (export "__data_end" (global 2))
  (export "__stack_low" (global 3))
  (export "__stack_high" (global 4))
  (export "__global_base" (global 5))
  (export "__heap_base" (global 6))
  (export "__heap_end" (global 7))
  (export "__memory_base" (global 8))
  (export "__table_base" (global 9)))

the result is 0 + 1 = 1.

When compiling both files with same options,

llc --march=wasm32 --filetype=obj temp/foo.ll -o temp/foo.o
llc --march=wasm32 --filetype=obj temp/main.ll -o temp/main.o

the result binary is below.

(module
  (type (;0;) (func))
  (type (;1;) (func (param i32 i32) (result i32)))
  (type (;2;) (func (result i32)))
  (func $__wasm_call_ctors (type 0))
  (func $foo (type 1) (param i32 i32) (result i32)
    local.get 1
    local.get 0
    i32.add)
  (func $__original_main (type 2) (result i32)
    i32.const -1
    i32.const 0
    call $foo)
  (func $main (type 1) (param i32 i32) (result i32)
    call $__original_main)
  (memory (;0;) 2)
 

the result is -1 ( 0 + -1 ). (this seems to be okay.)

When i compiled above two ll files with different compilation options and linked two object file to one binary, the result is different.

I think this has some issues.
Can anyone help me with this issue?

Replace the signext with zeroext in the argument declarations?

Oh, yes. That’s work. But what I really want to say is, there’s a possible problem with doing this optimization in this situation.

I think I wrote the question ambiguously, so I rewrote the question. Thank you.

Looks like something removes zext i1 assuming it’s noop, which it’s not in this case. I think it’s either custom wasm DAG combining operation that’s at fault or perhaps some generic dag combining.

EDIT: I saw similar “wrong” transformation happen when compiled for x86 target (although with a slightly different DAG transformation), so it seems to be not wasm-specific. Perhaps some infra incorrectly tracts sign/known bits of signext arguments.
You should file an issue on GitHub if you can.

I opened the issue in [WASM] "signext" attribute leads miscompilation · Issue #63388 · llvm/llvm-project · GitHub. Thank you.

Whether the call has a “signext” flag (vs. just the declaration) makes a difference. See Compiler Explorer .