Skip to content

Conversation

@Keenuts
Copy link
Contributor

@Keenuts Keenuts commented Nov 14, 2025

This commits adds the first part of the output semantics. It only considers return values (and sret), but does not handle inout or out parameters yet.
Those missing bits will reuse the same code, but will require additional testing & some fixups, so planning on adding them separately.

@Keenuts Keenuts requested a review from s-perron November 14, 2025 18:10
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. backend:DirectX HLSL HLSL Language Support llvm:ir labels Nov 14, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 14, 2025

@llvm/pr-subscribers-hlsl
@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-backend-directx

Author: Nathan Gauër (Keenuts)

Changes

This commits adds the first part of the output semantics. It only considers return values (and sret), but does not handle inout or out parameters yet.
Those missing bits will reuse the same code, but will require additional testing & some fixups, so planning on adding them separately.


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

26 Files Affected:

  • (modified) clang/lib/CodeGen/CGHLSLRuntime.cpp (+164-7)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+34)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+12-2)
  • (modified) clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl (+2-1)
  • (added) clang/test/CodeGenHLSL/semantics/missing-vs.hlsl (+6)
  • (added) clang/test/CodeGenHLSL/semantics/semantic-struct-2-output.hlsl (+35)
  • (added) clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl (+31)
  • (added) clang/test/CodeGenHLSL/semantics/semantic.struct.output.hlsl (+27)
  • (modified) clang/test/CodeGenHLSL/sret_output.hlsl (+22-10)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-compute.hlsl (+1-3)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-mesh.hlsl (+1-3)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-pixel.hlsl (+1-1)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-default-compute.hlsl (+1-2)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-default-lib.hlsl (+2-3)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-relaxed-compute.hlsl (+1-2)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-relaxed-lib.hlsl (+2-3)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-strict-compute.hlsl (+1-2)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-strict-lib.hlsl (+1-2)
  • (modified) clang/test/SemaHLSL/Semantics/position.ps.hlsl (+7-3)
  • (modified) clang/test/SemaHLSL/Semantics/position.ps.struct.hlsl (+6-3)
  • (modified) clang/test/SemaHLSL/Semantics/position.ps.struct.reuse.hlsl (+7-4)
  • (modified) clang/test/SemaHLSL/Semantics/position.vs.hlsl (+1-1)
  • (modified) clang/test/SemaHLSL/WaveBuiltinAvailability.hlsl (+2-2)
  • (modified) clang/test/SemaHLSL/num_threads.hlsl (+1-1)
  • (modified) clang/test/SemaHLSL/shader_type_attr.hlsl (+9-12)
  • (modified) llvm/include/llvm/IR/IntrinsicsDirectX.td (+6)
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index ec02096787c7a..741e60e9865cd 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -589,6 +589,36 @@ CGHLSLRuntime::emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
                                  VariableName.str());
 }
 
+static void createSPIRVLocationStore(IRBuilder<> &B, llvm::Module &M,
+                                     llvm::Value *Source, unsigned Location,
+                                     StringRef Name) {
+  auto *GV = new llvm::GlobalVariable(
+      M, Source->getType(), /* isConstant= */ false,
+      llvm::GlobalValue::ExternalLinkage,
+      /* Initializer= */ nullptr, /* Name= */ Name, /* insertBefore= */ nullptr,
+      llvm::GlobalVariable::GeneralDynamicTLSModel,
+      /* AddressSpace */ 8, /* isExternallyInitialized= */ false);
+  GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
+  addLocationDecoration(GV, Location);
+  B.CreateStore(Source, GV);
+}
+
+void CGHLSLRuntime::emitSPIRVUserSemanticStore(
+    llvm::IRBuilder<> &B, llvm::Value *Source,
+    HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
+  Twine BaseName = Twine(Semantic->getAttrName()->getName());
+  Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
+  unsigned Location = SPIRVLastAssignedOutputSemanticLocation;
+
+  // DXC completely ignores the semantic/index pair. Location are assigned from
+  // the first semantic to the last.
+  llvm::ArrayType *AT = dyn_cast<llvm::ArrayType>(Source->getType());
+  unsigned ElementCount = AT ? AT->getNumElements() : 1;
+  SPIRVLastAssignedOutputSemanticLocation += ElementCount;
+  createSPIRVLocationStore(B, CGM.getModule(), Source, Location,
+                           VariableName.str());
+}
+
 llvm::Value *
 CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
                                         HLSLAppliedSemanticAttr *Semantic,
@@ -609,6 +639,23 @@ CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
   return Value;
 }
 
+void CGHLSLRuntime::emitDXILUserSemanticStore(llvm::IRBuilder<> &B,
+                                              llvm::Value *Source,
+                                              HLSLAppliedSemanticAttr *Semantic,
+                                              std::optional<unsigned> Index) {
+  // DXIL packing rules etc shall be handled here.
+  // FIXME: generate proper sigpoint, index, col, row values.
+  SmallVector<Value *> Args{B.getInt32(4),
+                            B.getInt32(0),
+                            B.getInt32(0),
+                            B.getInt8(0),
+                            llvm::PoisonValue::get(B.getInt32Ty()),
+                            Source};
+
+  llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_store_output;
+  B.CreateIntrinsic(/*ReturnType=*/CGM.VoidTy, IntrinsicID, Args, nullptr);
+}
+
 llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
     IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
     HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
@@ -621,6 +668,19 @@ llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
   llvm_unreachable("Unsupported target for user-semantic load.");
 }
 
+void CGHLSLRuntime::emitUserSemanticStore(IRBuilder<> &B, llvm::Value *Source,
+                                          const clang::DeclaratorDecl *Decl,
+                                          HLSLAppliedSemanticAttr *Semantic,
+                                          std::optional<unsigned> Index) {
+  if (CGM.getTarget().getTriple().isSPIRV())
+    return emitSPIRVUserSemanticStore(B, Source, Semantic, Index);
+
+  if (CGM.getTarget().getTriple().isDXIL())
+    return emitDXILUserSemanticStore(B, Source, Semantic, Index);
+
+  llvm_unreachable("Unsupported target for user-semantic load.");
+}
+
 llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
     IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
     HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
@@ -669,6 +729,34 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
   llvm_unreachable("non-handled system semantic. FIXME.");
 }
 
+static void createSPIRVBuiltinStore(IRBuilder<> &B, llvm::Module &M,
+                                    llvm::Value *Source, const Twine &Name,
+                                    unsigned BuiltInID) {
+  auto *GV = new llvm::GlobalVariable(
+      M, Source->getType(), /* isConstant= */ false,
+      llvm::GlobalValue::ExternalLinkage,
+      /* Initializer= */ nullptr, Name, /* insertBefore= */ nullptr,
+      llvm::GlobalVariable::GeneralDynamicTLSModel,
+      /* AddressSpace */ 8, /* isExternallyInitialized= */ false);
+  addSPIRVBuiltinDecoration(GV, BuiltInID);
+  GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
+  B.CreateStore(Source, GV);
+}
+
+void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> &B, llvm::Value *Source,
+                                            const clang::DeclaratorDecl *Decl,
+                                            HLSLAppliedSemanticAttr *Semantic,
+                                            std::optional<unsigned> Index) {
+
+  std::string SemanticName = Semantic->getAttrName()->getName().upper();
+  if (SemanticName == "SV_POSITION")
+    createSPIRVBuiltinStore(B, CGM.getModule(), Source,
+                            Semantic->getAttrName()->getName(),
+                            /* BuiltIn::Position */ 0);
+  else
+    llvm_unreachable("non-handled system semantic. FIXME.");
+}
+
 llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
     IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
     const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic) {
@@ -679,6 +767,16 @@ llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
   return emitUserSemanticLoad(B, Type, Decl, Semantic, Index);
 }
 
+void CGHLSLRuntime::handleScalarSemanticStore(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
+    const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic) {
+  std::optional<unsigned> Index = Semantic->getSemanticIndex();
+  if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_"))
+    emitSystemSemanticStore(B, Source, Decl, Semantic, Index);
+  else
+    emitUserSemanticStore(B, Source, Decl, Semantic, Index);
+}
+
 std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
 CGHLSLRuntime::handleStructSemanticLoad(
     IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
@@ -705,6 +803,35 @@ CGHLSLRuntime::handleStructSemanticLoad(
   return std::make_pair(Aggregate, AttrBegin);
 }
 
+specific_attr_iterator<HLSLAppliedSemanticAttr>
+CGHLSLRuntime::handleStructSemanticStore(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
+    const clang::DeclaratorDecl *Decl,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd) {
+
+  const llvm::StructType *ST = cast<StructType>(Source->getType());
+
+  const clang::RecordDecl *RD = nullptr;
+  if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(Decl))
+    RD = FD->getDeclaredReturnType()->getAsRecordDecl();
+  else
+    RD = Decl->getType()->getAsRecordDecl();
+  assert(RD);
+
+  assert(std::distance(RD->field_begin(), RD->field_end()) ==
+         ST->getNumElements());
+
+  auto FieldDecl = RD->field_begin();
+  for (unsigned I = 0; I < ST->getNumElements(); ++I) {
+    llvm::Value *Extract = B.CreateExtractValue(Source, I);
+    AttrBegin =
+        handleSemanticStore(B, FD, Extract, *FieldDecl, AttrBegin, AttrEnd);
+  }
+
+  return AttrBegin;
+}
+
 std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
 CGHLSLRuntime::handleSemanticLoad(
     IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
@@ -721,6 +848,22 @@ CGHLSLRuntime::handleSemanticLoad(
                         AttrBegin);
 }
 
+specific_attr_iterator<HLSLAppliedSemanticAttr>
+CGHLSLRuntime::handleSemanticStore(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
+    const clang::DeclaratorDecl *Decl,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd) {
+  assert(AttrBegin != AttrEnd);
+  if (Source->getType()->isStructTy())
+    return handleStructSemanticStore(B, FD, Source, Decl, AttrBegin, AttrEnd);
+
+  HLSLAppliedSemanticAttr *Attr = *AttrBegin;
+  ++AttrBegin;
+  handleScalarSemanticStore(B, FD, Source, Decl, Attr);
+  return AttrBegin;
+}
+
 void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
                                       llvm::Function *Fn) {
   llvm::Module &M = CGM.getModule();
@@ -752,20 +895,22 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
     OB.emplace_back("convergencectrl", bundleArgs);
   }
 
-  // FIXME: support struct parameters where semantics are on members.
-  // See: https://github.com/llvm/llvm-project/issues/57874
+  std::unordered_map<const DeclaratorDecl *, llvm::Value *> OutputSemantic;
+
   unsigned SRetOffset = 0;
   for (const auto &Param : Fn->args()) {
     if (Param.hasStructRetAttr()) {
-      // FIXME: support output.
-      // See: https://github.com/llvm/llvm-project/issues/57874
       SRetOffset = 1;
-      Args.emplace_back(PoisonValue::get(Param.getType()));
+      llvm::Type *VarType = Param.getParamStructRetType();
+      llvm::Value *Var = B.CreateAlloca(VarType);
+      OutputSemantic.emplace(FD, Var);
+      Args.push_back(Var);
       continue;
     }
 
     const ParmVarDecl *PD = FD->getParamDecl(Param.getArgNo() - SRetOffset);
     llvm::Value *SemanticValue = nullptr;
+    // FIXME: support inout/out parameters for semantics.
     if ([[maybe_unused]] HLSLParamModifierAttr *MA =
             PD->getAttr<HLSLParamModifierAttr>()) {
       llvm_unreachable("Not handled yet");
@@ -792,8 +937,20 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
 
   CallInst *CI = B.CreateCall(FunctionCallee(Fn), Args, OB);
   CI->setCallingConv(Fn->getCallingConv());
-  // FIXME: Handle codegen for return type semantics.
-  // See: https://github.com/llvm/llvm-project/issues/57875
+
+  if (Fn->getReturnType() != CGM.VoidTy)
+    OutputSemantic.emplace(FD, CI);
+
+  for (auto &[Decl, Source] : OutputSemantic) {
+    AllocaInst *AI = dyn_cast<AllocaInst>(Source);
+    llvm::Value *SourceValue =
+        AI ? B.CreateLoad(AI->getAllocatedType(), Source) : Source;
+
+    auto AttrBegin = Decl->specific_attr_begin<HLSLAppliedSemanticAttr>();
+    auto AttrEnd = Decl->specific_attr_end<HLSLAppliedSemanticAttr>();
+    handleSemanticStore(B, FD, SourceValue, Decl, AttrBegin, AttrEnd);
+  }
+
   B.CreateRetVoid();
 
   // Add and identify root signature to function, if applicable
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 48935584f28a2..d9f7b3db1bb79 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -176,12 +176,22 @@ class CGHLSLRuntime {
                                       HLSLAppliedSemanticAttr *Semantic,
                                       std::optional<unsigned> Index);
 
+  void emitSystemSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                               const clang::DeclaratorDecl *Decl,
+                               HLSLAppliedSemanticAttr *Semantic,
+                               std::optional<unsigned> Index);
+
   llvm::Value *handleScalarSemanticLoad(llvm::IRBuilder<> &B,
                                         const FunctionDecl *FD,
                                         llvm::Type *Type,
                                         const clang::DeclaratorDecl *Decl,
                                         HLSLAppliedSemanticAttr *Semantic);
 
+  void handleScalarSemanticStore(llvm::IRBuilder<> &B, const FunctionDecl *FD,
+                                 llvm::Value *Source,
+                                 const clang::DeclaratorDecl *Decl,
+                                 HLSLAppliedSemanticAttr *Semantic);
+
   std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
   handleStructSemanticLoad(
       llvm::IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
@@ -189,12 +199,24 @@ class CGHLSLRuntime {
       specific_attr_iterator<HLSLAppliedSemanticAttr> begin,
       specific_attr_iterator<HLSLAppliedSemanticAttr> end);
 
+  specific_attr_iterator<HLSLAppliedSemanticAttr> handleStructSemanticStore(
+      llvm::IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
+      const clang::DeclaratorDecl *Decl,
+      specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
+      specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd);
+
   std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
   handleSemanticLoad(llvm::IRBuilder<> &B, const FunctionDecl *FD,
                      llvm::Type *Type, const clang::DeclaratorDecl *Decl,
                      specific_attr_iterator<HLSLAppliedSemanticAttr> begin,
                      specific_attr_iterator<HLSLAppliedSemanticAttr> end);
 
+  specific_attr_iterator<HLSLAppliedSemanticAttr>
+  handleSemanticStore(llvm::IRBuilder<> &B, const FunctionDecl *FD,
+                      llvm::Value *Source, const clang::DeclaratorDecl *Decl,
+                      specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
+                      specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd);
+
 public:
   CGHLSLRuntime(CodeGenModule &CGM) : CGM(CGM) {}
   virtual ~CGHLSLRuntime() {}
@@ -249,10 +271,22 @@ class CGHLSLRuntime {
                                     HLSLAppliedSemanticAttr *Semantic,
                                     std::optional<unsigned> Index);
 
+  void emitSPIRVUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                                  HLSLAppliedSemanticAttr *Semantic,
+                                  std::optional<unsigned> Index);
+  void emitDXILUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                                 HLSLAppliedSemanticAttr *Semantic,
+                                 std::optional<unsigned> Index);
+  void emitUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                             const clang::DeclaratorDecl *Decl,
+                             HLSLAppliedSemanticAttr *Semantic,
+                             std::optional<unsigned> Index);
+
   llvm::Triple::ArchType getArch();
 
   llvm::DenseMap<const clang::RecordType *, llvm::TargetExtType *> LayoutTypes;
   unsigned SPIRVLastAssignedInputSemanticLocation = 0;
+  unsigned SPIRVLastAssignedOutputSemanticLocation = 0;
 };
 
 } // namespace CodeGen
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 2b9b3abbd5360..dd8122c11e696 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -825,7 +825,9 @@ bool SemaHLSL::determineActiveSemantic(
       ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
   }
 
-  const Type *T = D->getType()->getUnqualifiedDesugaredType();
+  const Type *T = D == FD ? &*FD->getReturnType() : &*D->getType();
+  T = T->getUnqualifiedDesugaredType();
+
   const RecordType *RT = dyn_cast<RecordType>(T);
   if (!RT)
     return determineActiveSemanticOnScalar(FD, OutputDecl, D, ActiveSemantic,
@@ -915,13 +917,21 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
     if (ActiveSemantic.Semantic)
       ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
 
+    // FIXME: Verify output semantics in parameters.
     if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic,
                                  ActiveInputSemantics)) {
       Diag(Param->getLocation(), diag::note_previous_decl) << Param;
       FD->setInvalidDecl();
     }
   }
-  // FIXME: Verify return type semantic annotation.
+
+  SemanticInfo ActiveSemantic;
+  llvm::StringSet<> ActiveOutputSemantics;
+  ActiveSemantic.Semantic = FD->getAttr<HLSLParsedSemanticAttr>();
+  if (ActiveSemantic.Semantic)
+    ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
+  if (!FD->getReturnType()->isVoidType())
+    determineActiveSemantic(FD, FD, FD, ActiveSemantic, ActiveOutputSemantics);
 }
 
 void SemaHLSL::checkSemanticAnnotation(
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
index 1bba87ea07141..be30e79438831 100644
--- a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
@@ -3,8 +3,9 @@
 // CHECK: @SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations !0
 
 // CHECK: define void @main() {{.*}} {
-float4 main(float4 p : SV_Position) {
+float4 main(float4 p : SV_Position) : A {
   // CHECK: %[[#P:]] = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
   // CHECK: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[#P]])
+  // CHECK:            store <4 x float> %[[#R]], ptr addrspace(8) @A0, align 16
   return p;
 }
diff --git a/clang/test/CodeGenHLSL/semantics/missing-vs.hlsl b/clang/test/CodeGenHLSL/semantics/missing-vs.hlsl
new file mode 100644
index 0000000000000..41be9c0ab730c
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/missing-vs.hlsl
@@ -0,0 +1,6 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-vertex -x hlsl -emit-llvm -disable-llvm-passes -o - -hlsl-entry main %s -verify -verify-ignore-unexpected=note
+// RUN: %clang_cc1 -triple spirv-unknown-vulkan-vertex -x hlsl -emit-llvm -disable-llvm-passes -o - -hlsl-entry main %s -verify -verify-ignore-unexpected=note
+
+float main(unsigned GI : A) {
+  // expected-error@-1 {{semantic annotations must be present for all parameters of an entry function or patch constant function}}
+}
diff --git a/clang/test/CodeGenHLSL/semantics/semantic-struct-2-output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic-struct-2-output.hlsl
new file mode 100644
index 0000000000000..2f8dc97ef762e
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic-struct-2-output.hlsl
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK-DX,CHECK
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK-VK,CHECK
+
+
+struct Input {
+  float Idx : SV_Position0;
+  float Gid : SV_Position1;
+};
+
+struct Output {
+  float a : A;
+  float b : B;
+};
+
+// Make sure SV_DispatchThreadID translated into dx.thread.id.
+
+// CHECK-DX: define hidden void @_Z3foo5Input(ptr dead_on_unwind noalias writable sret(%struct.Output) align 1 %agg.result, ptr noundef byval(%struct.Input) align 1 %input)
+// CHECK-VK: define hidden spir_func void @_Z3foo5Input(ptr dead_on_unwind noalias writable sret(%struct.Output) align 1 %agg.result, ptr noundef byval(%struct.Input) align 1 %input)
+
+// CHECK: %Idx = getelementptr inbounds nuw %struct.Input, ptr %input, i32 0, i32 0
+// CHECK: %[[#tmp:]] = load float, ptr %Idx, align 1
+// CHECK: %a = getelementptr inbounds nuw %struct.Output, ptr %agg.result, i32 0, i32 0
+// CHECK: store float %[[#tmp]], ptr %a, align 1
+// CHECK: %Gid = getelementptr inbounds nuw %struct.Input, ptr %input, i32 0, i32 1
+// CHECK: %[[#tmp:]] = load float, ptr %Gid, align 1
+// CHECK: %b = getelementptr inbounds nuw %struct.Output, ptr %agg.result, i32 0, i32 1
+// CHECK: store float %[[#tmp]], ptr %b, align 1
+
+Output foo(Input input) {
+  Output o;
+  o.a = input.Idx;
+  o.b = input.Gid;
+  return o;
+}
+
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
new file mode 100644
index 0000000000000..2ff0e3835672c
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SPIRV -DTARGET=spv
+// RUN: %clang_cc1 -triple dxil-px-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes ...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Nov 14, 2025

@llvm/pr-subscribers-clang

Author: Nathan Gauër (Keenuts)

Changes

This commits adds the first part of the output semantics. It only considers return values (and sret), but does not handle inout or out parameters yet.
Those missing bits will reuse the same code, but will require additional testing & some fixups, so planning on adding them separately.


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

26 Files Affected:

  • (modified) clang/lib/CodeGen/CGHLSLRuntime.cpp (+164-7)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+34)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+12-2)
  • (modified) clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl (+2-1)
  • (added) clang/test/CodeGenHLSL/semantics/missing-vs.hlsl (+6)
  • (added) clang/test/CodeGenHLSL/semantics/semantic-struct-2-output.hlsl (+35)
  • (added) clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl (+31)
  • (added) clang/test/CodeGenHLSL/semantics/semantic.struct.output.hlsl (+27)
  • (modified) clang/test/CodeGenHLSL/sret_output.hlsl (+22-10)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-compute.hlsl (+1-3)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-mesh.hlsl (+1-3)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-pixel.hlsl (+1-1)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-default-compute.hlsl (+1-2)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-default-lib.hlsl (+2-3)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-relaxed-compute.hlsl (+1-2)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-relaxed-lib.hlsl (+2-3)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-strict-compute.hlsl (+1-2)
  • (modified) clang/test/SemaHLSL/Availability/avail-diag-strict-lib.hlsl (+1-2)
  • (modified) clang/test/SemaHLSL/Semantics/position.ps.hlsl (+7-3)
  • (modified) clang/test/SemaHLSL/Semantics/position.ps.struct.hlsl (+6-3)
  • (modified) clang/test/SemaHLSL/Semantics/position.ps.struct.reuse.hlsl (+7-4)
  • (modified) clang/test/SemaHLSL/Semantics/position.vs.hlsl (+1-1)
  • (modified) clang/test/SemaHLSL/WaveBuiltinAvailability.hlsl (+2-2)
  • (modified) clang/test/SemaHLSL/num_threads.hlsl (+1-1)
  • (modified) clang/test/SemaHLSL/shader_type_attr.hlsl (+9-12)
  • (modified) llvm/include/llvm/IR/IntrinsicsDirectX.td (+6)
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index ec02096787c7a..741e60e9865cd 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -589,6 +589,36 @@ CGHLSLRuntime::emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
                                  VariableName.str());
 }
 
+static void createSPIRVLocationStore(IRBuilder<> &B, llvm::Module &M,
+                                     llvm::Value *Source, unsigned Location,
+                                     StringRef Name) {
+  auto *GV = new llvm::GlobalVariable(
+      M, Source->getType(), /* isConstant= */ false,
+      llvm::GlobalValue::ExternalLinkage,
+      /* Initializer= */ nullptr, /* Name= */ Name, /* insertBefore= */ nullptr,
+      llvm::GlobalVariable::GeneralDynamicTLSModel,
+      /* AddressSpace */ 8, /* isExternallyInitialized= */ false);
+  GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
+  addLocationDecoration(GV, Location);
+  B.CreateStore(Source, GV);
+}
+
+void CGHLSLRuntime::emitSPIRVUserSemanticStore(
+    llvm::IRBuilder<> &B, llvm::Value *Source,
+    HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
+  Twine BaseName = Twine(Semantic->getAttrName()->getName());
+  Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
+  unsigned Location = SPIRVLastAssignedOutputSemanticLocation;
+
+  // DXC completely ignores the semantic/index pair. Location are assigned from
+  // the first semantic to the last.
+  llvm::ArrayType *AT = dyn_cast<llvm::ArrayType>(Source->getType());
+  unsigned ElementCount = AT ? AT->getNumElements() : 1;
+  SPIRVLastAssignedOutputSemanticLocation += ElementCount;
+  createSPIRVLocationStore(B, CGM.getModule(), Source, Location,
+                           VariableName.str());
+}
+
 llvm::Value *
 CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
                                         HLSLAppliedSemanticAttr *Semantic,
@@ -609,6 +639,23 @@ CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
   return Value;
 }
 
+void CGHLSLRuntime::emitDXILUserSemanticStore(llvm::IRBuilder<> &B,
+                                              llvm::Value *Source,
+                                              HLSLAppliedSemanticAttr *Semantic,
+                                              std::optional<unsigned> Index) {
+  // DXIL packing rules etc shall be handled here.
+  // FIXME: generate proper sigpoint, index, col, row values.
+  SmallVector<Value *> Args{B.getInt32(4),
+                            B.getInt32(0),
+                            B.getInt32(0),
+                            B.getInt8(0),
+                            llvm::PoisonValue::get(B.getInt32Ty()),
+                            Source};
+
+  llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_store_output;
+  B.CreateIntrinsic(/*ReturnType=*/CGM.VoidTy, IntrinsicID, Args, nullptr);
+}
+
 llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
     IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
     HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
@@ -621,6 +668,19 @@ llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
   llvm_unreachable("Unsupported target for user-semantic load.");
 }
 
+void CGHLSLRuntime::emitUserSemanticStore(IRBuilder<> &B, llvm::Value *Source,
+                                          const clang::DeclaratorDecl *Decl,
+                                          HLSLAppliedSemanticAttr *Semantic,
+                                          std::optional<unsigned> Index) {
+  if (CGM.getTarget().getTriple().isSPIRV())
+    return emitSPIRVUserSemanticStore(B, Source, Semantic, Index);
+
+  if (CGM.getTarget().getTriple().isDXIL())
+    return emitDXILUserSemanticStore(B, Source, Semantic, Index);
+
+  llvm_unreachable("Unsupported target for user-semantic load.");
+}
+
 llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
     IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
     HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
@@ -669,6 +729,34 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
   llvm_unreachable("non-handled system semantic. FIXME.");
 }
 
+static void createSPIRVBuiltinStore(IRBuilder<> &B, llvm::Module &M,
+                                    llvm::Value *Source, const Twine &Name,
+                                    unsigned BuiltInID) {
+  auto *GV = new llvm::GlobalVariable(
+      M, Source->getType(), /* isConstant= */ false,
+      llvm::GlobalValue::ExternalLinkage,
+      /* Initializer= */ nullptr, Name, /* insertBefore= */ nullptr,
+      llvm::GlobalVariable::GeneralDynamicTLSModel,
+      /* AddressSpace */ 8, /* isExternallyInitialized= */ false);
+  addSPIRVBuiltinDecoration(GV, BuiltInID);
+  GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
+  B.CreateStore(Source, GV);
+}
+
+void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> &B, llvm::Value *Source,
+                                            const clang::DeclaratorDecl *Decl,
+                                            HLSLAppliedSemanticAttr *Semantic,
+                                            std::optional<unsigned> Index) {
+
+  std::string SemanticName = Semantic->getAttrName()->getName().upper();
+  if (SemanticName == "SV_POSITION")
+    createSPIRVBuiltinStore(B, CGM.getModule(), Source,
+                            Semantic->getAttrName()->getName(),
+                            /* BuiltIn::Position */ 0);
+  else
+    llvm_unreachable("non-handled system semantic. FIXME.");
+}
+
 llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
     IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
     const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic) {
@@ -679,6 +767,16 @@ llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
   return emitUserSemanticLoad(B, Type, Decl, Semantic, Index);
 }
 
+void CGHLSLRuntime::handleScalarSemanticStore(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
+    const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic) {
+  std::optional<unsigned> Index = Semantic->getSemanticIndex();
+  if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_"))
+    emitSystemSemanticStore(B, Source, Decl, Semantic, Index);
+  else
+    emitUserSemanticStore(B, Source, Decl, Semantic, Index);
+}
+
 std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
 CGHLSLRuntime::handleStructSemanticLoad(
     IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
@@ -705,6 +803,35 @@ CGHLSLRuntime::handleStructSemanticLoad(
   return std::make_pair(Aggregate, AttrBegin);
 }
 
+specific_attr_iterator<HLSLAppliedSemanticAttr>
+CGHLSLRuntime::handleStructSemanticStore(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
+    const clang::DeclaratorDecl *Decl,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd) {
+
+  const llvm::StructType *ST = cast<StructType>(Source->getType());
+
+  const clang::RecordDecl *RD = nullptr;
+  if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(Decl))
+    RD = FD->getDeclaredReturnType()->getAsRecordDecl();
+  else
+    RD = Decl->getType()->getAsRecordDecl();
+  assert(RD);
+
+  assert(std::distance(RD->field_begin(), RD->field_end()) ==
+         ST->getNumElements());
+
+  auto FieldDecl = RD->field_begin();
+  for (unsigned I = 0; I < ST->getNumElements(); ++I) {
+    llvm::Value *Extract = B.CreateExtractValue(Source, I);
+    AttrBegin =
+        handleSemanticStore(B, FD, Extract, *FieldDecl, AttrBegin, AttrEnd);
+  }
+
+  return AttrBegin;
+}
+
 std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
 CGHLSLRuntime::handleSemanticLoad(
     IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
@@ -721,6 +848,22 @@ CGHLSLRuntime::handleSemanticLoad(
                         AttrBegin);
 }
 
+specific_attr_iterator<HLSLAppliedSemanticAttr>
+CGHLSLRuntime::handleSemanticStore(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
+    const clang::DeclaratorDecl *Decl,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd) {
+  assert(AttrBegin != AttrEnd);
+  if (Source->getType()->isStructTy())
+    return handleStructSemanticStore(B, FD, Source, Decl, AttrBegin, AttrEnd);
+
+  HLSLAppliedSemanticAttr *Attr = *AttrBegin;
+  ++AttrBegin;
+  handleScalarSemanticStore(B, FD, Source, Decl, Attr);
+  return AttrBegin;
+}
+
 void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
                                       llvm::Function *Fn) {
   llvm::Module &M = CGM.getModule();
@@ -752,20 +895,22 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
     OB.emplace_back("convergencectrl", bundleArgs);
   }
 
-  // FIXME: support struct parameters where semantics are on members.
-  // See: https://github.com/llvm/llvm-project/issues/57874
+  std::unordered_map<const DeclaratorDecl *, llvm::Value *> OutputSemantic;
+
   unsigned SRetOffset = 0;
   for (const auto &Param : Fn->args()) {
     if (Param.hasStructRetAttr()) {
-      // FIXME: support output.
-      // See: https://github.com/llvm/llvm-project/issues/57874
       SRetOffset = 1;
-      Args.emplace_back(PoisonValue::get(Param.getType()));
+      llvm::Type *VarType = Param.getParamStructRetType();
+      llvm::Value *Var = B.CreateAlloca(VarType);
+      OutputSemantic.emplace(FD, Var);
+      Args.push_back(Var);
       continue;
     }
 
     const ParmVarDecl *PD = FD->getParamDecl(Param.getArgNo() - SRetOffset);
     llvm::Value *SemanticValue = nullptr;
+    // FIXME: support inout/out parameters for semantics.
     if ([[maybe_unused]] HLSLParamModifierAttr *MA =
             PD->getAttr<HLSLParamModifierAttr>()) {
       llvm_unreachable("Not handled yet");
@@ -792,8 +937,20 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
 
   CallInst *CI = B.CreateCall(FunctionCallee(Fn), Args, OB);
   CI->setCallingConv(Fn->getCallingConv());
-  // FIXME: Handle codegen for return type semantics.
-  // See: https://github.com/llvm/llvm-project/issues/57875
+
+  if (Fn->getReturnType() != CGM.VoidTy)
+    OutputSemantic.emplace(FD, CI);
+
+  for (auto &[Decl, Source] : OutputSemantic) {
+    AllocaInst *AI = dyn_cast<AllocaInst>(Source);
+    llvm::Value *SourceValue =
+        AI ? B.CreateLoad(AI->getAllocatedType(), Source) : Source;
+
+    auto AttrBegin = Decl->specific_attr_begin<HLSLAppliedSemanticAttr>();
+    auto AttrEnd = Decl->specific_attr_end<HLSLAppliedSemanticAttr>();
+    handleSemanticStore(B, FD, SourceValue, Decl, AttrBegin, AttrEnd);
+  }
+
   B.CreateRetVoid();
 
   // Add and identify root signature to function, if applicable
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 48935584f28a2..d9f7b3db1bb79 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -176,12 +176,22 @@ class CGHLSLRuntime {
                                       HLSLAppliedSemanticAttr *Semantic,
                                       std::optional<unsigned> Index);
 
+  void emitSystemSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                               const clang::DeclaratorDecl *Decl,
+                               HLSLAppliedSemanticAttr *Semantic,
+                               std::optional<unsigned> Index);
+
   llvm::Value *handleScalarSemanticLoad(llvm::IRBuilder<> &B,
                                         const FunctionDecl *FD,
                                         llvm::Type *Type,
                                         const clang::DeclaratorDecl *Decl,
                                         HLSLAppliedSemanticAttr *Semantic);
 
+  void handleScalarSemanticStore(llvm::IRBuilder<> &B, const FunctionDecl *FD,
+                                 llvm::Value *Source,
+                                 const clang::DeclaratorDecl *Decl,
+                                 HLSLAppliedSemanticAttr *Semantic);
+
   std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
   handleStructSemanticLoad(
       llvm::IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
@@ -189,12 +199,24 @@ class CGHLSLRuntime {
       specific_attr_iterator<HLSLAppliedSemanticAttr> begin,
       specific_attr_iterator<HLSLAppliedSemanticAttr> end);
 
+  specific_attr_iterator<HLSLAppliedSemanticAttr> handleStructSemanticStore(
+      llvm::IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
+      const clang::DeclaratorDecl *Decl,
+      specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
+      specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd);
+
   std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
   handleSemanticLoad(llvm::IRBuilder<> &B, const FunctionDecl *FD,
                      llvm::Type *Type, const clang::DeclaratorDecl *Decl,
                      specific_attr_iterator<HLSLAppliedSemanticAttr> begin,
                      specific_attr_iterator<HLSLAppliedSemanticAttr> end);
 
+  specific_attr_iterator<HLSLAppliedSemanticAttr>
+  handleSemanticStore(llvm::IRBuilder<> &B, const FunctionDecl *FD,
+                      llvm::Value *Source, const clang::DeclaratorDecl *Decl,
+                      specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
+                      specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd);
+
 public:
   CGHLSLRuntime(CodeGenModule &CGM) : CGM(CGM) {}
   virtual ~CGHLSLRuntime() {}
@@ -249,10 +271,22 @@ class CGHLSLRuntime {
                                     HLSLAppliedSemanticAttr *Semantic,
                                     std::optional<unsigned> Index);
 
+  void emitSPIRVUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                                  HLSLAppliedSemanticAttr *Semantic,
+                                  std::optional<unsigned> Index);
+  void emitDXILUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                                 HLSLAppliedSemanticAttr *Semantic,
+                                 std::optional<unsigned> Index);
+  void emitUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                             const clang::DeclaratorDecl *Decl,
+                             HLSLAppliedSemanticAttr *Semantic,
+                             std::optional<unsigned> Index);
+
   llvm::Triple::ArchType getArch();
 
   llvm::DenseMap<const clang::RecordType *, llvm::TargetExtType *> LayoutTypes;
   unsigned SPIRVLastAssignedInputSemanticLocation = 0;
+  unsigned SPIRVLastAssignedOutputSemanticLocation = 0;
 };
 
 } // namespace CodeGen
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 2b9b3abbd5360..dd8122c11e696 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -825,7 +825,9 @@ bool SemaHLSL::determineActiveSemantic(
       ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
   }
 
-  const Type *T = D->getType()->getUnqualifiedDesugaredType();
+  const Type *T = D == FD ? &*FD->getReturnType() : &*D->getType();
+  T = T->getUnqualifiedDesugaredType();
+
   const RecordType *RT = dyn_cast<RecordType>(T);
   if (!RT)
     return determineActiveSemanticOnScalar(FD, OutputDecl, D, ActiveSemantic,
@@ -915,13 +917,21 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
     if (ActiveSemantic.Semantic)
       ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
 
+    // FIXME: Verify output semantics in parameters.
     if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic,
                                  ActiveInputSemantics)) {
       Diag(Param->getLocation(), diag::note_previous_decl) << Param;
       FD->setInvalidDecl();
     }
   }
-  // FIXME: Verify return type semantic annotation.
+
+  SemanticInfo ActiveSemantic;
+  llvm::StringSet<> ActiveOutputSemantics;
+  ActiveSemantic.Semantic = FD->getAttr<HLSLParsedSemanticAttr>();
+  if (ActiveSemantic.Semantic)
+    ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
+  if (!FD->getReturnType()->isVoidType())
+    determineActiveSemantic(FD, FD, FD, ActiveSemantic, ActiveOutputSemantics);
 }
 
 void SemaHLSL::checkSemanticAnnotation(
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
index 1bba87ea07141..be30e79438831 100644
--- a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
@@ -3,8 +3,9 @@
 // CHECK: @SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations !0
 
 // CHECK: define void @main() {{.*}} {
-float4 main(float4 p : SV_Position) {
+float4 main(float4 p : SV_Position) : A {
   // CHECK: %[[#P:]] = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
   // CHECK: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[#P]])
+  // CHECK:            store <4 x float> %[[#R]], ptr addrspace(8) @A0, align 16
   return p;
 }
diff --git a/clang/test/CodeGenHLSL/semantics/missing-vs.hlsl b/clang/test/CodeGenHLSL/semantics/missing-vs.hlsl
new file mode 100644
index 0000000000000..41be9c0ab730c
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/missing-vs.hlsl
@@ -0,0 +1,6 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-vertex -x hlsl -emit-llvm -disable-llvm-passes -o - -hlsl-entry main %s -verify -verify-ignore-unexpected=note
+// RUN: %clang_cc1 -triple spirv-unknown-vulkan-vertex -x hlsl -emit-llvm -disable-llvm-passes -o - -hlsl-entry main %s -verify -verify-ignore-unexpected=note
+
+float main(unsigned GI : A) {
+  // expected-error@-1 {{semantic annotations must be present for all parameters of an entry function or patch constant function}}
+}
diff --git a/clang/test/CodeGenHLSL/semantics/semantic-struct-2-output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic-struct-2-output.hlsl
new file mode 100644
index 0000000000000..2f8dc97ef762e
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic-struct-2-output.hlsl
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK-DX,CHECK
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK-VK,CHECK
+
+
+struct Input {
+  float Idx : SV_Position0;
+  float Gid : SV_Position1;
+};
+
+struct Output {
+  float a : A;
+  float b : B;
+};
+
+// Make sure SV_DispatchThreadID translated into dx.thread.id.
+
+// CHECK-DX: define hidden void @_Z3foo5Input(ptr dead_on_unwind noalias writable sret(%struct.Output) align 1 %agg.result, ptr noundef byval(%struct.Input) align 1 %input)
+// CHECK-VK: define hidden spir_func void @_Z3foo5Input(ptr dead_on_unwind noalias writable sret(%struct.Output) align 1 %agg.result, ptr noundef byval(%struct.Input) align 1 %input)
+
+// CHECK: %Idx = getelementptr inbounds nuw %struct.Input, ptr %input, i32 0, i32 0
+// CHECK: %[[#tmp:]] = load float, ptr %Idx, align 1
+// CHECK: %a = getelementptr inbounds nuw %struct.Output, ptr %agg.result, i32 0, i32 0
+// CHECK: store float %[[#tmp]], ptr %a, align 1
+// CHECK: %Gid = getelementptr inbounds nuw %struct.Input, ptr %input, i32 0, i32 1
+// CHECK: %[[#tmp:]] = load float, ptr %Gid, align 1
+// CHECK: %b = getelementptr inbounds nuw %struct.Output, ptr %agg.result, i32 0, i32 1
+// CHECK: store float %[[#tmp]], ptr %b, align 1
+
+Output foo(Input input) {
+  Output o;
+  o.a = input.Idx;
+  o.b = input.Gid;
+  return o;
+}
+
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
new file mode 100644
index 0000000000000..2ff0e3835672c
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SPIRV -DTARGET=spv
+// RUN: %clang_cc1 -triple dxil-px-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes ...
[truncated]

Copy link
Contributor

@s-perron s-perron left a comment

Choose a reason for hiding this comment

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

LGTM. Minor issues. The main one is the codegen test could check that the metadata declaring the location decoration is correct.

This commits adds the first part of the output semantics. It only
considers return values (and sret), but does not handle `inout` or `out`
parameters yet.
Those missing bits will reuse the same code, but will require additional
testing & some fixups, so planning on adding them separately.
@Keenuts Keenuts requested a review from tex3d November 18, 2025 15:53
@github-actions
Copy link

🐧 Linux x64 Test Results

  • 192733 tests passed
  • 6174 tests skipped

@Keenuts Keenuts merged commit 2fc42c7 into llvm:main Nov 19, 2025
11 checks passed
@Keenuts Keenuts deleted the hlsl-semantics-4 branch November 19, 2025 13:51
@Keenuts
Copy link
Contributor Author

Keenuts commented Nov 19, 2025

This PR broke some buildbots, just a missing include. Sending #168739 to fix forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:DirectX clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants