Skip to content

[WebAssembly] Add support for nonnull_extern_ref type #148935

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Return a WebAssembly externref type.
QualType getWebAssemblyExternrefType() const;

/// Return a WebAssembly non null externref type.
QualType getWebAssemblyNonNullExternrefType() const;

/// Return the unique reference to a vector type of the specified
/// element type and size.
///
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,9 @@ class QualType {
/// Returns true if it is a WebAssembly Externref Type.
bool isWebAssemblyExternrefType() const;

/// Returns true if it is a WebAssembly non null Externref Type.
bool isWebAssemblyNonNullExternrefType() const;

/// Returns true if it is a WebAssembly Funcref Type.
bool isWebAssemblyFuncrefType() const;

Expand Down Expand Up @@ -2402,6 +2405,9 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
/// Check if this is a WebAssembly Externref Type.
bool isWebAssemblyExternrefType() const;

/// Check if this is a WebAssembly non null Externref Type.
bool isWebAssemblyNonNullExternrefType() const;

/// Returns true if this is a WebAssembly table type: either an array of
/// reference types, or a pointer to a reference type (which can only be
/// created by array to pointer decay).
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/WebAssemblyReferenceTypes.def
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,7 @@

WASM_REF_TYPE("__externref_t", "externref_t", WasmExternRef, WasmExternRefTy, 10)

WASM_REF_TYPE("__non_null_externref_t", "non_null_externref_t", WasmNonNullExternRef, WasmNonNullExternRefTy, 10)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this should be 11 or something... So that when we constraint the types behaviour we can differentiate it from (the nullable) externref.


#undef WASM_TYPE
#undef WASM_REF_TYPE
4 changes: 3 additions & 1 deletion clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3550,6 +3550,7 @@ static void encodeTypeForFunctionPointerAuth(const ASTContext &Ctx,
#define AMDGPU_TYPE(Name, Id, SingletonId, Width, Align) case BuiltinType::Id:
#include "clang/Basic/AMDGPUTypes.def"
case BuiltinType::WasmExternRef:
case BuiltinType::WasmNonNullExternRef:
#define RVV_TYPE(Name, Id, SingletonId) case BuiltinType::Id:
#include "clang/Basic/RISCVVTypes.def"
llvm_unreachable("not yet implemented");
Expand Down Expand Up @@ -4584,7 +4585,8 @@ ASTContext::getBuiltinVectorTypeInfo(const BuiltinType *Ty) const {
QualType ASTContext::getWebAssemblyExternrefType() const {
if (Target->getTriple().isWasm() && Target->hasFeature("reference-types")) {
#define WASM_REF_TYPE(Name, MangledName, Id, SingletonId, AS) \
if (BuiltinType::Id == BuiltinType::WasmExternRef) \
if (BuiltinType::Id == BuiltinType::WasmExternRef || \
BuiltinType::Id == BuiltinType::WasmNonNullExternRef) \
return SingletonId;
#include "clang/Basic/WebAssemblyReferenceTypes.def"
}
Expand Down
13 changes: 12 additions & 1 deletion clang/lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2553,6 +2553,12 @@ bool Type::isWebAssemblyExternrefType() const {
return false;
}

bool Type::isWebAssemblyNonNullExternrefType() const {
if (const auto *BT = getAs<BuiltinType>())
return BT->getKind() == BuiltinType::WasmNonNullExternRef;
return false;
}

bool Type::isWebAssemblyTableType() const {
if (const auto *ATy = dyn_cast<ArrayType>(this))
return ATy->getElementType().isWebAssemblyReferenceType();
Expand Down Expand Up @@ -2922,13 +2928,18 @@ bool QualType::hasNonTrivialToPrimitiveCopyCUnion(const RecordDecl *RD) {
}

bool QualType::isWebAssemblyReferenceType() const {
return isWebAssemblyExternrefType() || isWebAssemblyFuncrefType();
return isWebAssemblyExternrefType() || isWebAssemblyNonNullExternrefType() ||
isWebAssemblyFuncrefType();
}

bool QualType::isWebAssemblyExternrefType() const {
return getTypePtr()->isWebAssemblyExternrefType();
}

bool QualType::isWebAssemblyNonNullExternrefType() const {
return getTypePtr()->isWebAssemblyNonNullExternrefType();
}

bool QualType::isWebAssemblyFuncrefType() const {
return getTypePtr()->isFunctionPointerType() &&
getAddressSpace() == LangAS::wasm_funcref;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CodeGen/CodeGenTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,9 @@ llvm::Type *CodeGenTypes::ConvertType(QualType T) {
case BuiltinType::Id: { \
if (BuiltinType::Id == BuiltinType::WasmExternRef) \
ResultType = CGM.getTargetCodeGenInfo().getWasmExternrefReferenceType(); \
else if (BuiltinType::Id == BuiltinType::WasmNonNullExternRef) \
ResultType = \
CGM.getTargetCodeGenInfo().getWasmNonNullExternrefReferenceType(); \
else \
llvm_unreachable("Unexpected wasm reference builtin type!"); \
} break;
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/TargetInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,10 @@ class TargetCodeGenInfo {
/// Return the WebAssembly externref reference type.
virtual llvm::Type *getWasmExternrefReferenceType() const { return nullptr; }

/// Return the WebAssembly externref reference type.
virtual llvm::Type *getWasmNonNullExternrefReferenceType() const {
return nullptr;
}
/// Return the WebAssembly funcref reference type.
virtual llvm::Type *getWasmFuncrefReferenceType() const { return nullptr; }

Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CodeGen/Targets/WebAssembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class WebAssemblyTargetCodeGenInfo final : public TargetCodeGenInfo {
virtual llvm::Type *getWasmExternrefReferenceType() const override {
return llvm::Type::getWasm_ExternrefTy(getABIInfo().getVMContext());
}
virtual llvm::Type *getWasmNonNullExternrefReferenceType() const override {
return llvm::Type::getWasm_NonNullExternrefTy(getABIInfo().getVMContext());
}
/// Return the WebAssembly funcref reference type.
virtual llvm::Type *getWasmFuncrefReferenceType() const override {
return llvm::Type::getWasm_FuncrefTy(getABIInfo().getVMContext());
Expand Down
47 changes: 47 additions & 0 deletions clang/test/CodeGen/WebAssembly/wasm-externref.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// RUN: %clang_cc1 -triple wasm32-unknown-unknown -target-feature +reference-types -o - -emit-llvm %s | FileCheck %s

typedef __externref_t externref_t;
typedef __non_null_externref_t nn_externref_t;

void helper(externref_t);
void helper_2(nn_externref_t);

// CHECK-LABEL: @handle(
// CHECK-NEXT: entry:
Expand All @@ -16,3 +18,48 @@ void helper(externref_t);
void handle(externref_t obj) {
helper(obj);
}


// CHECK-LABEL: @handle_2(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[OBJ_ADDR:%.*]] = alloca ptr addrspace(10), align 1
// CHECK-NEXT: store ptr addrspace(10) [[OBJ:%.*]], ptr [[OBJ_ADDR]], align 1
// CHECK-NEXT: [[TMP0:%.*]] = load ptr addrspace(10), ptr [[OBJ_ADDR]], align 1
// CHECK-NEXT: call void @helper_2(ptr addrspace(10) [[TMP0]])
// CHECK-NEXT: ret void
//
void handle_2(nn_externref_t obj) {
helper_2(obj);
}


nn_externref_t socketpair_js_concat(nn_externref_t, nn_externref_t)
__attribute__((import_module("wasm:js-string"), import_name("concat")));

nn_externref_t get_string_ref(const char *s);
void print_string_ref(nn_externref_t);

// CHECK-LABEL: @socketpair_example(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[STR1:%.*]] = alloca ptr addrspace(10), align 1
// CHECK-NEXT: [[STR2:%.*]] = alloca ptr addrspace(10), align 1
// CHECK-NEXT: [[RESULT:%.*]] = alloca ptr addrspace(10), align 1
// CHECK-NEXT: [[CALL:%.*]] = call ptr addrspace(10) @get_string_ref(ptr noundef @.str)
// CHECK-NEXT: store ptr addrspace(10) [[CALL]], ptr [[STR1]], align 1
// CHECK-NEXT: [[CALL1:%.*]] = call ptr addrspace(10) @get_string_ref(ptr noundef @.str.1)
// CHECK-NEXT: store ptr addrspace(10) [[CALL1]], ptr [[STR2]], align 1
// CHECK-NEXT: [[TMP0:%.*]] = load ptr addrspace(10), ptr [[STR1]], align 1
// CHECK-NEXT: [[TMP1:%.*]] = load ptr addrspace(10), ptr [[STR2]], align 1
// CHECK-NEXT: [[CALL2:%.*]] = call ptr addrspace(10) @socketpair_js_concat(ptr addrspace(10) [[TMP0]], ptr addrspace(10) [[TMP1]])
// CHECK-NEXT: store ptr addrspace(10) [[CALL2]], ptr [[RESULT]], align 1
// CHECK-NEXT: [[TMP2:%.*]] = load ptr addrspace(10), ptr [[RESULT]], align 1
// CHECK-NEXT: call void @print_string_ref(ptr addrspace(10) [[TMP2]])
// CHECK-NEXT: ret void
//
void socketpair_example() {
nn_externref_t str1 = get_string_ref("Hello, ");
nn_externref_t str2 = get_string_ref("world!");
nn_externref_t result = socketpair_js_concat(str1, str2);
print_string_ref(result);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

It would be interesting to have some tests to ensure the semantics of non-null externref. For example, you can't construct a null and assign it to non-null externref. You cannot assign an nullable externref to a non-nullable externref, however the opposite should work. You should be able to assign a non-nullable to a nullable.

51 changes: 51 additions & 0 deletions clang/test/Sema/wasm-refs-and-tables.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ __externref_t r1;
extern __externref_t r2;
static __externref_t r3;

__non_null_externref_t nn_r1;
extern __non_null_externref_t nn_r2;
static __non_null_externref_t nn_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}}
Expand All @@ -19,10 +23,24 @@ __externref_t t7[0]; // expected-error {{WebAssembly table must be s
static __externref_t t8[0][0]; // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
static __externref_t (*t9)[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}

__non_null_externref_t *nn_t1; // expected-error {{pointer to WebAssembly reference type is not allowed}}
__non_null_externref_t **nn_t2; // expected-error {{pointer to WebAssembly reference type is not allowed}}
__non_null_externref_t ******nn_t3; // expected-error {{pointer to WebAssembly reference type is not allowed}}
static __non_null_externref_t nn_t4[3]; // expected-error {{only zero-length WebAssembly tables are currently supported}}
static __non_null_externref_t nn_t5[]; // expected-error {{only zero-length WebAssembly tables are currently supported}}
static __non_null_externref_t nn_t6[] = {0}; // expected-error {{only zero-length WebAssembly tables are currently supported}}
__non_null_externref_t nn_t7[0]; // expected-error {{WebAssembly table must be static}}
static __non_null_externref_t nn_t8[0][0]; // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
static __non_null_externref_t (*nn_t9)[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}

static __externref_t table[0];
static __externref_t other_table[0] = {};
static __externref_t another_table[] = {}; // expected-error {{only zero-length WebAssembly tables are currently supported}}

static __non_null_externref_t nn_table[0];
static __non_null_externref_t nn_other_table[0] = {};
static __non_null_externref_t nn_another_table[] = {}; // expected-error {{only zero-length WebAssembly tables are currently supported}}

struct s {
__externref_t f1; // expected-error {{field has sizeless type '__externref_t'}}
__externref_t f2[0]; // expected-error {{field has sizeless type '__externref_t'}}
Expand All @@ -33,6 +51,17 @@ struct s {
__externref_t (*f7)[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}
};


struct nn_s {
__externref_t nn_f1; // expected-error {{field has sizeless type '__externref_t'}}
__externref_t nn_f2[0]; // expected-error {{field has sizeless type '__externref_t'}}
__externref_t nn_f3[]; // expected-error {{field has sizeless type '__externref_t'}}
__externref_t nn_f4[0][0]; // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
__externref_t *nn_f5; // expected-error {{pointer to WebAssembly reference type is not allowed}}
__externref_t ****nn_f6; // expected-error {{pointer to WebAssembly reference type is not allowed}}
__externref_t (*nn_f7)[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}
Copy link
Contributor

Choose a reason for hiding this comment

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

__non_null_externref_t?

};

union u {
__externref_t f1; // expected-error {{field has sizeless type '__externref_t'}}
__externref_t f2[0]; // expected-error {{field has sizeless type '__externref_t'}}
Expand All @@ -43,16 +72,38 @@ union u {
__externref_t (*f7)[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}
};


union nn_u {
__externref_t nn_f1; // expected-error {{field has sizeless type '__externref_t'}}
__externref_t nn_f2[0]; // expected-error {{field has sizeless type '__externref_t'}}
__externref_t nn_f3[]; // expected-error {{field has sizeless type '__externref_t'}}
__externref_t nn_f4[0][0]; // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
__externref_t *nn_f5; // expected-error {{pointer to WebAssembly reference type is not allowed}}
__externref_t ****nn_f6; // expected-error {{pointer to WebAssembly reference type is not allowed}}
__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_nn_argument_1(__non_null_externref_t table[]); // expected-error {{cannot use WebAssembly table as a function parameter}}
void illegal_nn_argument_2(__non_null_externref_t table[0][0]); // expected-error {{multi-dimensional arrays of WebAssembly references are not allowed}}
void illegal_nn_argument_3(__non_null_externref_t *table); // expected-error {{pointer to WebAssembly reference type is not allowed}}
void illegal_nn_argument_4(__non_null_externref_t ***table); // expected-error {{pointer to WebAssembly reference type is not allowed}}
void illegal_nn_argument_5(__non_null_externref_t (*table)[0]); // expected-error {{cannot form a pointer to a WebAssembly table}}
void illegal_nn_argument_6(__non_null_externref_t table[0]); // expected-error {{cannot use WebAssembly table as a function parameter}}

__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}}

__non_null_externref_t *illegal_nn_return_1(); // expected-error {{pointer to WebAssembly reference type is not allowed}}
__non_null_externref_t ***illegal_nn_return_2(); // expected-error {{pointer to WebAssembly reference type is not allowed}}
__non_null_externref_t (*illegal_nn_return_3())[0]; // expected-error {{cannot form a pointer to a WebAssembly table}}

void varargs(int, ...);
typedef void (*__funcref funcref_t)();
Expand Down
5 changes: 5 additions & 0 deletions llvm/include/llvm/IR/IntrinsicsWebAssembly.td
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ def int_wasm_memory_grow :
//===----------------------------------------------------------------------===//
def int_wasm_ref_null_extern :
DefaultAttrsIntrinsic<[llvm_externref_ty], [], [IntrNoMem]>;

// TODO: Is this correct?
def int_wasm_ref_nonnull_extern
: DefaultAttrsIntrinsic<[llvm_externref_ty], [], [IntrNoMem]>;

def int_wasm_ref_null_func :
DefaultAttrsIntrinsic<[llvm_funcref_ty], [], [IntrNoMem]>;
def int_wasm_ref_null_exn:
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/IR/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ class Type {
// Convenience methods for getting pointer types.
//
LLVM_ABI static Type *getWasm_ExternrefTy(LLVMContext &C);
LLVM_ABI static Type *getWasm_NonNullExternrefTy(LLVMContext &C);
LLVM_ABI static Type *getWasm_FuncrefTy(LLVMContext &C);

/// Return a pointer to the current type. This is equivalent to
Expand Down
7 changes: 7 additions & 0 deletions llvm/lib/IR/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ Type *Type::getWasm_ExternrefTy(LLVMContext &C) {
return Ty;
}

Type *Type::getWasm_NonNullExternrefTy(LLVMContext &C) {
// opaque pointer in addrspace(10)
// TODO: Hey Jasmine, Is this correct?
Copy link
Contributor

Choose a reason for hiding this comment

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

Because you cannot assign non-null to null externrefs interchangeably, it's almost like a different type. My initial thought would be to create a new AS for this.

static PointerType *Ty = PointerType::get(C, 10);
return Ty;
}

Type *Type::getWasm_FuncrefTy(LLVMContext &C) {
// opaque pointer in addrspace(20)
static PointerType *Ty = PointerType::get(C, 20);
Expand Down
Loading