[clang] [HLSL] Implement `export` keyword (PR #96823)

Helena Kotas via cfe-commits cfe-commits at lists.llvm.org
Thu Jun 27 18:26:45 PDT 2024


https://github.com/hekota updated https://github.com/llvm/llvm-project/pull/96823

>From b67ecd20cc2c11f4f99c2d90c95fdbd988659947 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Wed, 26 Jun 2024 12:31:39 -0700
Subject: [PATCH 1/3] [HLSL] Implement `export` keyword

Fixes #92812
---
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 ++
 clang/lib/Parse/ParseDeclCXX.cpp              |  8 +++
 clang/lib/Parse/Parser.cpp                    |  2 +-
 clang/lib/Sema/SemaModule.cpp                 | 32 ++++++++++++
 clang/test/AST/HLSL/export.hlsl               | 23 +++++++++
 clang/test/CodeGenHLSL/export.hlsl            | 20 ++++++++
 clang/test/SemaHLSL/export.hlsl               | 50 +++++++++++++++++++
 7 files changed, 137 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/AST/HLSL/export.hlsl
 create mode 100644 clang/test/CodeGenHLSL/export.hlsl
 create mode 100644 clang/test/SemaHLSL/export.hlsl

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 25a87078a5709..a2465ecc936e0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12265,6 +12265,9 @@ def warn_hlsl_availability_unavailable :
   Warning<err_unavailable.Summary>,
   InGroup<HLSLAvailability>, DefaultError;
 
+def err_hlsl_export_not_on_function : Error<
+  "export declaration can only be used on functions">;
+
 // Layout randomization diagnostics.
 def err_non_designated_init_used : Error<
   "a randomized struct can only be initialized with a designated initializer">;
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 5e3ee5f0579aa..226377e93fe56 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -445,6 +445,14 @@ Decl *Parser::ParseLinkage(ParsingDeclSpec &DS, DeclaratorContext Context) {
 ///         'export' declaration
 ///         'export' '{' declaration-seq[opt] '}'
 ///
+/// HLSL: Parse export function declaration.
+///
+///      export-function-declaration: 
+///         'export' function-declaration
+/// 
+///      export-declaration-group:
+///         'export' '{' function-declaration-seq[opt] '}'
+///
 Decl *Parser::ParseExportDeclaration() {
   assert(Tok.is(tok::kw_export));
   SourceLocation ExportLoc = ConsumeToken();
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 6d0cf7b174e50..ddc8aa9b49e64 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -970,7 +970,7 @@ Parser::ParseExternalDeclaration(ParsedAttributes &Attrs,
     SingleDecl = ParseModuleImport(SourceLocation(), IS);
   } break;
   case tok::kw_export:
-    if (getLangOpts().CPlusPlusModules) {
+    if (getLangOpts().CPlusPlusModules || getLangOpts().HLSL) {
       ProhibitAttributes(Attrs);
       SingleDecl = ParseExportDeclaration();
       break;
diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index ad118ac90e4aa..e920b880ecb4d 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -851,6 +851,21 @@ Decl *Sema::ActOnStartExportDecl(Scope *S, SourceLocation ExportLoc,
   CurContext->addDecl(D);
   PushDeclContext(S, D);
 
+  if (getLangOpts().HLSL) {
+    // exported functions cannot be in an unnamed namespace
+    for (const DeclContext *DC = CurContext; DC; DC = DC->getLexicalParent()) {
+      if (const auto *ND = dyn_cast<NamespaceDecl>(DC)) {
+        if (ND->isAnonymousNamespace()) {
+          Diag(ExportLoc, diag::err_export_within_anonymous_namespace);
+          Diag(ND->getLocation(), diag::note_anonymous_namespace);
+          D->setInvalidDecl();
+          return D;
+        }
+      }
+    }
+    return D;
+  }
+
   // C++2a [module.interface]p1:
   //   An export-declaration shall appear only [...] in the purview of a module
   //   interface unit. An export-declaration shall not appear directly or
@@ -924,6 +939,23 @@ static bool checkExportedDeclContext(Sema &S, DeclContext *DC,
 /// Check that it's valid to export \p D.
 static bool checkExportedDecl(Sema &S, Decl *D, SourceLocation BlockStart) {
 
+  // HLSL: export declaration is valid only on functions
+  if (S.getLangOpts().HLSL) {
+    auto *FD = dyn_cast<FunctionDecl>(D);
+    if (!FD) {
+      if (auto *ED2 = dyn_cast<ExportDecl>(D)) {
+        S.Diag(ED2->getBeginLoc(), diag::err_export_within_export);
+        if (auto *ED1 = dyn_cast<ExportDecl>(D->getDeclContext()))
+          S.Diag(ED1->getBeginLoc(), diag::note_export);
+      }
+      else {
+        S.Diag(D->getBeginLoc(), diag::err_hlsl_export_not_on_function);
+      }
+      D->setInvalidDecl();
+      return false;
+    }
+  }
+
   //  C++20 [module.interface]p3:
   //   [...] it shall not declare a name with internal linkage.
   bool HasName = false;
diff --git a/clang/test/AST/HLSL/export.hlsl b/clang/test/AST/HLSL/export.hlsl
new file mode 100644
index 0000000000000..69c4fb2b457ac
--- /dev/null
+++ b/clang/test/AST/HLSL/export.hlsl
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -finclude-default-header -x hlsl -ast-dump -o - %s | FileCheck %s
+
+// CHECK:ExportDecl 0x{{[0-9a-f]+}} <{{.*}}> col:1
+// CHECK:FunctionDecl 0x{{[0-9a-f]+}} <{{.*}}> col:13 used f1 'void ()'
+// CHECK:CompoundStmt 0x{{[0-9a-f]+}} <{{.*}}>
+export void f1() {}
+
+// CHECK:NamespaceDecl 0x{{[0-9a-f]+}} <{{.*}}>
+// CHECK:ExportDecl 0x{{[0-9a-f]+}} <{{.*}}> col:3
+// CHECK:FunctionDecl 0x{{[0-9a-f]+}} <{{.*}}> col:15 used f2 'void ()'
+// CHECK:CompoundStmt 0x{{[0-9a-f]+}} <{{.*}}>
+namespace MyNamespace {
+  export void f2() {}
+}
+
+// CHECK:ExportDecl 0x{{[0-9a-f]+}} <{{.*}}>
+// CHECK:FunctionDecl 0x{{[0-9a-f]+}} <{{.*}}> col:10 used f3 'void ()'
+// CHECK:FunctionDecl 0x{{[0-9a-f]+}} <{{.*}}> col:10 used f4 'void ()'
+// CHECK:CompoundStmt 0x{{[0-9a-f]+}} <{{.*}}>
+export {
+    void f3() {}
+    void f4() {}
+}
diff --git a/clang/test/CodeGenHLSL/export.hlsl b/clang/test/CodeGenHLSL/export.hlsl
new file mode 100644
index 0000000000000..cea8875684b9e
--- /dev/null
+++ b/clang/test/CodeGenHLSL/export.hlsl
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
+// RUN:   dxil-pc-shadermodel6.3-library %s \
+// RUN:   -emit-llvm -disable-llvm-passes -o - | FileCheck %s
+
+// CHECK: define void @"?f1@@YAXXZ"()
+export void f1() {
+}
+
+// CHECK: define void @"?f2 at MyNamespace@@YAXXZ"()
+namespace MyNamespace {
+  export void f2() {
+  }
+}
+
+export {
+// CHECK: define void @"?f3@@YAXXZ"()
+// CHECK: define void @"?f4@@YAXXZ"()    
+    void f3() {}
+    void f4() {}
+}
\ No newline at end of file
diff --git a/clang/test/SemaHLSL/export.hlsl b/clang/test/SemaHLSL/export.hlsl
new file mode 100644
index 0000000000000..0cb9248f3f589
--- /dev/null
+++ b/clang/test/SemaHLSL/export.hlsl
@@ -0,0 +1,50 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -o - %s -verify
+
+export void f1();
+
+export void f1() {}
+
+namespace { // expected-note {{anonymous namespace begins here}}
+    export void f2(); // expected-error {{export declaration appears within anonymous namespace}}
+}
+
+export void f3();
+
+export { // expected-note {{export block begins here}}
+    void f4() {}
+    export void f5() {} // expected-error {{export declaration appears within another export declaration}}
+    int A; // expected-error {{export declaration can only be used on functions}}
+    namespace ns { // expected-error {{export declaration can only be used on functions}}
+        void f6();
+    }
+}
+
+void export f7() {} // expected-error {{expected unqualified-id}}
+
+export static void f8() {} // expected-error {{declaration of 'f8' with internal linkage cannot be exported}}
+
+export void f9(); // expected-note {{previous declaration is here}}
+static void f9(); // expected-error {{static declaration of 'f9' follows non-static declaration}}
+
+static void f10(); // expected-note {{previous declaration is here}}
+export void f10(); // expected-error {{cannot export redeclaration 'f10' here since the previous declaration has internal linkage}}
+
+export float V1; // expected-error {{export declaration can only be used on functions}}
+
+static export float V2; // expected-error{{expected unqualified-id}}
+
+export static float V3 = 0; // expected-error {{export declaration can only be used on functions}}
+
+export groupshared float V4; // expected-error {{export declaration can only be used on functions}}
+
+void f6() {
+  export int i;  // expected-error {{expected expression}}
+}
+
+export cbuffer CB { // expected-error {{export declaration can only be used on functions}}
+    int a;
+}
+
+export template<typename T> void tf1(T t) {} // expected-error {{export declaration can only be used on functions}}
+
+void f5() export {} // expected-error {{expected function body after function declarator}}
\ No newline at end of file

>From 327f96bfe417559ec9836279c00af42230075740 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Wed, 26 Jun 2024 15:29:12 -0700
Subject: [PATCH 2/3] Add new lines at the end of test files

---
 clang/test/CodeGenHLSL/export.hlsl | 2 +-
 clang/test/SemaHLSL/export.hlsl    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/test/CodeGenHLSL/export.hlsl b/clang/test/CodeGenHLSL/export.hlsl
index cea8875684b9e..53f603739e329 100644
--- a/clang/test/CodeGenHLSL/export.hlsl
+++ b/clang/test/CodeGenHLSL/export.hlsl
@@ -17,4 +17,4 @@ export {
 // CHECK: define void @"?f4@@YAXXZ"()    
     void f3() {}
     void f4() {}
-}
\ No newline at end of file
+}
diff --git a/clang/test/SemaHLSL/export.hlsl b/clang/test/SemaHLSL/export.hlsl
index 0cb9248f3f589..6e53344dbee08 100644
--- a/clang/test/SemaHLSL/export.hlsl
+++ b/clang/test/SemaHLSL/export.hlsl
@@ -47,4 +47,4 @@ export cbuffer CB { // expected-error {{export declaration can only be used on f
 
 export template<typename T> void tf1(T t) {} // expected-error {{export declaration can only be used on functions}}
 
-void f5() export {} // expected-error {{expected function body after function declarator}}
\ No newline at end of file
+void f5() export {} // expected-error {{expected function body after function declarator}}

>From 191d78a368d71c8d05688b82456bda4f272e8f41 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Thu, 27 Jun 2024 18:25:37 -0700
Subject: [PATCH 3/3] Code review feedback

---
 clang/lib/Sema/SemaModule.cpp   | 70 ++++++++++++---------------------
 clang/test/SemaHLSL/export.hlsl |  6 +++
 2 files changed, 32 insertions(+), 44 deletions(-)

diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index e920b880ecb4d..0e8346d45939c 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -851,42 +851,29 @@ Decl *Sema::ActOnStartExportDecl(Scope *S, SourceLocation ExportLoc,
   CurContext->addDecl(D);
   PushDeclContext(S, D);
 
-  if (getLangOpts().HLSL) {
-    // exported functions cannot be in an unnamed namespace
-    for (const DeclContext *DC = CurContext; DC; DC = DC->getLexicalParent()) {
-      if (const auto *ND = dyn_cast<NamespaceDecl>(DC)) {
-        if (ND->isAnonymousNamespace()) {
-          Diag(ExportLoc, diag::err_export_within_anonymous_namespace);
-          Diag(ND->getLocation(), diag::note_anonymous_namespace);
-          D->setInvalidDecl();
-          return D;
-        }
-      }
-    }
-    return D;
-  }
-
   // C++2a [module.interface]p1:
   //   An export-declaration shall appear only [...] in the purview of a module
   //   interface unit. An export-declaration shall not appear directly or
   //   indirectly within [...] a private-module-fragment.
-  if (!isCurrentModulePurview()) {
-    Diag(ExportLoc, diag::err_export_not_in_module_interface) << 0;
-    D->setInvalidDecl();
-    return D;
-  } else if (currentModuleIsImplementation()) {
-    Diag(ExportLoc, diag::err_export_not_in_module_interface) << 1;
-    Diag(ModuleScopes.back().BeginLoc,
-         diag::note_not_module_interface_add_export)
-        << FixItHint::CreateInsertion(ModuleScopes.back().BeginLoc, "export ");
-    D->setInvalidDecl();
-    return D;
-  } else if (ModuleScopes.back().Module->Kind ==
-             Module::PrivateModuleFragment) {
-    Diag(ExportLoc, diag::err_export_in_private_module_fragment);
-    Diag(ModuleScopes.back().BeginLoc, diag::note_private_module_fragment);
-    D->setInvalidDecl();
-    return D;
+  if (!getLangOpts().HLSL) {
+    if (!isCurrentModulePurview()) {
+      Diag(ExportLoc, diag::err_export_not_in_module_interface) << 0;
+      D->setInvalidDecl();
+      return D;
+    } else if (currentModuleIsImplementation()) {
+      Diag(ExportLoc, diag::err_export_not_in_module_interface) << 1;
+      Diag(ModuleScopes.back().BeginLoc,
+          diag::note_not_module_interface_add_export)
+          << FixItHint::CreateInsertion(ModuleScopes.back().BeginLoc, "export ");
+      D->setInvalidDecl();
+      return D;
+    } else if (ModuleScopes.back().Module->Kind ==
+              Module::PrivateModuleFragment) {
+      Diag(ExportLoc, diag::err_export_in_private_module_fragment);
+      Diag(ModuleScopes.back().BeginLoc, diag::note_private_module_fragment);
+      D->setInvalidDecl();
+      return D;
+    }
   }
 
   for (const DeclContext *DC = CurContext; DC; DC = DC->getLexicalParent()) {
@@ -906,7 +893,7 @@ Decl *Sema::ActOnStartExportDecl(Scope *S, SourceLocation ExportLoc,
       //
       // Defer exporting the namespace until after we leave it, in order to
       // avoid marking all subsequent declarations in the namespace as exported.
-      if (!DeferredExportedNamespaces.insert(ND).second)
+      if (!getLangOpts().HLSL && !DeferredExportedNamespaces.insert(ND).second)
         break;
     }
   }
@@ -921,7 +908,9 @@ Decl *Sema::ActOnStartExportDecl(Scope *S, SourceLocation ExportLoc,
     return D;
   }
 
-  D->setModuleOwnershipKind(Decl::ModuleOwnershipKind::VisibleWhenImported);
+  if (!getLangOpts().HLSL)
+    D->setModuleOwnershipKind(Decl::ModuleOwnershipKind::VisibleWhenImported);
+
   return D;
 }
 
@@ -941,16 +930,9 @@ static bool checkExportedDecl(Sema &S, Decl *D, SourceLocation BlockStart) {
 
   // HLSL: export declaration is valid only on functions
   if (S.getLangOpts().HLSL) {
-    auto *FD = dyn_cast<FunctionDecl>(D);
-    if (!FD) {
-      if (auto *ED2 = dyn_cast<ExportDecl>(D)) {
-        S.Diag(ED2->getBeginLoc(), diag::err_export_within_export);
-        if (auto *ED1 = dyn_cast<ExportDecl>(D->getDeclContext()))
-          S.Diag(ED1->getBeginLoc(), diag::note_export);
-      }
-      else {
-        S.Diag(D->getBeginLoc(), diag::err_hlsl_export_not_on_function);
-      }
+    // Export-within-export was already diagnosed in ActOnStartExportDecl
+    if (!dyn_cast<FunctionDecl>(D) && !dyn_cast<ExportDecl>(D)) {
+      S.Diag(D->getBeginLoc(), diag::err_hlsl_export_not_on_function);
       D->setInvalidDecl();
       return false;
     }
diff --git a/clang/test/SemaHLSL/export.hlsl b/clang/test/SemaHLSL/export.hlsl
index 6e53344dbee08..9c624d6142232 100644
--- a/clang/test/SemaHLSL/export.hlsl
+++ b/clang/test/SemaHLSL/export.hlsl
@@ -19,6 +19,12 @@ export { // expected-note {{export block begins here}}
     }
 }
 
+export { // expected-note {{export block begins here}}
+    export { // expected-error {{export declaration appears within another export declaration}}
+        void f();
+    }
+}
+
 void export f7() {} // expected-error {{expected unqualified-id}}
 
 export static void f8() {} // expected-error {{declaration of 'f8' with internal linkage cannot be exported}}



More information about the cfe-commits mailing list