llvm / llvm/llvm-project

[WebAssembly] const local of __funcref type miscompiled at -O0: table.set fed an uninitialized i32

Open
#223,672 1 comment 1 reaction 0 assignees View on GitHub
backend:WebAssembly miscompilation
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Summary

On wasm32 at `-O0`, a `const`-qualified local of a `__funcref` function pointer type is miscompiled. Clang folds the initializer into a `ptrtoint (ptr @f to target("wasm.funcref"))` constant expression instead of emitting `llvm.wasm.ptr.to_funcref`, and the backend then feeds an uninitialized `i32` local to `table.set`. The resulting module fails validation. Dropping the `const` produces correct code.

## Reproducer

```cpp
typedef void (*__funcref Fn)();
__attribute__((import_module("env"), import_name("f"))) void f();
extern "C" void go() {
const Fn t = (Fn)f;
t();
}
```

```
clang++ --target=wasm32 -O0 -mreference-types -nostdlib -c repro.cpp -o repro.o
wasm-ld repro.o -o repro.wasm --no-entry --export=go
```

Validation (V8 via Node 24, Chrome 153 behaves the same):

```
CompileError: WebAssembly.compile(): Compiling function #1:"go" failed: table.set[1] expected type funcref, found local.get of type i32 @+104
```

## Generated code

`llvm-objdump -d repro.wasm`:

```
0000004b :
.local funcref, i32
51: 41 81 80 80 80 00 i32.const 1
57: 25 81 80 80 80 00 table.get 1
5d: 21 00 local.set 0
5f: 41 81 80 80 80 00 i32.const 1
65: 1a drop
66: 41 00 i32.const 0
68: 20 01 local.get 1
6a: 26 80 80 80 80 00 table.set 0
70: 41 00 i32.const 0
72: 11 80 80 80 80 00 80 80 80 80 00 call_indirect 0
```

Local 1 is an `i32` that is never written; the `funcref` in local 0 is never read.

`clang++ ... -S -emit-llvm`:

```llvm
define hidden void @go() #0 {
%1 = alloca target("wasm.funcref"), align 4
%2 = call target("wasm.funcref") @llvm.wasm.ptr.to_funcref(ptr @_Z1fv)
store target("wasm.funcref") %2, ptr %1, align 4
%3 = call ptr @llvm.wasm.funcref.to_ptr(target("wasm.funcref") ptrtoint (ptr @_Z1fv to target("wasm.funcref")))
call void %3()
ret void
}
```

The store to `%1` is dead; the load of `t` for the call was replaced by the constant `ptrtoint (ptr @_Z1fv to target("wasm.funcref"))`, which is not how the initializer was emitted.

## Expected

The same code as without `const`, which validates and runs:

```
0000004b :
.local funcref, funcref
4f: 41 81 80 80 80 00 i32.const 1
55: 25 81 80 80 80 00 table.get 1
5b: 21 00 local.set 0
5d: 20 00 local.get 0
5f: 21 01 local.set 1
61: 41 00 i32.const 0
63: 20 01 local.get 1
65: 26 80 80 80 80 00 table.set 0
```

## Version

```
clang version 23.1.0 (https://github.com/llvm/llvm-project ea7d852a70e8bdfaf601d6626a760f9771b2c4b4)
Target: x86_64-pc-windows-msvc (host), compiling for wasm32
```

Contributor guide

Open the contributing guide

Research direction

Start by running the provided clang++ and wasm-ld reproducer, then compare its LLVM IR and llvm-objdump output with the non-const version. Trace how the const funcref initializer becomes the shown ptrtoint expression and reaches the WebAssembly backend. Done means the generated module validates and the const and non-const cases produce equivalent valid calls.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, wasm
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.