[WebAssembly] ISel emits invalid call x@GOT instead of call x when devirtualizing indirect calls in PIC mode
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
Note: This bug was produced using an AI agent on a downstream emscripten issue: https://github.com/emscripten-core/emscripten/issues/27525
In WebAssembly PIC mode (`-relocation-model=pic`), devirtualized indirect function calls (e.g. indirect calls through function pointers loaded from memory that `DAGCombiner` optimizes to direct function calls) cause the ISel backend to emit `call x@GOT` with relocation `R_WASM_GLOBAL_INDEX_LEB` instead of `call x` with `R_WASM_FUNCTION_INDEX_LEB`.
When linked by `wasm-ld`, `R_WASM_GLOBAL_INDEX_LEB` resolves to a WebAssembly Global Index rather than a Function Index. At runtime or during post-link `wasm-opt` passes, calling a global index causes evaluation stack corruption (`popping from empty stack` error).
*Relates to Emscripten issue:* https://github.com/emscripten-core/emscripten/issues/27525
---
## Minimal Repro (`repro.ll`)
```llvm
target triple = "wasm32-unknown-emscripten"
@t = global ptr null, align 4
define void @o() {
entry:
store ptr @x, ptr @t, align 4
%0 = load ptr, ptr @t, align 4
tail call void %0()
ret void
}
declare void @x()
```
## Command
```bash
llc -march=wasm32 -relocation-model=pic -O1 repro.ll -o -
```
## Actual Output
```wasm
o:
.functype o () -> ()
.local i32
global.get x@GOT
local.set 0
global.get t@GOT
local.get 0
i32.store 0
call x@GOT # <--- BUG: invalid call target!
end_function
```
## Expected Output
```wasm
o:
.functype o () -> ()
.local i32
global.get x@GOT
local.set 0
global.get t@GOT
local.get 0
i32.store 0
call x # <--- Correct direct call target
end_function
```
---
## Root Cause Details
1. Initial call lowering creates `WebAssemblyISD::CALL` wrapping a `load` node.
2. `DAGCombiner` devirtualizes the `load` node to `GlobalAddressSDNode` `@x`.
3. During DAG Legalization in `-fPIC` mode, `LowerGlobalAddress` lowers `GlobalAddressSDNode` `@x` into `WebAssemblyISD::Wrapper` wrapping `TargetGlobalAddressSDNode` with `TargetFlags = WebAssemblyII::MO_GOT`.
4. In `WebAssemblyISelDAGToDAG.cpp`, when matching `WebAssemblyISD::CALL` / `RET_CALL`, the instruction selector strips the `WebAssemblyISD::Wrapper` node for function targets, but retains the underlying `TargetGlobalAddressSDNode` which still carries `TargetFlags = MO_GOT`.
5. `MCInstLower` sees `MO_GOT` on the call target operand of `CALL_PARAMS` and lowers it to `call x@GOT`, attaching `MCSymbolRefExpr::VK_WASM_GOT` and generating relocation `R_WASM_GLOBAL_INDEX_LEB`.
---
## Suggested Fix
In `llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp`, when stripping `WebAssemblyISD::Wrapper` from operand 1 (the call target) of a `WebAssemblyISD::CALL` or `WebAssemblyISD::RET_CALL` node, reset/clear the target flags to `WebAssemblyII::MO_NO_FLAG` if it points to a `Function`:
```diff
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
@@ -441,8 +441,16 @@ void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
SDValue NewOp = Op->getOperand(0);
if (auto *GlobalOp = dyn_cast(NewOp.getNode())) {
if (isa(
- GlobalOp->getGlobal()->stripPointerCastsAndAliases()))
- Op = NewOp;
+ GlobalOp->getGlobal()->stripPointerCastsAndAliases())) {
+ if (GlobalOp->getTargetFlags() != WebAssemblyII::MO_NO_FLAG) {
+ Op = CurDAG->getTargetGlobalAddress(
+ GlobalOp->getGlobal(), SDLoc(GlobalOp),
+ GlobalOp->getValueType(0), GlobalOp->getOffset(),
+ WebAssemblyII::MO_NO_FLAG);
+ } else {
+ Op = NewOp;
+ }
+ }
} else if (isa(NewOp.getNode())) {
Op = NewOp;
}
```
Contributor guide
Research direction
Start in llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp, focusing on CALL and RET_CALL selection and how Wrapper operands are stripped. Run llc -march=wasm32 -relocation-model=pic -O1 repro.ll -o - using the provided minimal repro, then verify the call target uses the function relocation and emits call x rather than call x@GOT.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, wasm
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100