https://github.com/alexcrichton updated 
https://github.com/llvm/llvm-project/pull/200076

>From 5fb29c53d4bf66a8036e3c3e35bbc1c05f007bbb Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Wed, 27 May 2026 13:25:52 -0700
Subject: [PATCH 01/10] [WebAssembly] Add a new `wasm_multivalue` calling
 convention

This is an implementation of WebAssembly/tool-conventions#268 here in
LLVM. This adds a new calling convention to Clang, named
`wasm_multivalue`, which is intended to be used on WebAssembly targets
to configure multiple return values and slightly tweak the ABI. Changes
here are:

* Parsing/validation of `__attribute__((wasm_multivalue))`. Note that
  validation here means that it's not only well-formed but on wasm
  targets the `multivalue` target feature is additionally enabled.
* Clang-level ABI adjustments for the `wasm_multivalue` calling
  convention. These are defined by WebAssembly/tool-conventions#268 and
  notably includes expanding structs with exactly 2 scalar fields in
  parameter-position and directly returning structs with any number of
  scalar fields in return-position.
* A new `wasm_multivaluecc` keyword/calling convention for LLVM IR. This
  is what Clang lowers to when using the `wasm_multivalue` calling
  convention.
* Adjustments at the LLVM ABI layer to support returning multiple values
  with the `wasm_multivaluecc` calling convention.

My goal after this would be to start integrating this into Rust next,
under and unstable feature, and then further continue
testing/vetting/etc for component model usage.
---
 clang/include/clang/Basic/Attr.td             |  6 ++
 clang/include/clang/Basic/AttrDocs.td         | 15 ++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  5 +-
 clang/include/clang/Basic/Specifiers.h        |  1 +
 clang/lib/AST/ItaniumMangle.cpp               |  1 +
 clang/lib/AST/Type.cpp                        |  3 +
 clang/lib/AST/TypePrinter.cpp                 |  6 ++
 clang/lib/Basic/Targets/WebAssembly.h         |  1 +
 clang/lib/CodeGen/CGCall.cpp                  |  2 +
 clang/lib/CodeGen/CGDebugInfo.cpp             |  2 +
 clang/lib/CodeGen/Targets/WebAssembly.cpp     | 74 ++++++++++++++++--
 clang/lib/Sema/SemaDeclAttr.cpp               | 13 ++++
 clang/lib/Sema/SemaType.cpp                   |  5 +-
 .../CodeGen/WebAssembly/wasm-multivalue-abi.c | 77 +++++++++++++++++++
 .../WebAssembly/wasm-multivalue-functype.c    | 45 +++++++++++
 clang/test/Sema/attr-wasm-multivalue.c        | 33 ++++++++
 llvm/include/llvm/AsmParser/LLToken.h         |  1 +
 llvm/include/llvm/BinaryFormat/Dwarf.def      |  1 +
 llvm/include/llvm/IR/CallingConv.h            |  4 +
 llvm/lib/AsmParser/LLLexer.cpp                |  1 +
 llvm/lib/AsmParser/LLParser.cpp               |  4 +
 llvm/lib/IR/AsmWriter.cpp                     |  3 +
 llvm/lib/IR/Function.cpp                      |  1 +
 .../GISel/WebAssemblyCallLowering.cpp         |  5 +-
 .../WebAssembly/WebAssemblyISelLowering.cpp   |  8 +-
 .../WebAssemblyMachineFunctionInfo.cpp        |  2 +-
 .../WebAssemblyRuntimeLibcallSignatures.cpp   | 26 +++----
 .../WebAssembly/WebAssemblyUtilities.cpp      | 13 +++-
 .../Target/WebAssembly/WebAssemblyUtilities.h | 12 ++-
 .../CodeGen/WebAssembly/wasm-multivalue-cc.ll | 38 +++++++++
 30 files changed, 373 insertions(+), 35 deletions(-)
 create mode 100644 clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
 create mode 100644 clang/test/CodeGen/WebAssembly/wasm-multivalue-functype.c
 create mode 100644 clang/test/Sema/attr-wasm-multivalue.c
 create mode 100644 llvm/test/CodeGen/WebAssembly/wasm-multivalue-cc.ll

diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index 70b5773f95b08..42a68ff02975d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3559,6 +3559,12 @@ def M68kRTD: DeclOrTypeAttr {
   let Documentation = [M68kRTDDocs];
 }
 
+def WasmMultivalue : DeclOrTypeAttr,
+                     TargetSpecificAttr<TargetWebAssembly> {
+  let Spellings = [Clang<"wasm_multivalue">];
+  let Documentation = [WasmMultivalueDocs];
+}
+
 def PreserveNone : DeclOrTypeAttr,
                    
TargetSpecificAttr<TargetArch<!listconcat(TargetAArch64.Arches, 
TargetAnyX86.Arches)>> {
   let Spellings = [Clang<"preserve_none">];
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 87b9053be7cb6..dafba0108506d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3732,6 +3732,21 @@ using the `rtd` instruction.
   }];
 }
 
+def WasmMultivalueDocs : Documentation {
+  let Category = DocCatCallingConvs;
+  let Content = [{
+On WebAssembly targets, this attribute selects the ``wasm-multivalue`` calling
+convention as defined in the WebAssembly/tool-conventions repository. Relative
+to the default calling convention this takes advantage of the multi-value
+proposal in its ABI definition.
+
+This calling convention requires the ``multivalue`` target feature. Using the
+attribute without this feature enabled is a compile-time error. Enabling
+``multivalue`` does not change the default calling convention; this attribute
+must be used to opt in to the new behavior on a per-function basis.
+  }];
+}
+
 def DocCatConsumed : DocumentationCategory<"Consumed Annotation Checking"> {
   let Content = [{
 Clang supports additional attributes for checking basic resource management
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 077aace321264..db24923e40b8d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13828,7 +13828,7 @@ def err_builtin_pass_in_regs_non_class : Error<
   "argument %0 is not an unqualified class type">;
 
 
-// WebAssembly reference type and table diagnostics.
+// WebAssembly-related diagnostics.
 def err_wasm_reference_pr : Error<
   "%select{pointer|reference}0 to WebAssembly reference type is not allowed">;
 def err_wasm_ca_reference : Error<
@@ -13871,6 +13871,9 @@ def 
err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union
     : Error<"not supported with the multivalue ABI for "
             "function pointers with a struct/union as %select{return "
             "value|parameter}0">;
+def err_wasm_multivalue_requires_feature : Error<
+  "the 'wasm_multivalue' calling convention requires the 'multivalue' target "
+  "feature to be enabled">;
 
 // OpenACC diagnostics.
 def warn_acc_routine_unimplemented
diff --git a/clang/include/clang/Basic/Specifiers.h 
b/clang/include/clang/Basic/Specifiers.h
index 8da6fd4cf454a..b50deb91eef2c 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -313,6 +313,7 @@ namespace clang {
     CC_RISCVVLSCall_16384, // __attribute__((riscv_vls_cc(16384)))
     CC_RISCVVLSCall_32768, // __attribute__((riscv_vls_cc(32768)))
     CC_RISCVVLSCall_65536, // __attribute__((riscv_vls_cc(65536)))
+    CC_WasmMultivalue,     // __attribute__((wasm_multivalue))
   };
 
   /// Checks whether the given calling convention supports variadic
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 1cb6fa05f22ac..743dc390866d6 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -3534,6 +3534,7 @@ StringRef 
CXXNameMangler::getCallingConvQualifierName(CallingConv CC) {
   case CC_PreserveMost:
   case CC_PreserveAll:
   case CC_M68kRTD:
+  case CC_WasmMultivalue:
   case CC_PreserveNone:
   case CC_RISCVVectorCall:
 #define CC_VLS_CASE(ABI_VLEN) case CC_RISCVVLSCall_##ABI_VLEN:
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 96a398aa21dad..fa66c146b1d87 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3741,6 +3741,8 @@ StringRef FunctionType::getNameForCallConv(CallingConv 
CC) {
     return "preserve_all";
   case CC_M68kRTD:
     return "m68k_rtd";
+  case CC_WasmMultivalue:
+    return "wasm_multivalue";
   case CC_PreserveNone:
     return "preserve_none";
     // clang-format off
@@ -4552,6 +4554,7 @@ bool AttributedType::isCallingConv() const {
   case attr::PreserveMost:
   case attr::PreserveAll:
   case attr::M68kRTD:
+  case attr::WasmMultivalue:
   case attr::PreserveNone:
   case attr::RISCVVectorCC:
   case attr::RISCVVLSCC:
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 80f5b90ba35c4..273e12168ac97 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1173,6 +1173,9 @@ void TypePrinter::printFunctionAfter(const 
FunctionType::ExtInfo &Info,
     case CC_M68kRTD:
       OS << " __attribute__((m68k_rtd))";
       break;
+    case CC_WasmMultivalue:
+      OS << " __attribute__((wasm_multivalue))";
+      break;
     case CC_PreserveNone:
       OS << " __attribute__((preserve_none))";
       break;
@@ -2081,6 +2084,9 @@ void TypePrinter::printAttributedAfter(const 
AttributedType *T,
   case attr::M68kRTD:
     OS << "m68k_rtd";
     break;
+  case attr::WasmMultivalue:
+    OS << "wasm_multivalue";
+    break;
   case attr::RISCVVectorCC:
     OS << "riscv_vector_cc";
     break;
diff --git a/clang/lib/Basic/Targets/WebAssembly.h 
b/clang/lib/Basic/Targets/WebAssembly.h
index 6085197498163..165acaabe96df 100644
--- a/clang/lib/Basic/Targets/WebAssembly.h
+++ b/clang/lib/Basic/Targets/WebAssembly.h
@@ -184,6 +184,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyTargetInfo : 
public TargetInfo {
     switch (CC) {
     case CC_C:
     case CC_Swift:
+    case CC_WasmMultivalue:
       return CCCR_OK;
     case CC_SwiftAsync:
       return CCCR_Error;
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 2468394929360..c45492674acdc 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -103,6 +103,8 @@ unsigned 
CodeGenTypes::ClangCallConvToLLVMCallConv(CallingConv CC) {
     return llvm::CallingConv::SwiftTail;
   case CC_M68kRTD:
     return llvm::CallingConv::M68k_RTD;
+  case CC_WasmMultivalue:
+    return llvm::CallingConv::WASM_Multivalue;
   case CC_PreserveNone:
     return llvm::CallingConv::PreserveNone;
     // clang-format off
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp 
b/clang/lib/CodeGen/CGDebugInfo.cpp
index 0d45df02a2a21..090741c1ff992 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -1838,6 +1838,8 @@ static unsigned getDwarfCC(CallingConv CC) {
     return llvm::dwarf::DW_CC_LLVM_X86RegCall;
   case CC_M68kRTD:
     return llvm::dwarf::DW_CC_LLVM_M68kRTD;
+  case CC_WasmMultivalue:
+    return llvm::dwarf::DW_CC_LLVM_WasmMultivalue;
   case CC_PreserveNone:
     return llvm::dwarf::DW_CC_LLVM_PreserveNone;
   case CC_RISCVVectorCall:
diff --git a/clang/lib/CodeGen/Targets/WebAssembly.cpp 
b/clang/lib/CodeGen/Targets/WebAssembly.cpp
index ebe996a4edd8d..8942e3e78b6de 100644
--- a/clang/lib/CodeGen/Targets/WebAssembly.cpp
+++ b/clang/lib/CodeGen/Targets/WebAssembly.cpp
@@ -28,17 +28,19 @@ class WebAssemblyABIInfo final : public ABIInfo {
       : ABIInfo(CGT), defaultInfo(CGT), Kind(Kind) {}
 
 private:
-  ABIArgInfo classifyReturnType(QualType RetTy) const;
-  ABIArgInfo classifyArgumentType(QualType Ty) const;
+  ABIArgInfo classifyReturnType(QualType RetTy, llvm::CallingConv::ID CC) 
const;
+  ABIArgInfo classifyArgumentType(QualType Ty, llvm::CallingConv::ID CC) const;
 
   // DefaultABIInfo's classifyReturnType and classifyArgumentType are
   // non-virtual, but computeInfo and EmitVAArg are virtual, so we
   // overload them.
   void computeInfo(CGFunctionInfo &FI) const override {
+    llvm::CallingConv::ID CC = FI.getCallingConvention();
     if (!getCXXABI().classifyReturnType(FI))
-      FI.getReturnInfo() = classifyReturnType(FI.getReturnType());
+      FI.getReturnInfo() =
+          classifyReturnType(FI.getReturnType(), CC);
     for (auto &Arg : FI.arguments())
-      Arg.info = classifyArgumentType(Arg.type);
+      Arg.info = classifyArgumentType(Arg.type, CC);
   }
 
   RValue EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, QualType Ty,
@@ -95,8 +97,54 @@ class WebAssemblyTargetCodeGenInfo final : public 
TargetCodeGenInfo {
   }
 };
 
+/// Count the number of "scalar fields" in the given record type, as defined by
+/// WebAssembly/tool-conventions for the "wasm-multivalue" calling convention
+/// primarily. A scalar field is a field that recursively, through nested
+/// structs, unions, and arrays, contains just a single scalar value.
+///
+/// Returns the number of scalar fields, or std::nullopt if the record contains
+/// a field that is not a scalar field (e.g., a sub-aggregate with multiple
+/// scalars, a bit-field, or a flexible array member).
+///
+/// Note that this is similar to `isSingleElementStruct` in structure.
+static std::optional<unsigned> countScalarFields(ASTContext &Context, QualType 
T) {
+  const auto *RD = T->getAsRecordDecl();
+  if (!RD)
+    return std::nullopt;
+  if (RD->hasFlexibleArrayMember())
+    return std::nullopt;
+
+  unsigned Count = 0;
+
+  // Check bases first for C++ records.
+  if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
+    for (const auto &Base : CXXRD->bases()) {
+      auto SubCount = countScalarFields(Context, Base.getType());
+      if (!SubCount)
+        return std::nullopt;
+      Count += *SubCount;
+    }
+  }
+
+  for (const auto *FD : RD->fields()) {
+    if (FD->isBitField())
+      return std::nullopt;
+    if (isEmptyField(Context, FD, true))
+      continue;
+
+    QualType T = FD->getType();
+    if (isAggregateTypeForABI(T) && !isSingleElementStruct(T, Context))
+      return std::nullopt;
+
+    ++Count;
+  }
+
+  return Count;
+}
+
 /// Classify argument of given type \p Ty.
-ABIArgInfo WebAssemblyABIInfo::classifyArgumentType(QualType Ty) const {
+ABIArgInfo WebAssemblyABIInfo::classifyArgumentType(QualType Ty,
+                                                    llvm::CallingConv::ID CC) 
const {
   Ty = useFirstFieldIfTransparentUnion(Ty);
 
   if (isAggregateTypeForABI(Ty)) {
@@ -113,6 +161,12 @@ ABIArgInfo 
WebAssemblyABIInfo::classifyArgumentType(QualType Ty) const {
     // though watch out for things like bitfields.
     if (const Type *SeltTy = isSingleElementStruct(Ty, getContext()))
       return ABIArgInfo::getDirect(CGT.ConvertType(QualType(SeltTy, 0)));
+    // For the wasm-multivalue calling convention, structs with exactly two
+    // scalar fields are passed directly as two arguments.
+    if (CC == llvm::CallingConv::WASM_Multivalue) {
+      if (auto N = countScalarFields(getContext(), Ty); N && *N == 2)
+        return ABIArgInfo::getExpand();
+    }
     // For the experimental multivalue ABI, fully expand all other aggregates
     if (Kind == WebAssemblyABIKind::ExperimentalMV) {
       const auto *RD = Ty->castAsRecordDecl();
@@ -132,7 +186,8 @@ ABIArgInfo 
WebAssemblyABIInfo::classifyArgumentType(QualType Ty) const {
   return defaultInfo.classifyArgumentType(Ty);
 }
 
-ABIArgInfo WebAssemblyABIInfo::classifyReturnType(QualType RetTy) const {
+ABIArgInfo WebAssemblyABIInfo::classifyReturnType(QualType RetTy,
+                                                  llvm::CallingConv::ID CC) 
const {
   if (isAggregateTypeForABI(RetTy)) {
     // Records with non-trivial destructors/copy-constructors should not be
     // returned by value.
@@ -145,6 +200,13 @@ ABIArgInfo WebAssemblyABIInfo::classifyReturnType(QualType 
RetTy) const {
       // ABIArgInfo::getDirect().
       if (const Type *SeltTy = isSingleElementStruct(RetTy, getContext()))
         return ABIArgInfo::getDirect(CGT.ConvertType(QualType(SeltTy, 0)));
+      // For the wasm-multivalue calling convention, structs whose fields are
+      // (recursively) scalars are returned directly via the multivalue
+      // proposal.
+      if (CC == llvm::CallingConv::WASM_Multivalue) {
+        if (auto N = countScalarFields(getContext(), RetTy); N && *N > 0)
+          return ABIArgInfo::getDirect();
+      }
       // For the experimental multivalue ABI, return all other aggregates
       if (Kind == WebAssemblyABIKind::ExperimentalMV)
         return ABIArgInfo::getDirect();
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index ae04d3855f01c..ac21210bd7112 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5543,6 +5543,9 @@ static void handleCallConvAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   case ParsedAttr::AT_M68kRTD:
     D->addAttr(::new (S.Context) M68kRTDAttr(S.Context, AL));
     return;
+  case ParsedAttr::AT_WasmMultivalue:
+    D->addAttr(::new (S.Context) WasmMultivalueAttr(S.Context, AL));
+    return;
   case ParsedAttr::AT_PreserveNone:
     D->addAttr(::new (S.Context) PreserveNoneAttr(S.Context, AL));
     return;
@@ -5814,6 +5817,15 @@ bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, 
CallingConv &CC,
   case ParsedAttr::AT_M68kRTD:
     CC = CC_M68kRTD;
     break;
+  case ParsedAttr::AT_WasmMultivalue:
+    CC = CC_WasmMultivalue;
+    if (Context.getTargetInfo().getTriple().isWasm() &&
+        !Context.getTargetInfo().hasFeature("multivalue")) {
+      Attrs.setInvalid();
+      Diag(Attrs.getLoc(), diag::err_wasm_multivalue_requires_feature);
+      return true;
+    }
+    break;
   case ParsedAttr::AT_PreserveNone:
     CC = CC_PreserveNone;
     break;
@@ -8068,6 +8080,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, 
const ParsedAttr &AL,
   case ParsedAttr::AT_PreserveNone:
   case ParsedAttr::AT_RISCVVectorCC:
   case ParsedAttr::AT_RISCVVLSCC:
+  case ParsedAttr::AT_WasmMultivalue:
     handleCallConvAttr(S, D, AL);
     break;
   case ParsedAttr::AT_DeviceKernel:
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 44ac4f6630690..2811cb0ee95d4 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -143,7 +143,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const 
ParsedAttr &attr,
   case ParsedAttr::AT_M68kRTD:                                                 
\
   case ParsedAttr::AT_PreserveNone:                                            
\
   case ParsedAttr::AT_RISCVVectorCC:                                           
\
-  case ParsedAttr::AT_RISCVVLSCC
+  case ParsedAttr::AT_RISCVVLSCC:                                              
\
+  case ParsedAttr::AT_WasmMultivalue
 
 // Function type attributes.
 #define FUNCTION_TYPE_ATTRS_CASELIST                                           
\
@@ -7803,6 +7804,8 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr 
&Attr) {
     return createSimpleAttr<PreserveAllAttr>(Ctx, Attr);
   case ParsedAttr::AT_M68kRTD:
     return createSimpleAttr<M68kRTDAttr>(Ctx, Attr);
+  case ParsedAttr::AT_WasmMultivalue:
+    return createSimpleAttr<WasmMultivalueAttr>(Ctx, Attr);
   case ParsedAttr::AT_PreserveNone:
     return createSimpleAttr<PreserveNoneAttr>(Ctx, Attr);
   case ParsedAttr::AT_RISCVVectorCC:
diff --git a/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c 
b/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
new file mode 100644
index 0000000000000..5194616a13733
--- /dev/null
+++ b/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -triple wasm32-unknown-unknown -target-feature +multivalue \
+// RUN:   %s -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 -triple wasm64-unknown-unknown -target-feature +multivalue \
+// RUN:   %s -emit-llvm -o - | FileCheck %s
+
+// Verify that the `wasm_multivalue` calling convention produces the function
+// signatures described in the WebAssembly tool conventions PR.
+
+// CHECK-LABEL: define wasm_multivaluecc void @f1()
+__attribute__((wasm_multivalue))
+void f1(void) {}
+
+// CHECK-LABEL: define wasm_multivaluecc i32 @f2(float {{.*}}, double {{.*}})
+__attribute__((wasm_multivalue))
+int f2(float a, double b) { return (int)(a + b); }
+
+// CHECK-LABEL: define wasm_multivaluecc i128 @f3(fp128
+__attribute__((wasm_multivalue))
+__int128 f3(long double x) { return (__int128)x; }
+
+struct Foo4 { };
+union Bar4 { };
+
+// CHECK-LABEL: define wasm_multivaluecc void @f4()
+__attribute__((wasm_multivalue))
+union Bar4 f4(struct Foo4 x) { union Bar4 r; return r; }
+
+struct Foo5 { int a; };
+union Bar5 { int a; };
+
+// CHECK-LABEL: define wasm_multivaluecc i32 @f5(i32
+__attribute__((wasm_multivalue))
+union Bar5 f5(struct Foo5 x) { union Bar5 r; r.a = x.a; return r; }
+
+struct Foo6 { int a; int b; };
+
+// CHECK-LABEL: define wasm_multivaluecc %struct.Foo6 @f6(i32 {{.*}}, i32 
{{.*}})
+__attribute__((wasm_multivalue))
+struct Foo6 f6(struct Foo6 x) { return x; }
+
+// CHECK-LABEL: define wasm_multivaluecc i128 @f7()
+__attribute__((wasm_multivalue))
+__int128 f7(void) { return 1; }
+
+struct Foo8 { int a; int b; int c; };
+// CHECK-LABEL: define wasm_multivaluecc %struct.Foo8 @f8(ptr
+__attribute__((wasm_multivalue))
+struct Foo8 f8(struct Foo8 x) { return x; }
+
+struct Foo9 {
+  struct Foo6 inner;
+};
+// CHECK-LABEL: define wasm_multivaluecc void @f9(ptr {{.*}} sret
+__attribute__((wasm_multivalue))
+struct Foo9 f9(void) { struct Foo9 r = {{0, 0}}; return r; }
+
+// bitfields force pointers
+struct Foo10 {
+  int a : 4;
+  int b : 4;
+};
+// CHECK-LABEL: define wasm_multivaluecc void @f10(ptr
+__attribute__((wasm_multivalue))
+struct Foo10 f10(void) { struct Foo10 r = {0, 0}; return r; }
+
+// The default calling convention isn't changed from `+multivalue`
+// CHECK-LABEL: define void @f11(ptr{{.*}}sret(%struct.Foo6){{.*}}, ptr 
{{.*}}byval(%struct.Foo6)
+struct Foo6 f11(struct Foo6 x) { return x; }
+
+// Test cross-calling-convention indierct calls
+typedef __attribute__((wasm_multivalue)) struct Foo6 (*mv_ptr)(struct Foo6);
+
+// CHECK-LABEL: define void @f12(
+// CHECK: call wasm_multivaluecc {{(noundef )?}}%struct.Foo6 %0(i32{{.*}}, i32
+struct Foo6 f12(mv_ptr fn, struct Foo6 x) {
+  return fn(x);
+}
diff --git a/clang/test/CodeGen/WebAssembly/wasm-multivalue-functype.c 
b/clang/test/CodeGen/WebAssembly/wasm-multivalue-functype.c
new file mode 100644
index 0000000000000..c522640901473
--- /dev/null
+++ b/clang/test/CodeGen/WebAssembly/wasm-multivalue-functype.c
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -triple wasm32-unknown-unknown -target-feature +multivalue 
%s -S -O2 -o - | FileCheck %s
+
+// CHECK: .functype f1 () -> ()
+void f1(void) {}
+// CHECK: .functype f1mv () -> ()
+__attribute__((wasm_multivalue))
+void f1mv(void) {}
+
+// CHECK: .functype f2 (f32, f64) -> (i32)
+int f2(float a, double b) { return (int)(a + b); }
+// CHECK: .functype f2mv (f32, f64) -> (i32)
+__attribute__((wasm_multivalue))
+int f2mv(float a, double b) { return (int)(a + b); }
+
+// CHECK: .functype f3 (i32, i64, i64) -> ()
+__int128 f3(long double x) { return (__int128)x; }
+// CHECK: .functype f3mv (i64, i64) -> (i64, i64)
+__attribute__((wasm_multivalue))
+__int128 f3mv(long double x) { return (__int128)x; }
+
+struct Foo4 { };
+union Bar4 { };
+
+// CHECK: .functype f4 () -> ()
+union Bar4 f4(struct Foo4 x) { union Bar4 r; return r; }
+// CHECK: .functype f4mv () -> ()
+__attribute__((wasm_multivalue))
+union Bar4 f4mv(struct Foo4 x) { union Bar4 r; return r; }
+
+struct Foo5 { int a; };
+union Bar5 { int a; };
+
+// CHECK: .functype f5 (i32) -> (i32)
+union Bar5 f5(struct Foo5 x) { union Bar5 r; r.a = x.a; return r; }
+// CHECK: .functype f5mv (i32) -> (i32)
+__attribute__((wasm_multivalue))
+union Bar5 f5mv(struct Foo5 x) { union Bar5 r; r.a = x.a; return r; }
+
+struct Foo6 { int a; int b; };
+
+// CHECK: .functype f6 (i32, i32) -> ()
+struct Foo6 f6(struct Foo6 x) { return x; }
+// CHECK: .functype f6mv (i32, i32) -> (i32, i32)
+__attribute__((wasm_multivalue))
+struct Foo6 f6mv(struct Foo6 x) { return x; }
diff --git a/clang/test/Sema/attr-wasm-multivalue.c 
b/clang/test/Sema/attr-wasm-multivalue.c
new file mode 100644
index 0000000000000..51e9f494b657d
--- /dev/null
+++ b/clang/test/Sema/attr-wasm-multivalue.c
@@ -0,0 +1,33 @@
+// RUN: %clang_cc1 -triple wasm32-unknown-unknown -target-feature +multivalue 
-fsyntax-only -verify=enabled %s
+// RUN: %clang_cc1 -triple wasm32-unknown-unknown -target-feature -multivalue 
-fsyntax-only -verify=disabled %s
+// RUN: %clang_cc1 -triple x86_64-linux -fsyntax-only -verify=nonwasm %s
+
+// disabled-error@+3 {{'wasm_multivalue' calling convention requires the 
'multivalue' target feature to be enabled}}
+// nonwasm-warning@+2 {{'wasm_multivalue' calling convention is not supported 
for this target}}
+// nonwasm-warning@+1 2 {{unknown attribute 'wasm_multivalue' ignored}}
+__attribute__((wasm_multivalue))
+void f1(void);
+
+
+struct Pair { int a; int b; };
+
+// disabled-error@+3 {{'wasm_multivalue' calling convention requires the 
'multivalue' target feature to be enabled}}
+// nonwasm-warning@+2 {{'wasm_multivalue' calling convention is not supported 
for this target}}
+// nonwasm-warning@+1 2 {{unknown attribute 'wasm_multivalue' ignored}}
+__attribute__((wasm_multivalue))
+struct Pair returns_pair(struct Pair x);
+
+// The attribute can be applied to function pointer types.
+// disabled-error@+3 {{'wasm_multivalue' calling convention requires the 
'multivalue' target feature to be enabled}}
+// nonwasm-warning@+2 {{'wasm_multivalue' calling convention is not supported 
for this target}}
+// nonwasm-warning@+1 2 {{unknown attribute 'wasm_multivalue' ignored}}
+typedef __attribute__((wasm_multivalue)) struct Pair (*pair_fn_t)(struct Pair);
+
+#if defined(__wasm__)
+// Attribute should not take arguments. Only checked on wasm because on other
+// targets the attribute is unknown and the diagnostic flow differs.
+// enabled-error@+2 {{'wasm_multivalue' attribute takes no arguments}}
+// disabled-error@+1 {{'wasm_multivalue' attribute takes no arguments}}
+__attribute__((wasm_multivalue(1)))
+void takes_no_args(void);
+#endif
diff --git a/llvm/include/llvm/AsmParser/LLToken.h 
b/llvm/include/llvm/AsmParser/LLToken.h
index 426ca35ededb6..ba0eba37f64d1 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -192,6 +192,7 @@ enum Kind {
   kw_cheriot_compartmentcallcc,
   kw_cheriot_compartmentcalleecc,
   kw_cheriot_librarycallcc,
+  kw_wasm_multivaluecc,
 
   // Attributes:
   kw_attributes,
diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.def 
b/llvm/include/llvm/BinaryFormat/Dwarf.def
index 8bb1766bcc259..c8bd9b3b5233a 100644
--- a/llvm/include/llvm/BinaryFormat/Dwarf.def
+++ b/llvm/include/llvm/BinaryFormat/Dwarf.def
@@ -1179,6 +1179,7 @@ HANDLE_DW_CC(0xcd, LLVM_PreserveNone)
 HANDLE_DW_CC(0xce, LLVM_RISCVVectorCall)
 HANDLE_DW_CC(0xcf, LLVM_SwiftTail)
 HANDLE_DW_CC(0xd0, LLVM_RISCVVLSCall)
+HANDLE_DW_CC(0xd1, LLVM_WasmMultivalue)
 // From GCC source code (include/dwarf2.h): This DW_CC_ value is not currently
 // generated by any toolchain.  It is used internally to GDB to indicate OpenCL
 // C functions that have been compiled with the IBM XL C for OpenCL compiler 
and
diff --git a/llvm/include/llvm/IR/CallingConv.h 
b/llvm/include/llvm/IR/CallingConv.h
index 249c512dda532..32f1e5bca2a03 100644
--- a/llvm/include/llvm/IR/CallingConv.h
+++ b/llvm/include/llvm/IR/CallingConv.h
@@ -297,6 +297,10 @@ namespace CallingConv {
     /// stateless compartment.
     CHERIoT_LibraryCall = 127,
 
+    /// Calling convention for WebAssembly that takes advantage of the
+    /// multivalue proposal.
+    WASM_Multivalue = 128,
+
     /// The highest possible ID. Must be some 2^k - 1.
     MaxID = 1023
   };
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index b1a0a71fcc3ae..79a3e2298e525 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -705,6 +705,7 @@ lltok::Kind LLLexer::LexIdentifier() {
   KEYWORD(cheriot_compartmentcallcc);
   KEYWORD(cheriot_compartmentcalleecc);
   KEYWORD(cheriot_librarycallcc);
+  KEYWORD(wasm_multivaluecc);
 
   KEYWORD(cc);
   KEYWORD(c);
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index b9c36c28d04cd..d797b06db506f 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -2257,6 +2257,7 @@ void LLParser::parseOptionalDLLStorageClass(unsigned 
&Res) {
 ///   ::= 'graalcc'
 ///   ::= 'riscv_vector_cc'
 ///   ::= 'riscv_vls_cc'
+///   ::= 'wasm_multivaluecc'
 ///   ::= 'cc' UINT
 ///
 bool LLParser::parseOptionalCallingConv(unsigned &CC) {
@@ -2376,6 +2377,9 @@ bool LLParser::parseOptionalCallingConv(unsigned &CC) {
   case lltok::kw_cheriot_librarycallcc:
     CC = CallingConv::CHERIoT_LibraryCall;
     break;
+  case lltok::kw_wasm_multivaluecc:
+    CC = CallingConv::WASM_Multivalue;
+    break;
   case lltok::kw_cc: {
       Lex.Lex();
       return parseUInt32(CC);
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 057255caaea3b..d3f7191ba8404 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -448,6 +448,9 @@ static void printCallingConv(unsigned cc, raw_ostream &Out) 
{
   case CallingConv::CHERIoT_LibraryCall:
     Out << "cheriot_librarycallcc";
     break;
+  case CallingConv::WASM_Multivalue:
+    Out << "wasm_multivaluecc";
+    break;
   }
 }
 
diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp
index de5d3f62efdc9..b1c671bf0bab3 100644
--- a/llvm/lib/IR/Function.cpp
+++ b/llvm/lib/IR/Function.cpp
@@ -1241,6 +1241,7 @@ bool 
llvm::CallingConv::supportsNonVoidReturnType(CallingConv::ID CC) {
   case CallingConv::RISCV_VLSCall_16384:
   case CallingConv::RISCV_VLSCall_32768:
   case CallingConv::RISCV_VLSCall_65536:
+  case CallingConv::WASM_Multivalue:
     return true;
   default:
     return false;
diff --git a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp 
b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
index 9f3a1d1ba7fa2..ae7372d68f544 100644
--- a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
@@ -47,6 +47,7 @@ static bool callingConvSupported(CallingConv::ID CallConv) {
          CallConv == CallingConv::PreserveAll ||
          CallConv == CallingConv::CXX_FAST_TLS ||
          CallConv == CallingConv::WASM_EmscriptenInvoke ||
+         CallConv == CallingConv::WASM_Multivalue ||
          CallConv == CallingConv::Swift;
 }
 
@@ -84,8 +85,8 @@ bool WebAssemblyCallLowering::canLowerReturn(MachineFunction 
&MF,
                                              CallingConv::ID CallConv,
                                              SmallVectorImpl<BaseArgInfo> 
&Outs,
                                              bool IsVarArg) const {
-  return WebAssembly::canLowerReturn(Outs.size(),
-                                     &MF.getSubtarget<WebAssemblySubtarget>());
+  return WebAssembly::canLowerReturn(
+      Outs.size(), &MF.getSubtarget<WebAssemblySubtarget>(), 
&MF.getFunction());
 }
 
 bool WebAssemblyCallLowering::lowerReturn(MachineIRBuilder &MIRBuilder,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index 7f22dc0fed135..8201c4afb3a23 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -1281,6 +1281,7 @@ static bool callingConvSupported(CallingConv::ID 
CallConv) {
          CallConv == CallingConv::PreserveAll ||
          CallConv == CallingConv::CXX_FAST_TLS ||
          CallConv == CallingConv::WASM_EmscriptenInvoke ||
+         CallConv == CallingConv::WASM_Multivalue ||
          CallConv == CallingConv::Swift || CallConv == CallingConv::SwiftTail;
 }
 
@@ -1581,11 +1582,11 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo 
&CLI,
 }
 
 bool WebAssemblyTargetLowering::CanLowerReturn(
-    CallingConv::ID /*CallConv*/, MachineFunction & /*MF*/, bool /*IsVarArg*/,
+    CallingConv::ID /*CallConv*/, MachineFunction &MF, bool /*IsVarArg*/,
     const SmallVectorImpl<ISD::OutputArg> &Outs, LLVMContext & /*Context*/,
     const Type *RetTy) const {
   // WebAssembly can only handle returning tuples with multivalue enabled
-  return WebAssembly::canLowerReturn(Outs.size(), Subtarget);
+  return WebAssembly::canLowerReturn(Outs.size(), Subtarget, 
&MF.getFunction());
 }
 
 SDValue WebAssemblyTargetLowering::LowerReturn(
@@ -1593,7 +1594,8 @@ SDValue WebAssemblyTargetLowering::LowerReturn(
     const SmallVectorImpl<ISD::OutputArg> &Outs,
     const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
     SelectionDAG &DAG) const {
-  assert(WebAssembly::canLowerReturn(Outs.size(), Subtarget) &&
+  assert(WebAssembly::canLowerReturn(Outs.size(), Subtarget,
+                                     &DAG.getMachineFunction().getFunction()) 
&&
          "MVP WebAssembly can only return up to one value");
   if (!callingConvSupported(CallConv))
     fail(DL, DAG, "WebAssembly doesn't support non-C calling conventions");
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
index 696a9529e432d..962a664fe82e0 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
@@ -69,7 +69,7 @@ void llvm::computeSignatureVTs(const FunctionType *Ty,
   MVT PtrVT = MVT::getIntegerVT(TM.createDataLayout().getPointerSizeInBits());
   if (!WebAssembly::canLowerReturn(
           Results.size(),
-          &TM.getSubtarget<WebAssemblySubtarget>(ContextFunc))) {
+          &TM.getSubtarget<WebAssemblySubtarget>(ContextFunc), TargetFunc)) {
     // WebAssembly can't lower returns of multiple values without demoting to
     // sret unless multivalue is enabled (see
     // WebAssemblyTargetLowering::CanLowerReturn). So replace multiple return
diff --git 
a/llvm/lib/Target/WebAssembly/WebAssemblyRuntimeLibcallSignatures.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyRuntimeLibcallSignatures.cpp
index f3c236ca8c9ce..ea6276bb83de7 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyRuntimeLibcallSignatures.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyRuntimeLibcallSignatures.cpp
@@ -732,7 +732,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(PtrTy);
     break;
   case i64_i64_func_f32:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -741,7 +741,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::F32);
     break;
   case i64_i64_func_f64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -750,7 +750,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::F64);
     break;
   case i16_i16_func_i16_i16:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I32);
       Rets.push_back(wasm::ValType::I32);
     } else {
@@ -760,7 +760,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I32);
     break;
   case i32_i32_func_i32_i32:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I32);
       Rets.push_back(wasm::ValType::I32);
     } else {
@@ -770,7 +770,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I32);
     break;
   case i64_i64_func_i64_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -780,7 +780,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i64_i64_iPTR:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -791,7 +791,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(PtrTy);
     break;
   case i64_i64_func_i64_i64_i64_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -803,7 +803,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i64_i64_i64_i64_iPTR:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -816,7 +816,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(PtrTy);
     break;
   case i64_i64_i64_i64_func_i64_i64_i64_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
@@ -830,7 +830,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i64_i64_i32:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -900,7 +900,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i64_i64_i64_i64_i64_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -914,7 +914,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i32:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -923,7 +923,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I32);
     break;
   case i64_i64_func_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
index ac8df67fe7557..6ea3190c4bf55 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
@@ -184,15 +184,20 @@ unsigned WebAssembly::getCopyOpcodeForRegClass(const 
TargetRegisterClass *RC) {
 }
 
 bool WebAssembly::canLowerMultivalueReturn(
-    const WebAssemblySubtarget *Subtarget) {
+    const WebAssemblySubtarget *Subtarget, const Function *F) {
+  if (!Subtarget->hasMultivalue())
+    return false;
+  if (F && F->getCallingConv() == CallingConv::WASM_Multivalue)
+    return true;
   const auto &TM = static_cast<const WebAssemblyTargetMachine &>(
       Subtarget->getTargetLowering()->getTargetMachine());
-  return Subtarget->hasMultivalue() && TM.usesMultivalueABI();
+  return TM.usesMultivalueABI();
 }
 
 bool WebAssembly::canLowerReturn(size_t ResultSize,
-                                 const WebAssemblySubtarget *Subtarget) {
-  return ResultSize <= 1 || canLowerMultivalueReturn(Subtarget);
+                                 const WebAssemblySubtarget *Subtarget,
+                                 const Function *F) {
+  return ResultSize <= 1 || canLowerMultivalueReturn(Subtarget, F);
 }
 
 MachineSDNode *WebAssembly::getTLSBase(SelectionDAG &DAG, const SDLoc &DL,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h 
b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
index 0827791d93657..820a7624af7db 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
@@ -69,13 +69,17 @@ unsigned getCopyOpcodeForRegClass(const TargetRegisterClass 
*RC);
 
 /// Returns true if multivalue returns of a function can be lowered directly,
 /// i.e., not indirectly via a pointer parameter that points to the value in
-/// memory.
-bool canLowerMultivalueReturn(const WebAssemblySubtarget *Subtarget);
+/// memory. If F is provided then its calling convention is taken into account
+/// when making this determination.
+bool canLowerMultivalueReturn(const WebAssemblySubtarget *Subtarget,
+                              const Function *F);
 
 /// Returns true if the function's return value(s) can be lowered directly,
 /// i.e., not indirectly via a pointer parameter that points to the value in
-/// memory.
-bool canLowerReturn(size_t ResultSize, const WebAssemblySubtarget *Subtarget);
+/// memory. If F is provided then its calling convention is taken into account
+/// when making this determination.
+bool canLowerReturn(size_t ResultSize, const WebAssemblySubtarget *Subtarget,
+                    const Function *F);
 
 // Get the TLS base value for the current target
 // If using libcall thread context, calls
diff --git a/llvm/test/CodeGen/WebAssembly/wasm-multivalue-cc.ll 
b/llvm/test/CodeGen/WebAssembly/wasm-multivalue-cc.ll
new file mode 100644
index 0000000000000..1d2b8b1ee0ab2
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/wasm-multivalue-cc.ll
@@ -0,0 +1,38 @@
+; RUN: llc < %s -asm-verbose=false -verify-machineinstrs | FileCheck %s
+
+target triple = "wasm32-unknown-unknown"
+
+%pair = type { i32, i32 }
+
+; CHECK-LABEL: returns_pair_mv:
+; CHECK-NEXT: .functype returns_pair_mv () -> (i32, i32)
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.const 2
+; CHECK-NEXT: end_function
+define wasm_multivaluecc %pair @returns_pair_mv() {
+  ret %pair { i32 1, i32 2 }
+}
+
+; CHECK-LABEL: returns_pair_c:
+; CHECK-NEXT: .functype returns_pair_c (i32) -> ()
+define %pair @returns_pair_c() {
+  ret %pair { i32 1, i32 2 }
+}
+
+; CHECK-LABEL: returns_i128:
+; CHECK-NEXT: .functype returns_i128 () -> (i64, i64)
+; CHECK-NEXT: i64.const 42
+; CHECK-NEXT: i64.const 0
+; CHECK-NEXT: end_function
+define wasm_multivaluecc i128 @returns_i128() {
+  ret i128 42
+}
+
+; CHECK-LABEL: forward_pair:
+; CHECK-NEXT: .functype forward_pair () -> (i32, i32)
+; CHECK-NEXT: call returns_pair_mv
+; CHECK-NEXT: end_function
+define wasm_multivaluecc %pair @forward_pair() {
+  %p = call wasm_multivaluecc %pair @returns_pair_mv()
+  ret %pair %p
+}

>From 99253281c56ad681bbc7adacccc00ddf18a9efdc Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Wed, 27 May 2026 16:11:00 -0700
Subject: [PATCH 02/10] Add cc to the C API

---
 llvm/include/llvm-c/Core.h | 83 +++++++++++++++++++-------------------
 1 file changed, 42 insertions(+), 41 deletions(-)

diff --git a/llvm/include/llvm-c/Core.h b/llvm/include/llvm-c/Core.h
index 86f636c636783..5582c097d3616 100644
--- a/llvm/include/llvm-c/Core.h
+++ b/llvm/include/llvm-c/Core.h
@@ -216,47 +216,48 @@ typedef enum {
 } LLVMDLLStorageClass;
 
 typedef enum {
-  LLVMCCallConv             = 0,
-  LLVMFastCallConv          = 8,
-  LLVMColdCallConv          = 9,
-  LLVMGHCCallConv           = 10,
-  LLVMHiPECallConv          = 11,
-  LLVMAnyRegCallConv        = 13,
-  LLVMPreserveMostCallConv  = 14,
-  LLVMPreserveAllCallConv   = 15,
-  LLVMSwiftCallConv         = 16,
-  LLVMCXXFASTTLSCallConv    = 17,
-  LLVMX86StdcallCallConv    = 64,
-  LLVMX86FastcallCallConv   = 65,
-  LLVMARMAPCSCallConv       = 66,
-  LLVMARMAAPCSCallConv      = 67,
-  LLVMARMAAPCSVFPCallConv   = 68,
-  LLVMMSP430INTRCallConv    = 69,
-  LLVMX86ThisCallCallConv   = 70,
-  LLVMPTXKernelCallConv     = 71,
-  LLVMPTXDeviceCallConv     = 72,
-  LLVMSPIRFUNCCallConv      = 75,
-  LLVMSPIRKERNELCallConv    = 76,
-  LLVMIntelOCLBICallConv    = 77,
-  LLVMX8664SysVCallConv     = 78,
-  LLVMWin64CallConv         = 79,
-  LLVMX86VectorCallCallConv = 80,
-  LLVMHHVMCallConv          = 81,
-  LLVMHHVMCCallConv         = 82,
-  LLVMX86INTRCallConv       = 83,
-  LLVMAVRINTRCallConv       = 84,
-  LLVMAVRSIGNALCallConv     = 85,
-  LLVMAVRBUILTINCallConv    = 86,
-  LLVMAMDGPUVSCallConv      = 87,
-  LLVMAMDGPUGSCallConv      = 88,
-  LLVMAMDGPUPSCallConv      = 89,
-  LLVMAMDGPUCSCallConv      = 90,
-  LLVMAMDGPUKERNELCallConv  = 91,
-  LLVMX86RegCallCallConv    = 92,
-  LLVMAMDGPUHSCallConv      = 93,
-  LLVMMSP430BUILTINCallConv = 94,
-  LLVMAMDGPULSCallConv      = 95,
-  LLVMAMDGPUESCallConv      = 96
+  LLVMCCallConv              = 0,
+  LLVMFastCallConv           = 8,
+  LLVMColdCallConv           = 9,
+  LLVMGHCCallConv            = 10,
+  LLVMHiPECallConv           = 11,
+  LLVMAnyRegCallConv         = 13,
+  LLVMPreserveMostCallConv   = 14,
+  LLVMPreserveAllCallConv    = 15,
+  LLVMSwiftCallConv          = 16,
+  LLVMCXXFASTTLSCallConv     = 17,
+  LLVMX86StdcallCallConv     = 64,
+  LLVMX86FastcallCallConv    = 65,
+  LLVMARMAPCSCallConv        = 66,
+  LLVMARMAAPCSCallConv       = 67,
+  LLVMARMAAPCSVFPCallConv    = 68,
+  LLVMMSP430INTRCallConv     = 69,
+  LLVMX86ThisCallCallConv    = 70,
+  LLVMPTXKernelCallConv      = 71,
+  LLVMPTXDeviceCallConv      = 72,
+  LLVMSPIRFUNCCallConv       = 75,
+  LLVMSPIRKERNELCallConv     = 76,
+  LLVMIntelOCLBICallConv     = 77,
+  LLVMX8664SysVCallConv      = 78,
+  LLVMWin64CallConv          = 79,
+  LLVMX86VectorCallCallConv  = 80,
+  LLVMHHVMCallConv           = 81,
+  LLVMHHVMCCallConv          = 82,
+  LLVMX86INTRCallConv        = 83,
+  LLVMAVRINTRCallConv        = 84,
+  LLVMAVRSIGNALCallConv      = 85,
+  LLVMAVRBUILTINCallConv     = 86,
+  LLVMAMDGPUVSCallConv       = 87,
+  LLVMAMDGPUGSCallConv       = 88,
+  LLVMAMDGPUPSCallConv       = 89,
+  LLVMAMDGPUCSCallConv       = 90,
+  LLVMAMDGPUKERNELCallConv   = 91,
+  LLVMX86RegCallCallConv     = 92,
+  LLVMAMDGPUHSCallConv       = 93,
+  LLVMMSP430BUILTINCallConv  = 94,
+  LLVMAMDGPULSCallConv       = 95,
+  LLVMAMDGPUESCallConv       = 96,
+  LLVMWasmMultivalueCallConv = 128
 } LLVMCallConv;
 
 typedef enum {

>From ffb15415e9f03a18a6d4a72205856bb5c420281c Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Wed, 27 May 2026 16:11:26 -0700
Subject: [PATCH 03/10] Run clang-format

---
 clang/lib/CodeGen/Targets/WebAssembly.cpp        | 16 +++++++++-------
 .../WebAssemblyMachineFunctionInfo.cpp           |  4 ++--
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/clang/lib/CodeGen/Targets/WebAssembly.cpp 
b/clang/lib/CodeGen/Targets/WebAssembly.cpp
index 8942e3e78b6de..6cc55c10114be 100644
--- a/clang/lib/CodeGen/Targets/WebAssembly.cpp
+++ b/clang/lib/CodeGen/Targets/WebAssembly.cpp
@@ -37,8 +37,7 @@ class WebAssemblyABIInfo final : public ABIInfo {
   void computeInfo(CGFunctionInfo &FI) const override {
     llvm::CallingConv::ID CC = FI.getCallingConvention();
     if (!getCXXABI().classifyReturnType(FI))
-      FI.getReturnInfo() =
-          classifyReturnType(FI.getReturnType(), CC);
+      FI.getReturnInfo() = classifyReturnType(FI.getReturnType(), CC);
     for (auto &Arg : FI.arguments())
       Arg.info = classifyArgumentType(Arg.type, CC);
   }
@@ -107,7 +106,8 @@ class WebAssemblyTargetCodeGenInfo final : public 
TargetCodeGenInfo {
 /// scalars, a bit-field, or a flexible array member).
 ///
 /// Note that this is similar to `isSingleElementStruct` in structure.
-static std::optional<unsigned> countScalarFields(ASTContext &Context, QualType 
T) {
+static std::optional<unsigned> countScalarFields(ASTContext &Context,
+                                                 QualType T) {
   const auto *RD = T->getAsRecordDecl();
   if (!RD)
     return std::nullopt;
@@ -143,8 +143,9 @@ static std::optional<unsigned> countScalarFields(ASTContext 
&Context, QualType T
 }
 
 /// Classify argument of given type \p Ty.
-ABIArgInfo WebAssemblyABIInfo::classifyArgumentType(QualType Ty,
-                                                    llvm::CallingConv::ID CC) 
const {
+ABIArgInfo
+WebAssemblyABIInfo::classifyArgumentType(QualType Ty,
+                                         llvm::CallingConv::ID CC) const {
   Ty = useFirstFieldIfTransparentUnion(Ty);
 
   if (isAggregateTypeForABI(Ty)) {
@@ -186,8 +187,9 @@ ABIArgInfo 
WebAssemblyABIInfo::classifyArgumentType(QualType Ty,
   return defaultInfo.classifyArgumentType(Ty);
 }
 
-ABIArgInfo WebAssemblyABIInfo::classifyReturnType(QualType RetTy,
-                                                  llvm::CallingConv::ID CC) 
const {
+ABIArgInfo
+WebAssemblyABIInfo::classifyReturnType(QualType RetTy,
+                                       llvm::CallingConv::ID CC) const {
   if (isAggregateTypeForABI(RetTy)) {
     // Records with non-trivial destructors/copy-constructors should not be
     // returned by value.
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
index 962a664fe82e0..9efeea7346c25 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
@@ -68,8 +68,8 @@ void llvm::computeSignatureVTs(const FunctionType *Ty,
 
   MVT PtrVT = MVT::getIntegerVT(TM.createDataLayout().getPointerSizeInBits());
   if (!WebAssembly::canLowerReturn(
-          Results.size(),
-          &TM.getSubtarget<WebAssemblySubtarget>(ContextFunc), TargetFunc)) {
+          Results.size(), &TM.getSubtarget<WebAssemblySubtarget>(ContextFunc),
+          TargetFunc)) {
     // WebAssembly can't lower returns of multiple values without demoting to
     // sret unless multivalue is enabled (see
     // WebAssemblyTargetLowering::CanLowerReturn). So replace multiple return

>From 36606fd695becdbc94fafc2e572a2e84bfa26165 Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Wed, 27 May 2026 16:16:20 -0700
Subject: [PATCH 04/10] clang-format

---
 llvm/include/llvm-c/Core.h | 82 +++++++++++++++++++-------------------
 1 file changed, 41 insertions(+), 41 deletions(-)

diff --git a/llvm/include/llvm-c/Core.h b/llvm/include/llvm-c/Core.h
index 5582c097d3616..3befdc94d688f 100644
--- a/llvm/include/llvm-c/Core.h
+++ b/llvm/include/llvm-c/Core.h
@@ -216,47 +216,47 @@ typedef enum {
 } LLVMDLLStorageClass;
 
 typedef enum {
-  LLVMCCallConv              = 0,
-  LLVMFastCallConv           = 8,
-  LLVMColdCallConv           = 9,
-  LLVMGHCCallConv            = 10,
-  LLVMHiPECallConv           = 11,
-  LLVMAnyRegCallConv         = 13,
-  LLVMPreserveMostCallConv   = 14,
-  LLVMPreserveAllCallConv    = 15,
-  LLVMSwiftCallConv          = 16,
-  LLVMCXXFASTTLSCallConv     = 17,
-  LLVMX86StdcallCallConv     = 64,
-  LLVMX86FastcallCallConv    = 65,
-  LLVMARMAPCSCallConv        = 66,
-  LLVMARMAAPCSCallConv       = 67,
-  LLVMARMAAPCSVFPCallConv    = 68,
-  LLVMMSP430INTRCallConv     = 69,
-  LLVMX86ThisCallCallConv    = 70,
-  LLVMPTXKernelCallConv      = 71,
-  LLVMPTXDeviceCallConv      = 72,
-  LLVMSPIRFUNCCallConv       = 75,
-  LLVMSPIRKERNELCallConv     = 76,
-  LLVMIntelOCLBICallConv     = 77,
-  LLVMX8664SysVCallConv      = 78,
-  LLVMWin64CallConv          = 79,
-  LLVMX86VectorCallCallConv  = 80,
-  LLVMHHVMCallConv           = 81,
-  LLVMHHVMCCallConv          = 82,
-  LLVMX86INTRCallConv        = 83,
-  LLVMAVRINTRCallConv        = 84,
-  LLVMAVRSIGNALCallConv      = 85,
-  LLVMAVRBUILTINCallConv     = 86,
-  LLVMAMDGPUVSCallConv       = 87,
-  LLVMAMDGPUGSCallConv       = 88,
-  LLVMAMDGPUPSCallConv       = 89,
-  LLVMAMDGPUCSCallConv       = 90,
-  LLVMAMDGPUKERNELCallConv   = 91,
-  LLVMX86RegCallCallConv     = 92,
-  LLVMAMDGPUHSCallConv       = 93,
-  LLVMMSP430BUILTINCallConv  = 94,
-  LLVMAMDGPULSCallConv       = 95,
-  LLVMAMDGPUESCallConv       = 96,
+  LLVMCCallConv = 0,
+  LLVMFastCallConv = 8,
+  LLVMColdCallConv = 9,
+  LLVMGHCCallConv = 10,
+  LLVMHiPECallConv = 11,
+  LLVMAnyRegCallConv = 13,
+  LLVMPreserveMostCallConv = 14,
+  LLVMPreserveAllCallConv = 15,
+  LLVMSwiftCallConv = 16,
+  LLVMCXXFASTTLSCallConv = 17,
+  LLVMX86StdcallCallConv = 64,
+  LLVMX86FastcallCallConv = 65,
+  LLVMARMAPCSCallConv = 66,
+  LLVMARMAAPCSCallConv = 67,
+  LLVMARMAAPCSVFPCallConv = 68,
+  LLVMMSP430INTRCallConv = 69,
+  LLVMX86ThisCallCallConv = 70,
+  LLVMPTXKernelCallConv = 71,
+  LLVMPTXDeviceCallConv = 72,
+  LLVMSPIRFUNCCallConv = 75,
+  LLVMSPIRKERNELCallConv = 76,
+  LLVMIntelOCLBICallConv = 77,
+  LLVMX8664SysVCallConv = 78,
+  LLVMWin64CallConv = 79,
+  LLVMX86VectorCallCallConv = 80,
+  LLVMHHVMCallConv = 81,
+  LLVMHHVMCCallConv = 82,
+  LLVMX86INTRCallConv = 83,
+  LLVMAVRINTRCallConv = 84,
+  LLVMAVRSIGNALCallConv = 85,
+  LLVMAVRBUILTINCallConv = 86,
+  LLVMAMDGPUVSCallConv = 87,
+  LLVMAMDGPUGSCallConv = 88,
+  LLVMAMDGPUPSCallConv = 89,
+  LLVMAMDGPUCSCallConv = 90,
+  LLVMAMDGPUKERNELCallConv = 91,
+  LLVMX86RegCallCallConv = 92,
+  LLVMAMDGPUHSCallConv = 93,
+  LLVMMSP430BUILTINCallConv = 94,
+  LLVMAMDGPULSCallConv = 95,
+  LLVMAMDGPUESCallConv = 96,
   LLVMWasmMultivalueCallConv = 128
 } LLVMCallConv;
 

>From b56eb7a0e54ea74e5aaa69343d4112e78ba4ba94 Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Wed, 27 May 2026 17:57:24 -0700
Subject: [PATCH 05/10] Handle `WasmMultivalue` in another location

---
 clang/tools/libclang/CXType.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/tools/libclang/CXType.cpp b/clang/tools/libclang/CXType.cpp
index 3feb56334d79c..e355f17099618 100644
--- a/clang/tools/libclang/CXType.cpp
+++ b/clang/tools/libclang/CXType.cpp
@@ -728,6 +728,7 @@ CXCallingConv clang_getFunctionTypeCallingConv(CXType X) {
       TCALLINGCONV(RISCVVLSCall_16384);
       TCALLINGCONV(RISCVVLSCall_32768);
       TCALLINGCONV(RISCVVLSCall_65536);
+      TCALLINGCONV(WasmMultivalue);
     case CC_SpirFunction: return CXCallingConv_Unexposed;
     case CC_DeviceKernel:
       return CXCallingConv_Unexposed;

>From c653634b54b2bbc4f455bf92a1beed59949a949b Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Wed, 27 May 2026 18:17:13 -0700
Subject: [PATCH 06/10] Try to fix CI error

---
 clang/include/clang-c/Index.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 119bd68ff9814..818a2d29d2979 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3086,6 +3086,7 @@ enum CXCallingConv {
   CXCallingConv_RISCVVLSCall_16384 = 31,
   CXCallingConv_RISCVVLSCall_32768 = 32,
   CXCallingConv_RISCVVLSCall_65536 = 33,
+  CXCallingConv_WasmMultivalue = 34,
 
   CXCallingConv_Invalid = 100,
   CXCallingConv_Unexposed = 200

>From c563023274699f29d16800da9bc3fab0af53da95 Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Thu, 28 May 2026 15:47:35 -0700
Subject: [PATCH 07/10] Rename `wasm_multivaluecc` to `wasm_multivalue`

---
 .../CodeGen/WebAssembly/wasm-multivalue-abi.c | 22 +++++++++----------
 llvm/include/llvm/AsmParser/LLToken.h         |  2 +-
 llvm/lib/AsmParser/LLLexer.cpp                |  2 +-
 llvm/lib/AsmParser/LLParser.cpp               |  4 ++--
 llvm/lib/IR/AsmWriter.cpp                     |  2 +-
 ...sm-multivalue-cc.ll => wasm-multivalue.ll} |  8 +++----
 6 files changed, 20 insertions(+), 20 deletions(-)
 rename llvm/test/CodeGen/WebAssembly/{wasm-multivalue-cc.ll => 
wasm-multivalue.ll} (80%)

diff --git a/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c 
b/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
index 5194616a13733..cdac18534e780 100644
--- a/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
+++ b/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
@@ -6,51 +6,51 @@
 // Verify that the `wasm_multivalue` calling convention produces the function
 // signatures described in the WebAssembly tool conventions PR.
 
-// CHECK-LABEL: define wasm_multivaluecc void @f1()
+// CHECK-LABEL: define wasm_multivalue void @f1()
 __attribute__((wasm_multivalue))
 void f1(void) {}
 
-// CHECK-LABEL: define wasm_multivaluecc i32 @f2(float {{.*}}, double {{.*}})
+// CHECK-LABEL: define wasm_multivalue i32 @f2(float {{.*}}, double {{.*}})
 __attribute__((wasm_multivalue))
 int f2(float a, double b) { return (int)(a + b); }
 
-// CHECK-LABEL: define wasm_multivaluecc i128 @f3(fp128
+// CHECK-LABEL: define wasm_multivalue i128 @f3(fp128
 __attribute__((wasm_multivalue))
 __int128 f3(long double x) { return (__int128)x; }
 
 struct Foo4 { };
 union Bar4 { };
 
-// CHECK-LABEL: define wasm_multivaluecc void @f4()
+// CHECK-LABEL: define wasm_multivalue void @f4()
 __attribute__((wasm_multivalue))
 union Bar4 f4(struct Foo4 x) { union Bar4 r; return r; }
 
 struct Foo5 { int a; };
 union Bar5 { int a; };
 
-// CHECK-LABEL: define wasm_multivaluecc i32 @f5(i32
+// CHECK-LABEL: define wasm_multivalue i32 @f5(i32
 __attribute__((wasm_multivalue))
 union Bar5 f5(struct Foo5 x) { union Bar5 r; r.a = x.a; return r; }
 
 struct Foo6 { int a; int b; };
 
-// CHECK-LABEL: define wasm_multivaluecc %struct.Foo6 @f6(i32 {{.*}}, i32 
{{.*}})
+// CHECK-LABEL: define wasm_multivalue %struct.Foo6 @f6(i32 {{.*}}, i32 {{.*}})
 __attribute__((wasm_multivalue))
 struct Foo6 f6(struct Foo6 x) { return x; }
 
-// CHECK-LABEL: define wasm_multivaluecc i128 @f7()
+// CHECK-LABEL: define wasm_multivalue i128 @f7()
 __attribute__((wasm_multivalue))
 __int128 f7(void) { return 1; }
 
 struct Foo8 { int a; int b; int c; };
-// CHECK-LABEL: define wasm_multivaluecc %struct.Foo8 @f8(ptr
+// CHECK-LABEL: define wasm_multivalue %struct.Foo8 @f8(ptr
 __attribute__((wasm_multivalue))
 struct Foo8 f8(struct Foo8 x) { return x; }
 
 struct Foo9 {
   struct Foo6 inner;
 };
-// CHECK-LABEL: define wasm_multivaluecc void @f9(ptr {{.*}} sret
+// CHECK-LABEL: define wasm_multivalue void @f9(ptr {{.*}} sret
 __attribute__((wasm_multivalue))
 struct Foo9 f9(void) { struct Foo9 r = {{0, 0}}; return r; }
 
@@ -59,7 +59,7 @@ struct Foo10 {
   int a : 4;
   int b : 4;
 };
-// CHECK-LABEL: define wasm_multivaluecc void @f10(ptr
+// CHECK-LABEL: define wasm_multivalue void @f10(ptr
 __attribute__((wasm_multivalue))
 struct Foo10 f10(void) { struct Foo10 r = {0, 0}; return r; }
 
@@ -71,7 +71,7 @@ struct Foo6 f11(struct Foo6 x) { return x; }
 typedef __attribute__((wasm_multivalue)) struct Foo6 (*mv_ptr)(struct Foo6);
 
 // CHECK-LABEL: define void @f12(
-// CHECK: call wasm_multivaluecc {{(noundef )?}}%struct.Foo6 %0(i32{{.*}}, i32
+// CHECK: call wasm_multivalue {{(noundef )?}}%struct.Foo6 %0(i32{{.*}}, i32
 struct Foo6 f12(mv_ptr fn, struct Foo6 x) {
   return fn(x);
 }
diff --git a/llvm/include/llvm/AsmParser/LLToken.h 
b/llvm/include/llvm/AsmParser/LLToken.h
index ba0eba37f64d1..66cacbeacf24f 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -192,7 +192,7 @@ enum Kind {
   kw_cheriot_compartmentcallcc,
   kw_cheriot_compartmentcalleecc,
   kw_cheriot_librarycallcc,
-  kw_wasm_multivaluecc,
+  kw_wasm_multivalue,
 
   // Attributes:
   kw_attributes,
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index 79a3e2298e525..c3a99d069e412 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -705,7 +705,7 @@ lltok::Kind LLLexer::LexIdentifier() {
   KEYWORD(cheriot_compartmentcallcc);
   KEYWORD(cheriot_compartmentcalleecc);
   KEYWORD(cheriot_librarycallcc);
-  KEYWORD(wasm_multivaluecc);
+  KEYWORD(wasm_multivalue);
 
   KEYWORD(cc);
   KEYWORD(c);
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index d797b06db506f..cff3138e0123f 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -2257,7 +2257,7 @@ void LLParser::parseOptionalDLLStorageClass(unsigned 
&Res) {
 ///   ::= 'graalcc'
 ///   ::= 'riscv_vector_cc'
 ///   ::= 'riscv_vls_cc'
-///   ::= 'wasm_multivaluecc'
+///   ::= 'wasm_multivalue'
 ///   ::= 'cc' UINT
 ///
 bool LLParser::parseOptionalCallingConv(unsigned &CC) {
@@ -2377,7 +2377,7 @@ bool LLParser::parseOptionalCallingConv(unsigned &CC) {
   case lltok::kw_cheriot_librarycallcc:
     CC = CallingConv::CHERIoT_LibraryCall;
     break;
-  case lltok::kw_wasm_multivaluecc:
+  case lltok::kw_wasm_multivalue:
     CC = CallingConv::WASM_Multivalue;
     break;
   case lltok::kw_cc: {
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index d3f7191ba8404..23972af1bb192 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -449,7 +449,7 @@ static void printCallingConv(unsigned cc, raw_ostream &Out) 
{
     Out << "cheriot_librarycallcc";
     break;
   case CallingConv::WASM_Multivalue:
-    Out << "wasm_multivaluecc";
+    Out << "wasm_multivalue";
     break;
   }
 }
diff --git a/llvm/test/CodeGen/WebAssembly/wasm-multivalue-cc.ll 
b/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
similarity index 80%
rename from llvm/test/CodeGen/WebAssembly/wasm-multivalue-cc.ll
rename to llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
index 1d2b8b1ee0ab2..e5743de841beb 100644
--- a/llvm/test/CodeGen/WebAssembly/wasm-multivalue-cc.ll
+++ b/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
@@ -9,7 +9,7 @@ target triple = "wasm32-unknown-unknown"
 ; CHECK-NEXT: i32.const 1
 ; CHECK-NEXT: i32.const 2
 ; CHECK-NEXT: end_function
-define wasm_multivaluecc %pair @returns_pair_mv() {
+define wasm_multivalue %pair @returns_pair_mv() {
   ret %pair { i32 1, i32 2 }
 }
 
@@ -24,7 +24,7 @@ define %pair @returns_pair_c() {
 ; CHECK-NEXT: i64.const 42
 ; CHECK-NEXT: i64.const 0
 ; CHECK-NEXT: end_function
-define wasm_multivaluecc i128 @returns_i128() {
+define wasm_multivalue i128 @returns_i128() {
   ret i128 42
 }
 
@@ -32,7 +32,7 @@ define wasm_multivaluecc i128 @returns_i128() {
 ; CHECK-NEXT: .functype forward_pair () -> (i32, i32)
 ; CHECK-NEXT: call returns_pair_mv
 ; CHECK-NEXT: end_function
-define wasm_multivaluecc %pair @forward_pair() {
-  %p = call wasm_multivaluecc %pair @returns_pair_mv()
+define wasm_multivalue %pair @forward_pair() {
+  %p = call wasm_multivalue %pair @returns_pair_mv()
   ret %pair %p
 }

>From 3c770923fc8e75cbe581d27d2b4bb1e6f2ab358f Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Thu, 28 May 2026 12:52:01 -0700
Subject: [PATCH 08/10] Use the `CallingConv` passed in, don't use the target
 function

Fixes a case where a function calls another function with a different
calling convention.
---
 .../GISel/WebAssemblyCallLowering.cpp         |  2 +-
 .../WebAssembly/WebAssemblyISelLowering.cpp   |  7 +-
 .../WebAssemblyMachineFunctionInfo.cpp        |  3 +-
 .../WebAssembly/WebAssemblyRegStackify.cpp    |  2 +
 .../WebAssemblyRuntimeLibcallSignatures.cpp   | 26 ++++----
 .../WebAssembly/WebAssemblyUtilities.cpp      |  8 +--
 .../Target/WebAssembly/WebAssemblyUtilities.h | 13 ++--
 .../CodeGen/WebAssembly/wasm-multivalue.ll    | 65 ++++++++++++++-----
 8 files changed, 80 insertions(+), 46 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp 
b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
index ae7372d68f544..93b42456c36f0 100644
--- a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
@@ -86,7 +86,7 @@ bool WebAssemblyCallLowering::canLowerReturn(MachineFunction 
&MF,
                                              SmallVectorImpl<BaseArgInfo> 
&Outs,
                                              bool IsVarArg) const {
   return WebAssembly::canLowerReturn(
-      Outs.size(), &MF.getSubtarget<WebAssemblySubtarget>(), 
&MF.getFunction());
+      Outs.size(), &MF.getSubtarget<WebAssemblySubtarget>(), CallConv);
 }
 
 bool WebAssemblyCallLowering::lowerReturn(MachineIRBuilder &MIRBuilder,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index 8201c4afb3a23..b9f9376afb5ad 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -1582,11 +1582,11 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo 
&CLI,
 }
 
 bool WebAssemblyTargetLowering::CanLowerReturn(
-    CallingConv::ID /*CallConv*/, MachineFunction &MF, bool /*IsVarArg*/,
+    CallingConv::ID CallConv, MachineFunction & /*MF*/, bool /*IsVarArg*/,
     const SmallVectorImpl<ISD::OutputArg> &Outs, LLVMContext & /*Context*/,
     const Type *RetTy) const {
   // WebAssembly can only handle returning tuples with multivalue enabled
-  return WebAssembly::canLowerReturn(Outs.size(), Subtarget, 
&MF.getFunction());
+  return WebAssembly::canLowerReturn(Outs.size(), Subtarget, CallConv);
 }
 
 SDValue WebAssemblyTargetLowering::LowerReturn(
@@ -1594,8 +1594,7 @@ SDValue WebAssemblyTargetLowering::LowerReturn(
     const SmallVectorImpl<ISD::OutputArg> &Outs,
     const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
     SelectionDAG &DAG) const {
-  assert(WebAssembly::canLowerReturn(Outs.size(), Subtarget,
-                                     &DAG.getMachineFunction().getFunction()) 
&&
+  assert(WebAssembly::canLowerReturn(Outs.size(), Subtarget, CallConv) &&
          "MVP WebAssembly can only return up to one value");
   if (!callingConvSupported(CallConv))
     fail(DL, DAG, "WebAssembly doesn't support non-C calling conventions");
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
index 9efeea7346c25..dd971ff31768d 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
@@ -69,7 +69,8 @@ void llvm::computeSignatureVTs(const FunctionType *Ty,
   MVT PtrVT = MVT::getIntegerVT(TM.createDataLayout().getPointerSizeInBits());
   if (!WebAssembly::canLowerReturn(
           Results.size(), &TM.getSubtarget<WebAssemblySubtarget>(ContextFunc),
-          TargetFunc)) {
+          TargetFunc ? TargetFunc->getCallingConv()
+                     : CallingConv::ID(CallingConv::C))) {
     // WebAssembly can't lower returns of multiple values without demoting to
     // sret unless multivalue is enabled (see
     // WebAssemblyTargetLowering::CanLowerReturn). So replace multiple return
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
index 9015ceab87fb7..6ab81c5fa152d 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
@@ -375,6 +375,8 @@ static bool isSafeToMove(const MachineOperand *Def, const 
MachineOperand *Use,
   // current def in the stack, which cannot be achieved, even with locals.
   // Also ensure we don't sink the def past any other prior uses.
   for (const auto &SubsequentDef : drop_begin(DefI->defs())) {
+    if (true)
+      return false;
     auto I = std::next(MachineBasicBlock::const_iterator(DefI));
     auto E = std::next(MachineBasicBlock::const_iterator(UseI));
     for (; I != E; ++I) {
diff --git 
a/llvm/lib/Target/WebAssembly/WebAssemblyRuntimeLibcallSignatures.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyRuntimeLibcallSignatures.cpp
index ea6276bb83de7..a48e90529b7eb 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyRuntimeLibcallSignatures.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyRuntimeLibcallSignatures.cpp
@@ -732,7 +732,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(PtrTy);
     break;
   case i64_i64_func_f32:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -741,7 +741,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::F32);
     break;
   case i64_i64_func_f64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -750,7 +750,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::F64);
     break;
   case i16_i16_func_i16_i16:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I32);
       Rets.push_back(wasm::ValType::I32);
     } else {
@@ -760,7 +760,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I32);
     break;
   case i32_i32_func_i32_i32:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I32);
       Rets.push_back(wasm::ValType::I32);
     } else {
@@ -770,7 +770,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I32);
     break;
   case i64_i64_func_i64_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -780,7 +780,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i64_i64_iPTR:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -791,7 +791,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(PtrTy);
     break;
   case i64_i64_func_i64_i64_i64_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -803,7 +803,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i64_i64_i64_i64_iPTR:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -816,7 +816,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(PtrTy);
     break;
   case i64_i64_i64_i64_func_i64_i64_i64_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
@@ -830,7 +830,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i64_i64_i32:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -900,7 +900,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i64_i64_i64_i64_i64_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -914,7 +914,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I64);
     break;
   case i64_i64_func_i32:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
@@ -923,7 +923,7 @@ void WebAssembly::getLibcallSignature(const 
WebAssemblySubtarget &Subtarget,
     Params.push_back(wasm::ValType::I32);
     break;
   case i64_i64_func_i64:
-    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, nullptr)) {
+    if (WebAssembly::canLowerMultivalueReturn(&Subtarget, CallingConv::C)) {
       Rets.push_back(wasm::ValType::I64);
       Rets.push_back(wasm::ValType::I64);
     } else {
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
index 6ea3190c4bf55..010ed5abc0e1f 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
@@ -184,10 +184,10 @@ unsigned WebAssembly::getCopyOpcodeForRegClass(const 
TargetRegisterClass *RC) {
 }
 
 bool WebAssembly::canLowerMultivalueReturn(
-    const WebAssemblySubtarget *Subtarget, const Function *F) {
+    const WebAssemblySubtarget *Subtarget, CallingConv::ID CC) {
   if (!Subtarget->hasMultivalue())
     return false;
-  if (F && F->getCallingConv() == CallingConv::WASM_Multivalue)
+  if (CC == CallingConv::WASM_Multivalue)
     return true;
   const auto &TM = static_cast<const WebAssemblyTargetMachine &>(
       Subtarget->getTargetLowering()->getTargetMachine());
@@ -196,8 +196,8 @@ bool WebAssembly::canLowerMultivalueReturn(
 
 bool WebAssembly::canLowerReturn(size_t ResultSize,
                                  const WebAssemblySubtarget *Subtarget,
-                                 const Function *F) {
-  return ResultSize <= 1 || canLowerMultivalueReturn(Subtarget, F);
+                                 CallingConv::ID CC) {
+  return ResultSize <= 1 || canLowerMultivalueReturn(Subtarget, CC);
 }
 
 MachineSDNode *WebAssembly::getTLSBase(SelectionDAG &DAG, const SDLoc &DL,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h 
b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
index 820a7624af7db..4fb8df9eee3e9 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
@@ -16,6 +16,7 @@
 #define LLVM_LIB_TARGET_WEBASSEMBLY_UTILS_WEBASSEMBLYUTILITIES_H
 
 #include "llvm/CodeGen/SelectionDAGNodes.h"
+#include "llvm/IR/CallingConv.h"
 #include "llvm/Support/CommandLine.h"
 
 namespace llvm {
@@ -69,17 +70,17 @@ unsigned getCopyOpcodeForRegClass(const TargetRegisterClass 
*RC);
 
 /// Returns true if multivalue returns of a function can be lowered directly,
 /// i.e., not indirectly via a pointer parameter that points to the value in
-/// memory. If F is provided then its calling convention is taken into account
-/// when making this determination.
+/// memory. The calling convention of the call or function whose return is
+/// being lowered is taken into account when making this determination.
 bool canLowerMultivalueReturn(const WebAssemblySubtarget *Subtarget,
-                              const Function *F);
+                              CallingConv::ID CC);
 
 /// Returns true if the function's return value(s) can be lowered directly,
 /// i.e., not indirectly via a pointer parameter that points to the value in
-/// memory. If F is provided then its calling convention is taken into account
-/// when making this determination.
+/// memory. The calling convention of the call or function whose return is
+/// being lowered is taken into account when making this determination.
 bool canLowerReturn(size_t ResultSize, const WebAssemblySubtarget *Subtarget,
-                    const Function *F);
+                    CallingConv::ID CC);
 
 // Get the TLS base value for the current target
 // If using libcall thread context, calls
diff --git a/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll 
b/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
index e5743de841beb..6d8b662974969 100644
--- a/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
+++ b/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
@@ -1,38 +1,69 @@
-; RUN: llc < %s -asm-verbose=false -verify-machineinstrs | FileCheck %s
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py 
UTC_ARGS: --version 5
+; RUN: llc < %s | FileCheck %s
 
 target triple = "wasm32-unknown-unknown"
 
 %pair = type { i32, i32 }
 
-; CHECK-LABEL: returns_pair_mv:
-; CHECK-NEXT: .functype returns_pair_mv () -> (i32, i32)
-; CHECK-NEXT: i32.const 1
-; CHECK-NEXT: i32.const 2
-; CHECK-NEXT: end_function
 define wasm_multivalue %pair @returns_pair_mv() {
+; CHECK-LABEL: returns_pair_mv:
+; CHECK:         .functype returns_pair_mv () -> (i32, i32)
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    i32.const 1
+; CHECK-NEXT:    i32.const 2
+; CHECK-NEXT:    # fallthrough-return
   ret %pair { i32 1, i32 2 }
 }
 
-; CHECK-LABEL: returns_pair_c:
-; CHECK-NEXT: .functype returns_pair_c (i32) -> ()
 define %pair @returns_pair_c() {
+; CHECK-LABEL: returns_pair_c:
+; CHECK:         .functype returns_pair_c (i32) -> ()
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    i64.const 8589934593
+; CHECK-NEXT:    i64.store 0
+; CHECK-NEXT:    # fallthrough-return
   ret %pair { i32 1, i32 2 }
 }
 
-; CHECK-LABEL: returns_i128:
-; CHECK-NEXT: .functype returns_i128 () -> (i64, i64)
-; CHECK-NEXT: i64.const 42
-; CHECK-NEXT: i64.const 0
-; CHECK-NEXT: end_function
 define wasm_multivalue i128 @returns_i128() {
+; CHECK-LABEL: returns_i128:
+; CHECK:         .functype returns_i128 () -> (i64, i64)
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    i64.const 42
+; CHECK-NEXT:    i64.const 0
+; CHECK-NEXT:    # fallthrough-return
   ret i128 42
 }
 
-; CHECK-LABEL: forward_pair:
-; CHECK-NEXT: .functype forward_pair () -> (i32, i32)
-; CHECK-NEXT: call returns_pair_mv
-; CHECK-NEXT: end_function
 define wasm_multivalue %pair @forward_pair() {
+; CHECK-LABEL: forward_pair:
+; CHECK:         .functype forward_pair () -> (i32, i32)
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call returns_pair_mv
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    # fallthrough-return
   %p = call wasm_multivalue %pair @returns_pair_mv()
   ret %pair %p
 }
+
+; A multi-value caller calling a non-multi-value ABI should do the appropriate
+; handling for the ABI-at-hand.
+define wasm_multivalue %pair @caller_mv_callee_c() {
+; CHECK-LABEL: caller_mv_callee_c:
+; CHECK:         .functype caller_mv_callee_c () -> (i32, i32)
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call returns_pair_c
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    # fallthrough-return
+  %p = call %pair @returns_pair_c()
+  ret %pair %p
+}

>From 976cda022f5b296811174638960133b211eeb6ea Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Thu, 28 May 2026 16:32:16 -0700
Subject: [PATCH 09/10] Remove some debugging

---
 .../WebAssembly/WebAssemblyRegStackify.cpp    |  2 --
 .../CodeGen/WebAssembly/wasm-multivalue.ll    | 25 +++++++++++++------
 2 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
index 6ab81c5fa152d..9015ceab87fb7 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
@@ -375,8 +375,6 @@ static bool isSafeToMove(const MachineOperand *Def, const 
MachineOperand *Use,
   // current def in the stack, which cannot be achieved, even with locals.
   // Also ensure we don't sink the def past any other prior uses.
   for (const auto &SubsequentDef : drop_begin(DefI->defs())) {
-    if (true)
-      return false;
     auto I = std::next(MachineBasicBlock::const_iterator(DefI));
     auto E = std::next(MachineBasicBlock::const_iterator(UseI));
     for (; I != E; ++I) {
diff --git a/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll 
b/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
index 6d8b662974969..ebcaae43cbb55 100644
--- a/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
+++ b/llvm/test/CodeGen/WebAssembly/wasm-multivalue.ll
@@ -39,13 +39,8 @@ define wasm_multivalue i128 @returns_i128() {
 define wasm_multivalue %pair @forward_pair() {
 ; CHECK-LABEL: forward_pair:
 ; CHECK:         .functype forward_pair () -> (i32, i32)
-; CHECK-NEXT:    .local i32, i32
 ; CHECK-NEXT:  # %bb.0:
 ; CHECK-NEXT:    call returns_pair_mv
-; CHECK-NEXT:    local.set 1
-; CHECK-NEXT:    local.set 0
-; CHECK-NEXT:    local.get 0
-; CHECK-NEXT:    local.get 1
 ; CHECK-NEXT:    # fallthrough-return
   %p = call wasm_multivalue %pair @returns_pair_mv()
   ret %pair %p
@@ -56,12 +51,28 @@ define wasm_multivalue %pair @forward_pair() {
 define wasm_multivalue %pair @caller_mv_callee_c() {
 ; CHECK-LABEL: caller_mv_callee_c:
 ; CHECK:         .functype caller_mv_callee_c () -> (i32, i32)
-; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:    .local i32, i32, i32
 ; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    global.get __stack_pointer
+; CHECK-NEXT:    i32.const 16
+; CHECK-NEXT:    i32.sub
+; CHECK-NEXT:    local.tee 0
+; CHECK-NEXT:    global.set __stack_pointer
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    i32.const 8
+; CHECK-NEXT:    i32.add
 ; CHECK-NEXT:    call returns_pair_c
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    i32.load 12
 ; CHECK-NEXT:    local.set 1
-; CHECK-NEXT:    local.set 0
 ; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    i32.load 8
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    i32.const 16
+; CHECK-NEXT:    i32.add
+; CHECK-NEXT:    global.set __stack_pointer
+; CHECK-NEXT:    local.get 2
 ; CHECK-NEXT:    local.get 1
 ; CHECK-NEXT:    # fallthrough-return
   %p = call %pair @returns_pair_c()

>From c8296e385b8242710e465038271b8b76498d4cd2 Mon Sep 17 00:00:00 2001
From: Alex Crichton <[email protected]>
Date: Thu, 28 May 2026 17:05:32 -0700
Subject: [PATCH 10/10] Add test showcasing what happens to arrays

---
 .../CodeGen/WebAssembly/wasm-multivalue-abi.c | 40 +++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c 
b/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
index cdac18534e780..9319ba4d16647 100644
--- a/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
+++ b/clang/test/CodeGen/WebAssembly/wasm-multivalue-abi.c
@@ -75,3 +75,43 @@ typedef __attribute__((wasm_multivalue)) struct Foo6 
(*mv_ptr)(struct Foo6);
 struct Foo6 f12(mv_ptr fn, struct Foo6 x) {
   return fn(x);
 }
+
+struct Foo13 {
+  int empty_array[0];
+};
+
+// CHECK-LABEL: define wasm_multivalue void @f13()
+__attribute__((wasm_multivalue))
+struct Foo13 f13(struct Foo13 x) {
+  return x;
+}
+
+struct Foo14 {
+  int one_element_array[1];
+};
+
+// CHECK-LABEL: define wasm_multivalue i32 @f14(i32
+__attribute__((wasm_multivalue))
+struct Foo14 f14(struct Foo14 x) {
+  return x;
+}
+
+struct Foo15 {
+  int two_element_array[2];
+};
+
+// CHECK-LABEL: define wasm_multivalue void @f15(ptr {{.*}}, ptr {{.*}})
+__attribute__((wasm_multivalue))
+struct Foo15 f15(struct Foo15 x) {
+  return x;
+}
+
+struct Foo16 {
+  int three_element_array[3];
+};
+
+// CHECK-LABEL: define wasm_multivalue void @f16(ptr {{.*}}, ptr {{.*}})
+__attribute__((wasm_multivalue))
+struct Foo16 f16(struct Foo16 x) {
+  return x;
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to