Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WebAssembly] Disallow tail calls with byval arguments #125142

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

tlively
Copy link
Collaborator

@tlively tlively commented Jan 31, 2025

WebAssembly disallows tail calls with stack-allocated arguments because they tail calls leave no way to fix up the stack pointer after the call. WebAssembly also passes byval arguments on the stack, but we were not considering them when deciding whether a tail call should be allowed. Properly disallow tail calls in the presence of byval arguments and fix the existing test that should have caught this.

Fixes #124443.

WebAssembly disallows tail calls with stack-allocated arguments because
they tail calls leave no way to fix up the stack pointer after the call.
WebAssembly also passes `byval` arguments on the stack, but we were not
considering them when deciding whether a tail call should be allowed.
Properly disallow tail calls in the presence of byval arguments and fix
the existing test that should have caught this.

Fixes llvm#124443.
@llvmbot
Copy link
Member

llvmbot commented Jan 31, 2025

@llvm/pr-subscribers-backend-webassembly

Author: Thomas Lively (tlively)

Changes

WebAssembly disallows tail calls with stack-allocated arguments because they tail calls leave no way to fix up the stack pointer after the call. WebAssembly also passes byval arguments on the stack, but we were not considering them when deciding whether a tail call should be allowed. Properly disallow tail calls in the presence of byval arguments and fix the existing test that should have caught this.

Fixes #124443.


Full diff: https://github.com/llvm/llvm-project/pull/125142.diff

2 Files Affected:

  • (modified) llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp (+7)
  • (modified) llvm/test/CodeGen/WebAssembly/tailcall.ll (+4-3)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index 02db1b142a22b5..4cc06f057a5760 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -1211,6 +1211,13 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
         }
       }
     }
+    // Similarly, we cannot tail call with byval arguments.
+    for (unsigned I = 0; I < CLI.Outs.size(); ++I) {
+      const ISD::OutputArg &Out = CLI.Outs[I];
+      if (Out.Flags.isByVal() && Out.Flags.getByValSize() != 0)
+        NoTail(
+            "WebAssembly does not support tail calling with stack arguments");
+    }
   }
 
   SmallVectorImpl<ISD::InputArg> &Ins = CLI.Ins;
diff --git a/llvm/test/CodeGen/WebAssembly/tailcall.ll b/llvm/test/CodeGen/WebAssembly/tailcall.ll
index 84bd142462e37e..a1eeaf9f2e77e4 100644
--- a/llvm/test/CodeGen/WebAssembly/tailcall.ll
+++ b/llvm/test/CodeGen/WebAssembly/tailcall.ll
@@ -296,12 +296,13 @@ define i32 @mismatched_byval(ptr %x) {
 ; CHECK-NEXT:    global.set __stack_pointer, $pop7
 ; CHECK-NEXT:    i32.load $push0=, 0($0)
 ; CHECK-NEXT:    i32.store 12($1), $pop0
+; CHECK-NEXT:    i32.const $push5=, 12
+; CHECK-NEXT:    i32.add $push6=, $1, $pop5
+; CHECK-NEXT:    call $0=, quux, $pop6
 ; CHECK-NEXT:    i32.const $push3=, 16
 ; CHECK-NEXT:    i32.add $push4=, $1, $pop3
 ; CHECK-NEXT:    global.set __stack_pointer, $pop4
-; CHECK-NEXT:    i32.const $push5=, 12
-; CHECK-NEXT:    i32.add $push6=, $1, $pop5
-; CHECK-NEXT:    return_call quux, $pop6
+; CHECK-NEXT:    return $0
   %v = tail call i32 @quux(ptr byval(i32) %x)
   ret i32 %v
 }

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A function with a byval argument should be able to tail-call another function with a byval argument in the same position. See 914a399 376d7b2 .

const ISD::OutputArg &Out = CLI.Outs[I];
if (Out.Flags.isByVal() && Out.Flags.getByValSize() != 0)
NoTail(
"WebAssembly does not support tail calling with stack arguments");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message can probably more specifically mention byval arguments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was assuming that "byval argument" is not going to mean much to most developers who might see this error message.

@@ -296,12 +296,13 @@ define i32 @mismatched_byval(ptr %x) {
; CHECK-NEXT: global.set __stack_pointer, $pop7
; CHECK-NEXT: i32.load $push0=, 0($0)
; CHECK-NEXT: i32.store 12($1), $pop0
; CHECK-NEXT: i32.const $push5=, 12
; CHECK-NEXT: i32.add $push6=, $1, $pop5
; CHECK-NEXT: call $0=, quux, $pop6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that if this were actually a "matched" byval (i.e. the caller had a byval param that was passed directly to the callee's byval param) then it could work, since the caller wouldn't need to allocate space on its own stack for the byval.
I'm not sure it would be worth implementing that though.

@tlively
Copy link
Collaborator Author

tlively commented Jan 31, 2025

Thanks for the references, @efriedma-quic. I don't quite understand the thing about copying via a temporary slot, but I don't think it applies to WebAssembly because we always copy to a fresh stack slot. My mental model for how this works may be wrong, though.

@efriedma-quic
Copy link
Collaborator

Aren't byval arguments passed via the explicit stack pointer? I think you have the same issues you'd have with a conventional architecture.

@tlively
Copy link
Collaborator Author

tlively commented Jan 31, 2025

Probably, but I don't understand those issues :) Can you help explain what bad situation the double copy avoids?

I've also added some tests for edge cases we would have to handle if we tried to do the argument forwarding optimization. That optimization seems more complex than I had initially hoped, so I'd rather not tackle it in this PR.

@efriedma-quic
Copy link
Collaborator

Suppose you have a function like void g(S, S); void f(S a, S b) { return g(b, a); }. To tail-call, you have to essentially swap a and b. You use a temporary to avoid overwriting the argument before you copy it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants