Skip to content

Conversation

hoodmane
Copy link
Contributor

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.

@hoodmane hoodmane marked this pull request as draft October 15, 2025 18:33
@llvmbot llvmbot added clang Clang issues not falling into any other category lld backend:WebAssembly clang:frontend Language frontend issues, e.g. anything involving "Sema" llvm:mc Machine (object) code lld:wasm llvm:binary-utilities labels Oct 15, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 15, 2025

@llvm/pr-subscribers-llvm-mc
@llvm/pr-subscribers-lld
@llvm/pr-subscribers-backend-webassembly

@llvm/pr-subscribers-lld-wasm

Author: Hood Chatham (hoodmane)

Changes

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.


Patch is 30.07 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/163610.diff

23 Files Affected:

  • (modified) clang/lib/AST/Type.cpp (-3)
  • (modified) clang/lib/Sema/Sema.cpp (+7)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+8-4)
  • (modified) clang/lib/Sema/SemaType.cpp (+3-4)
  • (modified) clang/test/CodeGen/WebAssembly/wasm-externref.c (+40)
  • (modified) clang/test/Sema/wasm-refs-and-tables.c (+30-32)
  • (added) lld/test/wasm/externref-table-defined.s (+31)
  • (added) lld/test/wasm/externref-table-undefined.s (+30)
  • (modified) lld/wasm/Config.h (+3)
  • (modified) lld/wasm/Driver.cpp (+1)
  • (modified) lld/wasm/SymbolTable.cpp (+43)
  • (modified) lld/wasm/SymbolTable.h (+3)
  • (modified) lld/wasm/Symbols.cpp (+1)
  • (modified) lld/wasm/Symbols.h (+1)
  • (modified) lld/wasm/Writer.cpp (+11)
  • (modified) llvm/include/llvm/MC/MCSymbolWasm.h (+4)
  • (modified) llvm/lib/Object/WasmObjectFile.cpp (+10-2)
  • (modified) llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h (+3)
  • (modified) llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp (+43)
  • (modified) llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp (+26)
  • (modified) llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h (+4)
  • (added) llvm/test/CodeGen/WebAssembly/externref-load.ll (+13)
  • (added) llvm/test/CodeGen/WebAssembly/externref-store.ll (+14)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index f7949e94d227e..2e37d90171ad4 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2613,9 +2613,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 12e5c658e7f3a..98578753d4d04 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 29c9c47d4504c..0e3fc9faee281 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -4774,12 +4774,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 d745cdbf0526f..8e436069bbfdf 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@+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@+2 {{cannot use WebAssembly table as a function parameter}}
-  // cpp-error@+1 {{no matching function for call to 'illegal_argument_1'}}
-  illegal_argument_1(table);
+  // cpp-error@+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 1c5d21c06f5af..01f659b9b6ed9 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1493,6 +1493,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 *createDefinedExt...
[truncated]

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`.
@sbc100
Copy link
Collaborator

sbc100 commented Oct 15, 2025

I think this is a continuation of the work that @pmatos started. So CC'ing him in case he has any time to take a look. Also @dschuff and @tlively

Its quite a large and consequential change so perhaps you could describe here (or in the link a bug?) what you use case is and what kind of thing things unlock for you?

@sbc100
Copy link
Collaborator

sbc100 commented Oct 15, 2025

(BTW, the commit title prefix we use for Wasm is [WebAssembly]

@hoodmane hoodmane changed the title [Wasm][Clang] Add support for pointer to externref [WebAssembly][Clang] Add support for pointer to externref Oct 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:WebAssembly clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category lld:wasm lld llvm:binary-utilities llvm:mc Machine (object) code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants