[clang] [lld] [llvm] [Wasm][Clang] Add support for pointer to externref (PR #163610)

Hood Chatham via cfe-commits cfe-commits at lists.llvm.org
Wed Oct 15 12:18:12 PDT 2025


https://github.com/hoodmane updated https://github.com/llvm/llvm-project/pull/163610

>From d083935d54a1a84a0c54426ee38d38e6a9f04af8 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Wed, 15 Oct 2025 13:53:50 -0400
Subject: [PATCH] [Wasm][Clang] Add support for pointer to externref

Add support for values of type `__externref_t *`.
We add a special table called `__externref_table`. Loads and stores
to externref pointers are converted to table_get and table_set
instructions using `__externref_table`.

This is a bit tricky because tables are given type array of externref
`__externref_t table[]`. Tables are also not first class values and
most operations don't work on them. However, arrays decay to pointers.
Previously since `__externref_t*` was always illegal, this wasn't a
problem. I dealt with this by preventing arrays from decaying to
pointers if the value type of the array is `__externref_t`.
---
 clang/lib/AST/Type.cpp                        |  3 -
 clang/lib/Sema/Sema.cpp                       |  7 +++
 clang/lib/Sema/SemaExprCXX.cpp                | 12 ++--
 clang/lib/Sema/SemaType.cpp                   |  7 +--
 .../test/CodeGen/WebAssembly/wasm-externref.c | 40 ++++++++++++
 clang/test/Sema/wasm-refs-and-tables.c        | 62 +++++++++----------
 lld/test/wasm/externref-table-defined.s       | 31 ++++++++++
 lld/test/wasm/externref-table-undefined.s     | 30 +++++++++
 lld/wasm/Config.h                             |  3 +
 lld/wasm/Driver.cpp                           |  1 +
 lld/wasm/SymbolTable.cpp                      | 43 +++++++++++++
 lld/wasm/SymbolTable.h                        |  3 +
 lld/wasm/Symbols.cpp                          |  1 +
 lld/wasm/Symbols.h                            |  1 +
 lld/wasm/Writer.cpp                           | 11 ++++
 llvm/include/llvm/MC/MCSymbolWasm.h           |  4 ++
 llvm/lib/Object/WasmObjectFile.cpp            | 12 +++-
 .../WebAssembly/Utils/WasmAddressSpaces.h     |  3 +
 .../WebAssembly/WebAssemblyISelDAGToDAG.cpp   | 43 +++++++++++++
 .../WebAssembly/WebAssemblyUtilities.cpp      | 25 ++++++++
 .../Target/WebAssembly/WebAssemblyUtilities.h |  4 ++
 .../CodeGen/WebAssembly/externref-load.ll     | 13 ++++
 .../CodeGen/WebAssembly/externref-store.ll    | 14 +++++
 23 files changed, 328 insertions(+), 45 deletions(-)
 create mode 100644 lld/test/wasm/externref-table-defined.s
 create mode 100644 lld/test/wasm/externref-table-undefined.s
 create mode 100644 llvm/test/CodeGen/WebAssembly/externref-load.ll
 create mode 100644 llvm/test/CodeGen/WebAssembly/externref-store.ll

diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index ee7a68ee8ba7e..48a00d5a8aa12 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2559,9 +2559,6 @@ bool Type::isWebAssemblyTableType() const {
   if (const auto *ATy = dyn_cast<ArrayType>(this))
     return ATy->getElementType().isWebAssemblyReferenceType();
 
-  if (const auto *PTy = dyn_cast<PointerType>(this))
-    return PTy->getPointeeType().isWebAssemblyReferenceType();
-
   return false;
 }
 
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 39fa25f66f3b7..f1851a7096f80 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -832,6 +832,13 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
         }
       }
     }
+    // WebAssembly tables are not first class values and cannot decay to
+    // pointers. If they are used anywhere they would decay, it is an error.
+    if (ExprTy->isWebAssemblyTableType()) {
+      Diag(E->getExprLoc(), diag::err_wasm_cast_table)
+          << 1 << E->getSourceRange();
+      return ExprError();
+    }
   }
 
   if (ImplicitCastExpr *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 0fe242dce45e9..99e12ba0bb936 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -4779,12 +4779,16 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
     break;
   }
 
-  case ICK_Array_To_Pointer:
+  case ICK_Array_To_Pointer: {
     FromType = Context.getArrayDecayedType(FromType);
-    From = ImpCastExprToType(From, FromType, CK_ArrayToPointerDecay, VK_PRValue,
-                             /*BasePath=*/nullptr, CCK)
-               .get();
+    auto Expr =
+        ImpCastExprToType(From, FromType, CK_ArrayToPointerDecay, VK_PRValue,
+                          /*BasePath=*/nullptr, CCK);
+    if (Expr.isInvalid())
+      return ExprError();
+    From = Expr.get();
     break;
+  }
 
   case ICK_HLSL_Array_RValue:
     if (ToType->isArrayParameterType()) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index a9e7c34de94f4..89230071e1bb5 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -1837,11 +1837,10 @@ QualType Sema::BuildPointerType(QualType T,
   if (getLangOpts().OpenCL)
     T = deduceOpenCLPointeeAddrSpace(*this, T);
 
-  // In WebAssembly, pointers to reference types and pointers to tables are
-  // illegal.
   if (getASTContext().getTargetInfo().getTriple().isWasm()) {
-    if (T.isWebAssemblyReferenceType()) {
-      Diag(Loc, diag::err_wasm_reference_pr) << 0;
+    // In WebAssembly, pointers to tables are illegal.
+    if (T->isWebAssemblyTableType()) {
+      Diag(Loc, diag::err_wasm_table_pr) << 0;
       return QualType();
     }
 
diff --git a/clang/test/CodeGen/WebAssembly/wasm-externref.c b/clang/test/CodeGen/WebAssembly/wasm-externref.c
index 788438bb4a86a..9ff517c1fd304 100644
--- a/clang/test/CodeGen/WebAssembly/wasm-externref.c
+++ b/clang/test/CodeGen/WebAssembly/wasm-externref.c
@@ -16,3 +16,43 @@ void helper(externref_t);
 void handle(externref_t obj) {
   helper(obj);
 }
+
+static externref_t __externref_table[0];
+
+externref_t* p = 0;
+
+__attribute__((constructor))
+// CHECK-LABEL: @set_p(
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[TMP0:%.*]] = call ptr addrspace(10) @llvm.wasm.ref.null.extern()
+// CHECK-NEXT:    [[TMP1:%.*]] = call i32 @llvm.wasm.table.grow.externref(ptr addrspace(1) @__externref_table, ptr addrspace(10) [[TMP0]], i32 1)
+// CHECK-NEXT:    [[TMP2:%.*]] = inttoptr i32 [[TMP1]] to ptr
+// CHECK-NEXT:    store ptr [[TMP2]], ptr @p, align 4
+// CHECK-NEXT:    ret void
+//
+void set_p(void) {
+    p = (externref_t *)__builtin_wasm_table_grow(__externref_table, __builtin_wasm_ref_null_extern(), 1);
+}
+
+// CHECK-LABEL: @load_ref(
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr @p, align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr addrspace(10), ptr [[TMP0]], align 1
+// CHECK-NEXT:    ret ptr addrspace(10) [[TMP1]]
+//
+externref_t load_ref() {
+    return *p;
+}
+
+// CHECK-LABEL: @store_ref(
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[X_ADDR:%.*]] = alloca ptr addrspace(10), align 1
+// CHECK-NEXT:    store ptr addrspace(10) [[X:%.*]], ptr [[X_ADDR]], align 1
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr addrspace(10), ptr [[X_ADDR]], align 1
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr @p, align 4
+// CHECK-NEXT:    store ptr addrspace(10) [[TMP0]], ptr [[TMP1]], align 1
+// CHECK-NEXT:    ret void
+//
+void store_ref(externref_t x) {
+    *p = x;
+}
diff --git a/clang/test/Sema/wasm-refs-and-tables.c b/clang/test/Sema/wasm-refs-and-tables.c
index dd8536c52cd03..6249d372c4344 100644
--- a/clang/test/Sema/wasm-refs-and-tables.c
+++ b/clang/test/Sema/wasm-refs-and-tables.c
@@ -9,9 +9,9 @@ __externref_t r1;
 extern __externref_t r2;
 static __externref_t r3;
 
-__externref_t *t1;               // expected-error {{pointer to WebAssembly reference type is not allowed}}
-__externref_t **t2;              // expected-error {{pointer to WebAssembly reference type is not allowed}}
-__externref_t ******t3;          // expected-error {{pointer to WebAssembly reference type is not allowed}}
+__externref_t *t1;
+__externref_t **t2;
+__externref_t ******t3;
 static __externref_t t4[3];      // expected-error {{only zero-length WebAssembly tables are currently supported}}
 static __externref_t t5[];       // expected-error {{only zero-length WebAssembly tables are currently supported}}
 static __externref_t t6[] = {0}; // expected-error {{only zero-length WebAssembly tables are currently supported}}
@@ -28,8 +28,8 @@ struct s {
   __externref_t f2[0];    // expected-error {{field has sizeless type '__externref_t'}}
   __externref_t f3[];     // expected-error {{field has sizeless type '__externref_t'}}
   __externref_t f4[0][0]; // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
-  __externref_t *f5;      // expected-error {{pointer to WebAssembly reference type is not allowed}}
-  __externref_t ****f6;   // expected-error {{pointer to WebAssembly reference type is not allowed}}
+  __externref_t *f5;
+  __externref_t ****f6;
   __externref_t (*f7)[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}
 };
 
@@ -38,21 +38,21 @@ union u {
   __externref_t f2[0];    // expected-error {{field has sizeless type '__externref_t'}}
   __externref_t f3[];     // expected-error {{field has sizeless type '__externref_t'}}
   __externref_t f4[0][0]; // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
-  __externref_t *f5;      // expected-error {{pointer to WebAssembly reference type is not allowed}}
-  __externref_t ****f6;   // expected-error {{pointer to WebAssembly reference type is not allowed}}
+  __externref_t *f5;
+  __externref_t ****f6;
   __externref_t (*f7)[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}
 };
 
-void illegal_argument_1(__externref_t table[]);     // expected-error {{cannot use WebAssembly table as a function parameter}}
-void illegal_argument_2(__externref_t table[0][0]); // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
-void illegal_argument_3(__externref_t *table);      // expected-error {{pointer to WebAssembly reference type is not allowed}}
-void illegal_argument_4(__externref_t ***table);    // expected-error {{pointer to WebAssembly reference type is not allowed}}
-void illegal_argument_5(__externref_t (*table)[0]); // expected-error {{cannot form a pointer to a WebAssembly table}}
-void illegal_argument_6(__externref_t table[0]);    // expected-error {{cannot use WebAssembly table as a function parameter}}
+void illegal_argument_1(__externref_t table[0][0]); // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
+void illegal_argument_2(__externref_t (*table)[0]); // expected-error {{cannot form a pointer to a WebAssembly table}}
 
-__externref_t *illegal_return_1();   // expected-error {{pointer to WebAssembly reference type is not allowed}}
-__externref_t ***illegal_return_2(); // expected-error {{pointer to WebAssembly reference type is not allowed}}
-__externref_t (*illegal_return_3())[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}
+void okay_argument_1(__externref_t *table);
+void okay_argument_2(__externref_t ***table);
+void okay_argument_3(__externref_t table[0]);
+
+__externref_t *okay_return_1();
+__externref_t ***okay_return_2();
+__externref_t (*illegal_return3())[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}
 
 void varargs(int, ...);
 typedef void (*__funcref funcref_t)();
@@ -61,15 +61,14 @@ typedef void (*__funcref __funcref funcref_fail_t)(); // expected-warning {{attr
 __externref_t func(__externref_t ref) {
   &ref;                        // expected-error {{cannot take address of WebAssembly reference}}
   int foo = 40;
-  (__externref_t *)(&foo);     // expected-error {{pointer to WebAssembly reference type is not allowed}}
-  (__externref_t ****)(&foo);  // expected-error {{pointer to WebAssembly reference type is not allowed}}
+  (__externref_t ****)(&foo);
   sizeof(ref);                 // expected-error {{invalid application of 'sizeof' to sizeless type '__externref_t'}}
   sizeof(__externref_t);       // expected-error {{invalid application of 'sizeof' to sizeless type '__externref_t'}}
   sizeof(__externref_t[0]);    // expected-error {{invalid application of 'sizeof' to WebAssembly table}}
   sizeof(table);               // expected-error {{invalid application of 'sizeof' to WebAssembly table}}
   sizeof(__externref_t[0][0]); // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
-  sizeof(__externref_t *);     // expected-error {{pointer to WebAssembly reference type is not allowed}}
-  sizeof(__externref_t ***);   // expected-error {{pointer to WebAssembly reference type is not allowed}};
+  sizeof(__externref_t *);
+  sizeof(__externref_t ***);
   // expected-warning at +1 {{'_Alignof' applied to an expression is a GNU extension}}
   _Alignof(ref);                 // expected-error {{invalid application of 'alignof' to sizeless type '__externref_t'}}
   _Alignof(__externref_t);       // expected-error {{invalid application of 'alignof' to sizeless type '__externref_t'}}
@@ -77,8 +76,8 @@ __externref_t func(__externref_t ref) {
   _Alignof(__externref_t[0]);    // expected-error {{invalid application of 'alignof' to sizeless type '__externref_t'}}
   _Alignof(table);               // expected-warning {{'_Alignof' applied to an expression is a GNU extension}} expected-error {{invalid application of 'alignof' to WebAssembly table}}
   _Alignof(__externref_t[0][0]); // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
-  _Alignof(__externref_t *);     // expected-error {{pointer to WebAssembly reference type is not allowed}}
-  _Alignof(__externref_t ***);   // expected-error {{pointer to WebAssembly reference type is not allowed}};
+  _Alignof(__externref_t *);
+  _Alignof(__externref_t ***);
   varargs(1, ref);               // expected-error {{cannot pass expression of type '__externref_t' to variadic function}}
 
   __externref_t lt1[0];           // expected-error {{WebAssembly table cannot be declared within a function}}
@@ -86,24 +85,23 @@ __externref_t func(__externref_t ref) {
   static __externref_t lt3[0][0]; // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
   static __externref_t(*lt4)[0];  // expected-error {{cannot form a pointer to a WebAssembly table}}
   // conly-error at +2 {{cannot use WebAssembly table as a function parameter}}
-  // cpp-error at +1 {{no matching function for call to 'illegal_argument_1'}}
-  illegal_argument_1(table);
+  // cpp-error at +1 {{no matching function for call to 'okay_argument_3'}}
+  okay_argument_3(table);
   varargs(1, table);              // expected-error {{cannot use WebAssembly table as a function parameter}}
-  table == 1;                     // expected-error {{invalid operands to binary expression ('__attribute__((address_space(1))) __externref_t[0]' and 'int')}}
-  1 >= table;                     // expected-error {{invalid operands to binary expression ('int' and '__attribute__((address_space(1))) __externref_t[0]')}}
-  table == other_table;           // expected-error {{invalid operands to binary expression ('__attribute__((address_space(1))) __externref_t[0]' and '__attribute__((address_space(1))) __externref_t[0]')}}
-  table !=- table;                // expected-error {{invalid argument type '__attribute__((address_space(1))) __externref_t *' to unary expression}}
-  !table;                         // expected-error {{invalid argument type '__attribute__((address_space(1))) __externref_t *' to unary expression}}
+  table == 1;                     // expected-error {{cannot cast from a WebAssembly table}}
+  1 >= table;                     // expected-error {{cannot cast from a WebAssembly table}}
+  table == other_table;           // expected-error {{cannot cast from a WebAssembly table}}
+  table !=- table;                // expected-error {{cannot cast from a WebAssembly table}}
+  !table;                         // expected-error {{cannot cast from a WebAssembly table}}
   1 && table;                     // expected-error {{invalid operands to binary expression ('int' and '__attribute__((address_space(1))) __externref_t[0]')}}
   table || 1;                     // expected-error {{invalid operands to binary expression ('__attribute__((address_space(1))) __externref_t[0]' and 'int')}}
-  1 ? table : table;              // expected-error {{cannot use a WebAssembly table within a branch of a conditional expression}}
-  table ? : other_table;          // expected-error {{cannot use a WebAssembly table within a branch of a conditional expression}}
+  table ? : other_table;          // expected-error {{cannot cast from a WebAssembly table}}
   (void *)table;                  // expected-error {{cannot cast from a WebAssembly table}}
   void *u;
   u = table;                      // expected-error {{cannot assign a WebAssembly table}}
   void *v = table;                // expected-error {{cannot assign a WebAssembly table}}
   &table;                         // expected-error {{cannot form a reference to a WebAssembly table}}
-  (void)table;
+  (void)table;                    // conly-error {{cannot cast from a WebAssembly table}}
 
   table[0];                       // expected-error {{cannot subscript a WebAssembly table}}
   table[0] = ref;                 // expected-error {{cannot subscript a WebAssembly table}}
diff --git a/lld/test/wasm/externref-table-defined.s b/lld/test/wasm/externref-table-defined.s
new file mode 100644
index 0000000000000..41bf7491c1271
--- /dev/null
+++ b/lld/test/wasm/externref-table-defined.s
@@ -0,0 +1,31 @@
+# RUN: llvm-mc -filetype=obj -mattr=+reference-types -triple=wasm32-unknown-unknown %s -o %t.o
+# RUN: wasm-ld -o %t.wasm %t.o
+# RUN: obj2yaml %t.wasm | FileCheck %s
+
+.tabletype	__externref_table, externref
+__externref_table:
+
+.globl _start
+_start:
+  .functype _start () -> ()
+	i32.const	0
+	i32.load	p
+	table.get	__externref_table
+	drop
+	end_function
+
+
+
+.section	.bss.p,"",@
+.globl	p
+.p2align	2, 0x0
+p:
+	.int32	0
+	.size	p, 4
+
+# CHECK:      - Type:            TABLE
+# CHECK-NEXT:    Tables:
+# CHECK-NEXT:      - Index:           0
+# CHECK-NEXT:        ElemType:        EXTERNREF
+# CHECK-NEXT:        Limits:
+# CHECK-NEXT:          Minimum:         0x0
diff --git a/lld/test/wasm/externref-table-undefined.s b/lld/test/wasm/externref-table-undefined.s
new file mode 100644
index 0000000000000..e1aacd674dd88
--- /dev/null
+++ b/lld/test/wasm/externref-table-undefined.s
@@ -0,0 +1,30 @@
+# RUN: llvm-mc -filetype=obj -mattr=+reference-types -triple=wasm32-unknown-unknown %s -o %t.o
+# RUN: wasm-ld -o %t.wasm %t.o
+# RUN: obj2yaml %t.wasm | FileCheck %s
+
+.tabletype	__externref_table, externref
+
+.globl _start
+_start:
+  .functype _start () -> ()
+	i32.const	0
+	i32.load	p
+	table.get	__externref_table
+	drop
+	end_function
+
+
+
+.section	.bss.p,"",@
+.globl	p
+.p2align	2, 0x0
+p:
+	.int32	0
+	.size	p, 4
+
+# CHECK:      - Type:            TABLE
+# CHECK-NEXT:    Tables:
+# CHECK-NEXT:      - Index:           0
+# CHECK-NEXT:        ElemType:        EXTERNREF
+# CHECK-NEXT:        Limits:
+# CHECK-NEXT:          Minimum:         0x0
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 9d903e0c799db..d09cd58389d08 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -248,6 +248,9 @@ struct Ctx {
     // Used as an address space for function pointers, with each function that
     // is used as a function pointer being allocated a slot.
     TableSymbol *indirectFunctionTable;
+    // __externref_table
+    // Used as an address space for pointers to externref.
+    TableSymbol *externrefTable;
   };
   WasmSym sym;
 
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 9c0e1b58e62f9..602dc565daa22 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1496,6 +1496,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // Provide the indirect function table if needed.
   ctx.sym.indirectFunctionTable =
       symtab->resolveIndirectFunctionTable(/*required =*/false);
+  ctx.sym.externrefTable = symtab->resolveExternrefTable();
 
   if (errorCount())
     return;
diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp
index 91677b34ea2ca..041900b0c566e 100644
--- a/lld/wasm/SymbolTable.cpp
+++ b/lld/wasm/SymbolTable.cpp
@@ -818,6 +818,19 @@ TableSymbol *SymbolTable::createDefinedIndirectFunctionTable(StringRef name) {
   return sym;
 }
 
+TableSymbol *SymbolTable::createDefinedExternrefTable(StringRef name) {
+  const uint32_t invalidIndex = -1;
+  WasmLimits limits{0, 0, 0, 0}; // Set by the writer.
+  WasmTableType type{ValType::EXTERNREF, limits};
+  WasmTable desc{invalidIndex, type, name};
+  InputTable *table = make<InputTable>(desc, nullptr);
+  uint32_t flags = ctx.arg.exportTable ? 0 : WASM_SYMBOL_VISIBILITY_HIDDEN;
+  TableSymbol *sym = addSyntheticTable(name, flags, table);
+  sym->markLive();
+  sym->forceExport = ctx.arg.exportTable;
+  return sym;
+}
+
 // Whether or not we need an indirect function table is usually a function of
 // whether an input declares a need for it.  However sometimes it's possible for
 // no input to need the indirect function table, but then a late
@@ -859,6 +872,36 @@ TableSymbol *SymbolTable::resolveIndirectFunctionTable(bool required) {
   return nullptr;
 }
 
+TableSymbol *SymbolTable::resolveExternrefTable() {
+  Symbol *existing = find(externrefTableName);
+  if (existing) {
+    if (!isa<TableSymbol>(existing)) {
+      error(Twine("reserved symbol must be of type table: `") +
+            externrefTableName + "`");
+      return nullptr;
+    }
+    if (existing->isDefined()) {
+      error(Twine("reserved symbol must not be defined in input files: `") +
+            externrefTableName + "`");
+      return nullptr;
+    }
+  }
+
+  if (ctx.arg.importTable) {
+    if (existing) {
+      existing->importModule = defaultModule;
+      existing->importName = externrefTableName;
+      return cast<TableSymbol>(existing);
+    }
+  } else if ((existing && existing->isLive())) {
+    // A defined table is required. The existing table is
+    // guaranteed to be undefined due to the check above.
+    return createDefinedExternrefTable(externrefTableName);
+  }
+
+  return nullptr;
+}
+
 void SymbolTable::addLazy(StringRef name, InputFile *file) {
   LLVM_DEBUG(dbgs() << "addLazy: " << name << "\n");
 
diff --git a/lld/wasm/SymbolTable.h b/lld/wasm/SymbolTable.h
index 5d09d8b685716..004933ca575c4 100644
--- a/lld/wasm/SymbolTable.h
+++ b/lld/wasm/SymbolTable.h
@@ -85,6 +85,7 @@ class SymbolTable {
                           InputFile *file, const WasmSignature *sig);
 
   TableSymbol *resolveIndirectFunctionTable(bool required);
+  TableSymbol *resolveExternrefTable();
 
   void addLazy(StringRef name, InputFile *f);
 
@@ -116,6 +117,8 @@ class SymbolTable {
 
   TableSymbol *createDefinedIndirectFunctionTable(StringRef name);
   TableSymbol *createUndefinedIndirectFunctionTable(StringRef name);
+  TableSymbol *createDefinedExternrefTable(StringRef name);
+  TableSymbol *createUndefinedExternrefTable(StringRef name);
 
   // Maps symbol names to index into the symVector.  -1 means that symbols
   // is to not yet in the vector but it should have tracing enabled if it is
diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp
index f2040441e6257..f77ce35c4bae2 100644
--- a/lld/wasm/Symbols.cpp
+++ b/lld/wasm/Symbols.cpp
@@ -434,6 +434,7 @@ void printTraceSymbol(Symbol *sym) {
 
 const char *defaultModule = "env";
 const char *functionTableName = "__indirect_function_table";
+const char *externrefTableName = "__externref_table";
 const char *memoryName = "memory";
 
 } // namespace wasm
diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h
index 55ee21939ce07..e6d1c4ff1d00c 100644
--- a/lld/wasm/Symbols.h
+++ b/lld/wasm/Symbols.h
@@ -25,6 +25,7 @@ extern const char *defaultModule;
 
 // The name under which to import or export the wasm table.
 extern const char *functionTableName;
+extern const char *externrefTableName;
 
 // The name under which to import or export the wasm memory.
 extern const char *memoryName;
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 9a5b56fc52e2f..bfd74a8221009 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -946,6 +946,15 @@ static void finalizeIndirectFunctionTable() {
   ctx.sym.indirectFunctionTable->setLimits(limits);
 }
 
+static void finalizeExternrefTable() {
+  if (!ctx.sym.externrefTable)
+    return;
+
+  uint32_t tableSize = 0;
+  WasmLimits limits = {0, tableSize, 0, 0};
+  ctx.sym.externrefTable->setLimits(limits);
+}
+
 static void scanRelocations() {
   for (ObjFile *file : ctx.objectFiles) {
     LLVM_DEBUG(dbgs() << "scanRelocations: " << file->getName() << "\n");
@@ -1773,6 +1782,8 @@ void Writer::run() {
   scanRelocations();
   log("-- finalizeIndirectFunctionTable");
   finalizeIndirectFunctionTable();
+  log("-- finalizeExternrefTable");
+  finalizeExternrefTable();
   log("-- createSyntheticInitFunctions");
   createSyntheticInitFunctions();
   log("-- assignIndexes");
diff --git a/llvm/include/llvm/MC/MCSymbolWasm.h b/llvm/include/llvm/MC/MCSymbolWasm.h
index 5c9f14e5e6d64..3e7953cc66ba4 100644
--- a/llvm/include/llvm/MC/MCSymbolWasm.h
+++ b/llvm/include/llvm/MC/MCSymbolWasm.h
@@ -116,6 +116,10 @@ class MCSymbolWasm : public MCSymbol {
     return isTable() && hasTableType() &&
            getTableType().ElemType == wasm::ValType::FUNCREF;
   }
+  bool isExternrefTable() const {
+    return isTable() && hasTableType() &&
+           getTableType().ElemType == wasm::ValType::EXTERNREF;
+  }
   void setFunctionTable(bool is64) {
     setType(wasm::WASM_SYMBOL_TYPE_TABLE);
     uint8_t flags =
diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp
index ee7a3068af91d..5ed5a1d7f2c9a 100644
--- a/llvm/lib/Object/WasmObjectFile.cpp
+++ b/llvm/lib/Object/WasmObjectFile.cpp
@@ -1098,7 +1098,11 @@ Error WasmObjectFile::parseRelocSection(StringRef Name, ReadContext &Ctx) {
     case wasm::R_WASM_MEMORY_ADDR_REL_SLEB:
     case wasm::R_WASM_MEMORY_ADDR_TLS_SLEB:
     case wasm::R_WASM_MEMORY_ADDR_LOCREL_I32:
-      if (!isValidDataSymbol(Reloc.Index))
+      // Allow table symbols in debug sections (for debug info referencing
+      // tables)
+      if (!isValidDataSymbol(Reloc.Index) &&
+          !(Section.Type == wasm::WASM_SEC_CUSTOM &&
+            isValidTableSymbol(Reloc.Index)))
         return badReloc("invalid data relocation");
       Reloc.Addend = readVarint32(Ctx);
       break;
@@ -1107,7 +1111,11 @@ Error WasmObjectFile::parseRelocSection(StringRef Name, ReadContext &Ctx) {
     case wasm::R_WASM_MEMORY_ADDR_I64:
     case wasm::R_WASM_MEMORY_ADDR_REL_SLEB64:
     case wasm::R_WASM_MEMORY_ADDR_TLS_SLEB64:
-      if (!isValidDataSymbol(Reloc.Index))
+      // Allow table symbols in debug sections (for debug info referencing
+      // tables)
+      if (!isValidDataSymbol(Reloc.Index) &&
+          !(Section.Type == wasm::WASM_SEC_CUSTOM &&
+            isValidTableSymbol(Reloc.Index)))
         return badReloc("invalid data relocation");
       Reloc.Addend = readVarint64(Ctx);
       break;
diff --git a/llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h b/llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h
index 2239badca69c3..189b31898b49c 100644
--- a/llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h
+++ b/llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h
@@ -40,6 +40,9 @@ inline bool isWasmVarAddressSpace(unsigned AS) {
 inline bool isValidAddressSpace(unsigned AS) {
   return isDefaultAddressSpace(AS) || isWasmVarAddressSpace(AS);
 }
+inline bool isExternrefAddressSpace(unsigned AS) {
+  return AS == WASM_ADDRESS_SPACE_EXTERNREF;
+}
 
 } // namespace WebAssembly
 
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
index 2541b0433ab59..9e7b25219fa96 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
@@ -227,6 +227,49 @@ void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
     return;
   }
 
+  case ISD::LOAD: {
+    LoadSDNode *LN = cast<LoadSDNode>(Node);
+    EVT VT = LN->getValueType(0);
+
+    if (VT == MVT::externref) {
+      MCSymbol *Table = WebAssembly::getOrCreateExternrefTableSymbol(
+          MF.getContext(), Subtarget);
+      SDValue TableSym = CurDAG->getMCSymbol(Table, PtrVT);
+      SDValue Ptr = LN->getOperand(1);
+
+      MachineSDNode *TableGet = CurDAG->getMachineNode(
+          WebAssembly::TABLE_GET_EXTERNREF, DL, MVT::externref, MVT::Other,
+          TableSym, Ptr, LN->getChain());
+
+      ReplaceNode(Node, TableGet);
+      CurDAG->RemoveDeadNode(Node);
+      return;
+    }
+    break;
+  }
+
+  case ISD::STORE: {
+    StoreSDNode *SN = cast<StoreSDNode>(Node);
+    SDValue Value = SN->getOperand(1);
+    EVT VT = Value.getValueType();
+
+    if (VT == MVT::externref) {
+      MCSymbol *Table = WebAssembly::getOrCreateExternrefTableSymbol(
+          MF.getContext(), Subtarget);
+      SDValue TableSym = CurDAG->getMCSymbol(Table, PtrVT);
+      SDValue Ptr = SN->getOperand(2);
+
+      MachineSDNode *TableSet = CurDAG->getMachineNode(
+          WebAssembly::TABLE_SET_EXTERNREF, DL, MVT::Other,
+          {TableSym, Ptr, Value, SN->getChain()});
+
+      ReplaceNode(Node, TableSet);
+      CurDAG->RemoveDeadNode(Node);
+      return;
+    }
+    break;
+  }
+
   case ISD::INTRINSIC_WO_CHAIN: {
     unsigned IntNo = Node->getConstantOperandVal(0);
     switch (IntNo) {
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
index 890486778e700..f4fc9f02f9047 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
@@ -120,6 +120,31 @@ MCSymbolWasm *WebAssembly::getOrCreateFunctionTableSymbol(
   return Sym;
 }
 
+MCSymbolWasm *WebAssembly::getOrCreateExternrefTableSymbol(
+    MCContext &Ctx, const WebAssemblySubtarget *Subtarget) {
+  StringRef Name = "__externref_table";
+  auto *Sym = static_cast<MCSymbolWasm *>(Ctx.lookupSymbol(Name));
+  if (Sym) {
+    // If the symbol exists but doesn't have the proper table type, set it now
+    if (Sym->isExternrefTable()) {
+      // Type is correct all is good
+    } else if (!Sym->getType().has_value()) {
+      // Symbol has no type set yet. This happens if it was declared like
+      // static __externref_t __externref_table[0];
+      Sym->setType(wasm::WASM_SYMBOL_TYPE_TABLE);
+      Sym->setTableType(wasm::ValType::EXTERNREF);
+    } else {
+      Ctx.reportError(SMLoc(), "symbol is not a wasm externref table");
+    }
+  } else {
+    Sym = static_cast<MCSymbolWasm *>(Ctx.getOrCreateSymbol(Name));
+    Sym->setType(wasm::WASM_SYMBOL_TYPE_TABLE);
+    Sym->setTableType(wasm::ValType::EXTERNREF);
+  }
+  // MVP object files can't have symtab entries for tables.
+  return Sym;
+}
+
 MCSymbolWasm *WebAssembly::getOrCreateFuncrefCallTableSymbol(
     MCContext &Ctx, const WebAssemblySubtarget *Subtarget) {
   StringRef Name = "__funcref_call_table";
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
index 046b1b5db2a79..795374cd1249f 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
@@ -56,6 +56,10 @@ MCSymbolWasm *
 getOrCreateFuncrefCallTableSymbol(MCContext &Ctx,
                                   const WebAssemblySubtarget *Subtarget);
 
+MCSymbolWasm *
+getOrCreateExternrefTableSymbol(MCContext &Ctx,
+                                const WebAssemblySubtarget *Subtarget);
+
 /// Find a catch instruction from an EH pad. Returns null if no catch
 /// instruction found or the catch is in an invalid location.
 MachineInstr *findCatch(MachineBasicBlock *EHPad);
diff --git a/llvm/test/CodeGen/WebAssembly/externref-load.ll b/llvm/test/CodeGen/WebAssembly/externref-load.ll
new file mode 100644
index 0000000000000..14f4e8be04a65
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/externref-load.ll
@@ -0,0 +1,13 @@
+; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s
+
+%externref = type ptr addrspace(10)
+
+; CHECK-LABEL: load_ref:
+; CHECK-NEXT: .functype load_ref (i32) -> (externref)
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: table.get	__externref_table
+define %externref @load_ref(ptr %p) {
+entry:
+  %1 = load %externref, ptr %p
+  ret %externref %1
+}
diff --git a/llvm/test/CodeGen/WebAssembly/externref-store.ll b/llvm/test/CodeGen/WebAssembly/externref-store.ll
new file mode 100644
index 0000000000000..3cfdaaa0733fb
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/externref-store.ll
@@ -0,0 +1,14 @@
+; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s
+
+%externref = type ptr addrspace(10)
+
+; CHECK-LABEL: store_ref:
+; CHECK-NEXT: .functype store_ref (i32, externref) -> ()
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: table.set	__externref_table
+define void @store_ref(ptr %p, %externref %x) {
+entry:
+  store %externref %x, ptr %p
+  ret void
+}



More information about the cfe-commits mailing list