[llvm] [WebAssembly] Add call_ref (0x14) and return_call_ref (0x15) (PR #195942)
Jonas Devlieghere via llvm-commits
llvm-commits at lists.llvm.org
Wed May 6 10:55:16 PDT 2026
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/195942
>From 3c84bed97e6da6d4763573ce27c91e8280c1b73d Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Tue, 5 May 2026 14:09:10 -0700
Subject: [PATCH 1/2] [WebAssembly] Add call_ref (0x14) and return_call_ref
(0x15)
Add MC-layer support for the typed function references opcodes:
- Instruction definitions in WebAssemblyInstrCall.td, gated on
HasReferenceTypes (and HasTailCall for return_call_ref).
- Asm-parser hook that accepts the (ty) -> (ty) signature syntax,
mirroring call_indirect / return_call_indirect.
- Stack-effect modeling in WebAssemblyAsmTypeCheck so non-trivial
signatures type-check correctly.
- Encoding and disassembly tests under test/MC/WebAssembly.
Codegen does not yet select these opcodes. The motivation is to unblock
LLDB, which uses LLVM's disassembler.
rdar://170659547
---
.../AsmParser/WebAssemblyAsmParser.cpp | 4 ++
.../AsmParser/WebAssemblyAsmTypeCheck.cpp | 12 ++++++
.../WebAssembly/WebAssemblyInstrCall.td | 16 ++++++++
.../WebAssembly/function-references.txt | 25 +++++++++++++
.../test/MC/WebAssembly/function-references.s | 37 +++++++++++++++++++
5 files changed, 94 insertions(+)
create mode 100644 llvm/test/MC/Disassembler/WebAssembly/function-references.txt
create mode 100644 llvm/test/MC/WebAssembly/function-references.s
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 33e2e1c0d48f6..00aa86087430e 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -706,6 +706,10 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
if (parseFunctionTableOperand(&FunctionTable))
return true;
ExpectFuncType = true;
+ } else if (Name == "call_ref" || Name == "return_call_ref") {
+ // The typed function references forms take a function signature as
+ // their sole explicit operand (the funcref is popped from the stack).
+ ExpectFuncType = true;
} else if (Name == "ref.test") {
// When we get support for wasm-gc types, this should become
// ExpectRefType.
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp
index bf9f8e6e90817..6ad8967f4075f 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp
@@ -617,6 +617,18 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
return Error;
}
+ if (Name == "call_ref" || Name == "return_call_ref") {
+ // Funcref target popped from the stack, followed by the signature's
+ // parameters; pushes the signature's results.
+ bool Error = popType(ErrorLoc, wasm::ValType::FUNCREF);
+ Error |= checkSig(ErrorLoc, LastSig);
+ if (Name == "return_call_ref") {
+ Error |= endOfFunction(ErrorLoc, false);
+ pushType(Polymorphic{});
+ }
+ return Error;
+ }
+
if (Name == "call" || Name == "return_call") {
bool Error = false;
const wasm::WasmSignature *Sig = nullptr;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
index 485b12e256224..263c580ecaf99 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
@@ -81,4 +81,20 @@ defm RET_CALL_INDIRECT :
0x13>,
Requires<[HasTailCall]>;
+// Typed function references: call_ref / return_call_ref pop a funcref from the
+// stack and branch to it, using the given type index to describe the signature.
+let variadicOpsAreDefs = 1 in
+defm CALL_REF :
+ I<(outs), (ins TypeIndex:$type, FUNCREF:$ref, variable_ops),
+ (outs), (ins TypeIndex:$type), [],
+ "call_ref\t$type, $ref", "call_ref\t$type", 0x14>,
+ Requires<[HasReferenceTypes]>;
+
+let isReturn = 1, isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in
+defm RET_CALL_REF :
+ I<(outs), (ins TypeIndex:$type, FUNCREF:$ref, variable_ops),
+ (outs), (ins TypeIndex:$type), [],
+ "return_call_ref\t$type, $ref", "return_call_ref\t$type", 0x15>,
+ Requires<[HasReferenceTypes, HasTailCall]>;
+
} // Uses = [SP32,SP64], isCall = 1
diff --git a/llvm/test/MC/Disassembler/WebAssembly/function-references.txt b/llvm/test/MC/Disassembler/WebAssembly/function-references.txt
new file mode 100644
index 0000000000000..d0d48f6eccef4
--- /dev/null
+++ b/llvm/test/MC/Disassembler/WebAssembly/function-references.txt
@@ -0,0 +1,25 @@
+# RUN: llvm-mc --disassemble %s -triple=wasm32-unknown-unknown -mattr=+reference-types,+tail-call | FileCheck %s
+# RUN: llvm-mc --disassemble %s -triple=wasm64-unknown-unknown -mattr=+reference-types,+tail-call | FileCheck %s
+
+# The Requires<[...]> feature gates do not filter the disassembler tables, so
+# the same bytes should decode even without features enabled.
+# RUN: llvm-mc --disassemble %s -triple=wasm32-unknown-unknown | FileCheck %s
+
+# CHECK: call_ref 0
+0x14 0x00
+
+# CHECK: call_ref 127
+0x14 0x7F
+
+# Multi-byte ULEB type index.
+# CHECK: call_ref 128
+0x14 0x80 0x01
+
+# CHECK: return_call_ref 0
+0x15 0x00
+
+# CHECK: return_call_ref 42
+0x15 0x2A
+
+# CHECK: end
+0x0B
diff --git a/llvm/test/MC/WebAssembly/function-references.s b/llvm/test/MC/WebAssembly/function-references.s
new file mode 100644
index 0000000000000..3bfee9f35d608
--- /dev/null
+++ b/llvm/test/MC/WebAssembly/function-references.s
@@ -0,0 +1,37 @@
+# RUN: llvm-mc -show-encoding -triple=wasm32-unknown-unknown -mattr=+reference-types,+tail-call < %s | FileCheck %s
+# RUN: llvm-mc -show-encoding -triple=wasm64-unknown-unknown -mattr=+reference-types,+tail-call < %s | FileCheck %s
+
+# Verify that dropping the tail-call feature rejects return_call_ref. The
+# reference-types predicate is not enforced at the parser level today (shared
+# with other Requires<[HasReferenceTypes]> opcodes such as ref.null_func), so
+# we only assert the tail-call gate.
+# RUN: not llvm-mc -triple=wasm32-unknown-unknown -mattr=+reference-types %s 2>&1 \
+# RUN: | FileCheck --check-prefix=NO-TAIL-CALL %s
+
+# NO-TAIL-CALL: error: instruction requires: tail-call
+
+call_ref_void:
+ .functype call_ref_void () -> ()
+ ref.null_func
+ # CHECK: call_ref () -> () # encoding: [0x14,
+ # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex0 at TYPEINDEX, kind: fixup_uleb128_i32
+ call_ref () -> ()
+ end_function
+
+call_ref_sig:
+ .functype call_ref_sig () -> (i32)
+ i32.const 1
+ i32.const 2
+ ref.null_func
+ # CHECK: call_ref (i32, i32) -> (i32) # encoding: [0x14,
+ # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex1 at TYPEINDEX, kind: fixup_uleb128_i32
+ call_ref (i32, i32) -> (i32)
+ end_function
+
+return_call_ref_void:
+ .functype return_call_ref_void () -> ()
+ ref.null_func
+ # CHECK: return_call_ref () -> () # encoding: [0x15,
+ # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex2 at TYPEINDEX, kind: fixup_uleb128_i32
+ return_call_ref () -> ()
+ end_function
>From 48ca50dcbfcfcdcfa55e6a0e2b9d3a47a3bbae79 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Wed, 6 May 2026 10:54:50 -0700
Subject: [PATCH 2/2] Address Demetrius' feedback
---
.../AsmParser/WebAssemblyAsmParser.cpp | 4 ++++
.../Target/WebAssembly/WebAssemblyInstrCall.td | 6 ++++--
.../Target/WebAssembly/WebAssemblyInstrRef.td | 5 +++++
.../WebAssembly/function-references.txt | 10 ++++++++--
llvm/test/MC/WebAssembly/function-references.s | 18 ++++++++++++------
5 files changed, 33 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 00aa86087430e..c7b1f9c80f6e3 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -714,6 +714,10 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
// When we get support for wasm-gc types, this should become
// ExpectRefType.
ExpectFuncType = true;
+ } else if (Name == "ref.cast") {
+ // When we get support for wasm-gc types, this should become
+ // ExpectRefType.
+ ExpectFuncType = true;
}
if (Name.contains("atomic.")) {
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
index 263c580ecaf99..5ee489e50fa96 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
@@ -83,18 +83,20 @@ defm RET_CALL_INDIRECT :
// Typed function references: call_ref / return_call_ref pop a funcref from the
// stack and branch to it, using the given type index to describe the signature.
+// These were introduced with the function-references proposal, which was folded
+// into the GC proposal, so they are gated on HasGC.
let variadicOpsAreDefs = 1 in
defm CALL_REF :
I<(outs), (ins TypeIndex:$type, FUNCREF:$ref, variable_ops),
(outs), (ins TypeIndex:$type), [],
"call_ref\t$type, $ref", "call_ref\t$type", 0x14>,
- Requires<[HasReferenceTypes]>;
+ Requires<[HasGC]>;
let isReturn = 1, isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in
defm RET_CALL_REF :
I<(outs), (ins TypeIndex:$type, FUNCREF:$ref, variable_ops),
(outs), (ins TypeIndex:$type), [],
"return_call_ref\t$type, $ref", "return_call_ref\t$type", 0x15>,
- Requires<[HasReferenceTypes, HasTailCall]>;
+ Requires<[HasTailCall, HasGC]>;
} // Uses = [SP32,SP64], isCall = 1
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
index 304c4f3fcb028..f6e366f18f7e3 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
@@ -41,6 +41,11 @@ defm REF_TEST_FUNCREF : I<(outs I32:$res), (ins TypeIndex:$type, FUNCREF:$ref),
"ref.test\t$type, $ref", "ref.test $type", 0xfb14>,
Requires<[HasGC]>;
+defm REF_CAST_FUNCREF : I<(outs FUNCREF:$res), (ins TypeIndex:$type, FUNCREF:$ref),
+ (outs), (ins TypeIndex:$type), [],
+ "ref.cast\t$type, $ref", "ref.cast $type", 0xfb16>,
+ Requires<[HasGC]>;
+
defm REF_FUNC : I<(outs FUNCREF:$res), (ins function32_op:$func),
(outs), (ins function32_op:$func), [],
"ref.func\t$func", "ref.func $func", 0xd2>,
diff --git a/llvm/test/MC/Disassembler/WebAssembly/function-references.txt b/llvm/test/MC/Disassembler/WebAssembly/function-references.txt
index d0d48f6eccef4..4ad50e605e7d0 100644
--- a/llvm/test/MC/Disassembler/WebAssembly/function-references.txt
+++ b/llvm/test/MC/Disassembler/WebAssembly/function-references.txt
@@ -1,5 +1,5 @@
-# RUN: llvm-mc --disassemble %s -triple=wasm32-unknown-unknown -mattr=+reference-types,+tail-call | FileCheck %s
-# RUN: llvm-mc --disassemble %s -triple=wasm64-unknown-unknown -mattr=+reference-types,+tail-call | FileCheck %s
+# RUN: llvm-mc --disassemble %s -triple=wasm32-unknown-unknown -mattr=+reference-types,+tail-call,+gc | FileCheck %s
+# RUN: llvm-mc --disassemble %s -triple=wasm64-unknown-unknown -mattr=+reference-types,+tail-call,+gc | FileCheck %s
# The Requires<[...]> feature gates do not filter the disassembler tables, so
# the same bytes should decode even without features enabled.
@@ -21,5 +21,11 @@
# CHECK: return_call_ref 42
0x15 0x2A
+# CHECK: ref.cast 0
+0xFB 0x16 0x00
+
+# CHECK: ref.cast 127
+0xFB 0x16 0x7F
+
# CHECK: end
0x0B
diff --git a/llvm/test/MC/WebAssembly/function-references.s b/llvm/test/MC/WebAssembly/function-references.s
index 3bfee9f35d608..6bd750d114ee9 100644
--- a/llvm/test/MC/WebAssembly/function-references.s
+++ b/llvm/test/MC/WebAssembly/function-references.s
@@ -1,11 +1,11 @@
-# RUN: llvm-mc -show-encoding -triple=wasm32-unknown-unknown -mattr=+reference-types,+tail-call < %s | FileCheck %s
-# RUN: llvm-mc -show-encoding -triple=wasm64-unknown-unknown -mattr=+reference-types,+tail-call < %s | FileCheck %s
+# RUN: llvm-mc -show-encoding -triple=wasm32-unknown-unknown -mattr=+reference-types,+tail-call,+gc < %s | FileCheck %s
+# RUN: llvm-mc -show-encoding -triple=wasm64-unknown-unknown -mattr=+reference-types,+tail-call,+gc < %s | FileCheck %s
# Verify that dropping the tail-call feature rejects return_call_ref. The
# reference-types predicate is not enforced at the parser level today (shared
# with other Requires<[HasReferenceTypes]> opcodes such as ref.null_func), so
# we only assert the tail-call gate.
-# RUN: not llvm-mc -triple=wasm32-unknown-unknown -mattr=+reference-types %s 2>&1 \
+# RUN: not llvm-mc -triple=wasm32-unknown-unknown -mattr=+reference-types,+gc %s 2>&1 \
# RUN: | FileCheck --check-prefix=NO-TAIL-CALL %s
# NO-TAIL-CALL: error: instruction requires: tail-call
@@ -13,8 +13,11 @@
call_ref_void:
.functype call_ref_void () -> ()
ref.null_func
+ # CHECK: ref.cast () -> () # encoding: [0xfb,0x16,
+ # CHECK-NEXT: fixup A - offset: 2, value: .Ltypeindex0 at TYPEINDEX, kind: fixup_uleb128_i32
+ ref.cast () -> ()
# CHECK: call_ref () -> () # encoding: [0x14,
- # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex0 at TYPEINDEX, kind: fixup_uleb128_i32
+ # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex1 at TYPEINDEX, kind: fixup_uleb128_i32
call_ref () -> ()
end_function
@@ -23,8 +26,11 @@ call_ref_sig:
i32.const 1
i32.const 2
ref.null_func
+ # CHECK: ref.cast (i32, i32) -> (i32) # encoding: [0xfb,0x16,
+ # CHECK-NEXT: fixup A - offset: 2, value: .Ltypeindex2 at TYPEINDEX, kind: fixup_uleb128_i32
+ ref.cast (i32, i32) -> (i32)
# CHECK: call_ref (i32, i32) -> (i32) # encoding: [0x14,
- # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex1 at TYPEINDEX, kind: fixup_uleb128_i32
+ # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex3 at TYPEINDEX, kind: fixup_uleb128_i32
call_ref (i32, i32) -> (i32)
end_function
@@ -32,6 +38,6 @@ return_call_ref_void:
.functype return_call_ref_void () -> ()
ref.null_func
# CHECK: return_call_ref () -> () # encoding: [0x15,
- # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex2 at TYPEINDEX, kind: fixup_uleb128_i32
+ # CHECK-NEXT: fixup A - offset: 1, value: .Ltypeindex4 at TYPEINDEX, kind: fixup_uleb128_i32
return_call_ref () -> ()
end_function
More information about the llvm-commits
mailing list