Skip to content

[HLSL][RootSignature] Add parsing of floats for StaticSampler #140181

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

Merged
merged 18 commits into from
May 29, 2025

Conversation

inbelic
Copy link
Contributor

@inbelic inbelic commented May 16, 2025

  • defines in-memory representaiton of MipLODBias to allow for testing of a float parameter
  • defines handleInt and handleFloat to handle converting a token's NumSpelling into a valid float
  • plugs this into parseFloatParam to fill in the MipLODBias param

The parsing of floats is required to match the behaviour of DXC. This behaviour is outlined as follows:

  • if the number is an integer then convert it using _atoi64, check for overflow and static_cast this to a float
  • if the number is a float then convert it using strtod, check for float overflow and static_cast this to a float, this will implicitly also check for double over/underflow and if the string is malformed then it will return an error

This pr matches this behaviour by parsing as, uint/int accordingly and then casting, or, by using the correct APFloat semantics/rounding mode with NumericLiteralParser.

  • adds testing of error diagnostics and valid float param values to demonstrate functionality

Part 2 of #126574

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" HLSL HLSL Language Support labels May 16, 2025
@llvmbot
Copy link
Member

llvmbot commented May 16, 2025

@llvm/pr-subscribers-hlsl

Author: Finn Plummer (inbelic)

Changes
  • defines in-memory representaiton of MipLODBias to allow for testing of a float parameter
  • defines handleInt and handleFloat to handle converting a token's NumSpelling into a valid float
  • plugs this into parseFloatParam to fill in the MipLODBias param

The parsing of floats is required to match the behaviour of DXC. This behaviour is outlined as follows:

  • if the number is an integer then convert it using _atoi64, check for overflow and static_cast this to a float
  • if the number is a float then convert it using strtod, check for float overflow and static_cast this to a float, this will implicitly also check for double over/underflow and if the string is malformed then it will return an error

This pr matches this behaviour by parsing as, uint/int accordingly and then casting, or, by using the correct APFloat semantics/rounding mode with NumericLiteralParser.

  • adds testing of error diagnostics and valid float param values to demonstrate functionality

Part 2 of #126574


Full diff: https://github.com/llvm/llvm-project/pull/140181.diff

7 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+5-1)
  • (modified) clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def (+3)
  • (modified) clang/include/clang/Parse/ParseHLSLRootSignature.h (+7)
  • (modified) clang/lib/Parse/ParseHLSLRootSignature.cpp (+143-3)
  • (modified) clang/unittests/Lex/LexHLSLRootSignatureTest.cpp (+2)
  • (modified) clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp (+161-1)
  • (modified) llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h (+1)
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index fd525140d0482..554d70de86902 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1856,7 +1856,11 @@ def err_hlsl_unexpected_end_of_params
     : Error<"expected %0 to denote end of parameters, or, another valid parameter of %1">;
 def err_hlsl_rootsig_repeat_param : Error<"specified the same parameter '%0' multiple times">;
 def err_hlsl_rootsig_missing_param : Error<"did not specify mandatory parameter '%0'">;
-def err_hlsl_number_literal_overflow : Error<"integer literal is too large to be represented as a 32-bit %select{signed |}0 integer type">;
+def err_hlsl_number_literal_overflow : Error<
+  "%select{integer|float}0 literal is too large to be represented as a "
+  "%select{32-bit %select{signed|}1 integer|float}0 type">;
+def err_hlsl_number_literal_underflow : Error<
+  "float literal has a magnitude that is too small to be represented as a float type">;
 def err_hlsl_rootsig_non_zero_flag : Error<"flag value is neither a literal 0 nor a named value">;
 
 } // end of Parser diagnostics
diff --git a/clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def b/clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def
index ddebe82987197..5d16eaa5b72f6 100644
--- a/clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def
+++ b/clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def
@@ -100,6 +100,9 @@ KEYWORD(flags)
 KEYWORD(numDescriptors)
 KEYWORD(offset)
 
+// StaticSampler Keywords:
+KEYWORD(mipLODBias)
+
 // Unbounded Enum:
 UNBOUNDED_ENUM(unbounded, "unbounded")
 
diff --git a/clang/include/clang/Parse/ParseHLSLRootSignature.h b/clang/include/clang/Parse/ParseHLSLRootSignature.h
index 80fedc2f16574..c12b022a030ef 100644
--- a/clang/include/clang/Parse/ParseHLSLRootSignature.h
+++ b/clang/include/clang/Parse/ParseHLSLRootSignature.h
@@ -111,12 +111,14 @@ class RootSignatureParser {
 
   struct ParsedStaticSamplerParams {
     std::optional<llvm::hlsl::rootsig::Register> Reg;
+    std::optional<float> MipLODBias;
   };
   std::optional<ParsedStaticSamplerParams> parseStaticSamplerParams();
 
   // Common parsing methods
   std::optional<uint32_t> parseUIntParam();
   std::optional<llvm::hlsl::rootsig::Register> parseRegister();
+  std::optional<float> parseFloatParam();
 
   /// Parsing methods of various enums
   std::optional<llvm::hlsl::rootsig::ShaderVisibility> parseShaderVisibility();
@@ -128,6 +130,11 @@ class RootSignatureParser {
   /// Use NumericLiteralParser to convert CurToken.NumSpelling into a unsigned
   /// 32-bit integer
   std::optional<uint32_t> handleUIntLiteral();
+  /// Use NumericLiteralParser to convert CurToken.NumSpelling into a signed
+  /// 32-bit integer
+  std::optional<int32_t> handleIntLiteral(bool Negated);
+  /// Use NumericLiteralParser to convert CurToken.NumSpelling into a float
+  std::optional<float> handleFloatLiteral(bool Negated);
 
   /// Flags may specify the value of '0' to denote that there should be no
   /// flags set.
diff --git a/clang/lib/Parse/ParseHLSLRootSignature.cpp b/clang/lib/Parse/ParseHLSLRootSignature.cpp
index 6e4bb4d59e109..c3589fa190432 100644
--- a/clang/lib/Parse/ParseHLSLRootSignature.cpp
+++ b/clang/lib/Parse/ParseHLSLRootSignature.cpp
@@ -6,6 +6,8 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <float.h>
+
 #include "clang/Parse/ParseHLSLRootSignature.h"
 
 #include "clang/Lex/LiteralSupport.h"
@@ -378,6 +380,10 @@ std::optional<StaticSampler> RootSignatureParser::parseStaticSampler() {
 
   Sampler.Reg = Params->Reg.value();
 
+  // Fill in optional values
+  if (Params->MipLODBias.has_value())
+    Sampler.MipLODBias = Params->MipLODBias.value();
+
   if (consumeExpectedToken(TokenKind::pu_r_paren,
                            diag::err_hlsl_unexpected_end_of_params,
                            /*param of=*/TokenKind::kw_StaticSampler))
@@ -663,6 +669,23 @@ RootSignatureParser::parseStaticSamplerParams() {
         return std::nullopt;
       Params.Reg = Reg;
     }
+
+    // `mipLODBias` `=` NUMBER
+    if (tryConsumeExpectedToken(TokenKind::kw_mipLODBias)) {
+      if (Params.MipLODBias.has_value()) {
+        getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param)
+            << CurToken.TokKind;
+        return std::nullopt;
+      }
+
+      if (consumeExpectedToken(TokenKind::pu_equal))
+        return std::nullopt;
+
+      auto MipLODBias = parseFloatParam();
+      if (!MipLODBias.has_value())
+        return std::nullopt;
+      Params.MipLODBias = (float)*MipLODBias;
+    }
   } while (tryConsumeExpectedToken(TokenKind::pu_comma));
 
   return Params;
@@ -711,6 +734,35 @@ std::optional<Register> RootSignatureParser::parseRegister() {
   return Reg;
 }
 
+std::optional<float> RootSignatureParser::parseFloatParam() {
+  assert(CurToken.TokKind == TokenKind::pu_equal &&
+         "Expects to only be invoked starting at given keyword");
+  // Consume sign modifier
+  bool Signed =
+      tryConsumeExpectedToken({TokenKind::pu_plus, TokenKind::pu_minus});
+  bool Negated = Signed && CurToken.TokKind == TokenKind::pu_minus;
+
+  // Handle an uint and interpret it as a float
+  if (!Signed && tryConsumeExpectedToken(TokenKind::int_literal)) {
+    auto UInt = handleUIntLiteral();
+    if (!UInt.has_value())
+      return std::nullopt;
+    return (float)UInt.value();
+  } else if (tryConsumeExpectedToken(TokenKind::int_literal)) {
+    auto Int = handleIntLiteral(Negated);
+    if (!Int.has_value())
+      return std::nullopt;
+    return (float)Int.value();
+  } else if (tryConsumeExpectedToken(TokenKind::float_literal)) {
+    auto Float = handleFloatLiteral(Negated);
+    if (!Float.has_value())
+      return std::nullopt;
+    return Float.value();
+  }
+
+  return std::nullopt;
+}
+
 std::optional<llvm::hlsl::rootsig::ShaderVisibility>
 RootSignatureParser::parseShaderVisibility() {
   assert(CurToken.TokKind == TokenKind::pu_equal &&
@@ -821,22 +873,110 @@ std::optional<uint32_t> RootSignatureParser::handleUIntLiteral() {
                                       PP.getSourceManager(), PP.getLangOpts(),
                                       PP.getTargetInfo(), PP.getDiagnostics());
   if (Literal.hadError)
-    return true; // Error has already been reported so just return
+    return std::nullopt; // Error has already been reported so just return
 
-  assert(Literal.isIntegerLiteral() && "IsNumberChar will only support digits");
+  assert(Literal.isIntegerLiteral() &&
+         "NumSpelling can only consist of digits");
 
   llvm::APSInt Val = llvm::APSInt(32, false);
   if (Literal.GetIntegerValue(Val)) {
     // Report that the value has overflowed
     PP.getDiagnostics().Report(CurToken.TokLoc,
                                diag::err_hlsl_number_literal_overflow)
-        << 0 << CurToken.NumSpelling;
+        << /*integer type*/ 0 << /*is signed*/ 0;
     return std::nullopt;
   }
 
   return Val.getExtValue();
 }
 
+std::optional<int32_t> RootSignatureParser::handleIntLiteral(bool Negated) {
+  // Parse the numeric value and do semantic checks on its specification
+  clang::NumericLiteralParser Literal(CurToken.NumSpelling, CurToken.TokLoc,
+                                      PP.getSourceManager(), PP.getLangOpts(),
+                                      PP.getTargetInfo(), PP.getDiagnostics());
+  if (Literal.hadError)
+    return std::nullopt; // Error has already been reported so just return
+
+  assert(Literal.isIntegerLiteral() &&
+         "NumSpelling can only consist of digits");
+
+  llvm::APSInt Val = llvm::APSInt(32, true);
+  if (Literal.GetIntegerValue(Val) || INT32_MAX < Val.getExtValue()) {
+    // Report that the value has overflowed
+    PP.getDiagnostics().Report(CurToken.TokLoc,
+                               diag::err_hlsl_number_literal_overflow)
+        << /*integer type*/ 0 << /*is signed*/ 1;
+    return std::nullopt;
+  }
+
+  if (Negated)
+    Val = -Val;
+
+  return static_cast<int32_t>(Val.getExtValue());
+}
+
+std::optional<float> RootSignatureParser::handleFloatLiteral(bool Negated) {
+  // Parse the numeric value and do semantic checks on its specification
+  clang::NumericLiteralParser Literal(CurToken.NumSpelling, CurToken.TokLoc,
+                                      PP.getSourceManager(), PP.getLangOpts(),
+                                      PP.getTargetInfo(), PP.getDiagnostics());
+  if (Literal.hadError)
+    return std::nullopt; // Error has already been reported so just return
+
+  assert(Literal.isFloatingLiteral() &&
+         "NumSpelling consists only of [0-9.ef+-]. Any malformed NumSpelling "
+         "will be caught and reported by NumericLiteralParser.");
+
+  // DXC used `strtod` to convert the token string to a float which corresponds
+  // to:
+  auto DXCSemantics = llvm::APFloat::Semantics::S_IEEEdouble;
+  auto DXCRoundingMode = llvm::RoundingMode::NearestTiesToEven;
+
+  llvm::APFloat Val =
+      llvm::APFloat(llvm::APFloat::EnumToSemantics(DXCSemantics));
+  llvm::APFloat::opStatus Status = Literal.GetFloatValue(Val, DXCRoundingMode);
+
+  // Note: we do not error when opStatus::opInexact by itself as this just
+  // denotes that rounding occured but not that it is invalid
+  assert(!(Status & llvm::APFloat::opStatus::opInvalidOp) &&
+         "NumSpelling consists only of [0-9.ef+-]. Any malformed NumSpelling "
+         "will be caught and reported by NumericLiteralParser.");
+
+  assert(!(Status & llvm::APFloat::opStatus::opDivByZero) &&
+         "It is not possible for a division to be performed when "
+         "constructing an APFloat from a string");
+
+  if (Status & llvm::APFloat::opStatus::opUnderflow) {
+    // Report that the value has underflowed
+    PP.getDiagnostics().Report(CurToken.TokLoc,
+                               diag::err_hlsl_number_literal_underflow);
+    return std::nullopt;
+  }
+
+  if (Status & llvm::APFloat::opStatus::opOverflow) {
+    // Report that the value has overflowed
+    PP.getDiagnostics().Report(CurToken.TokLoc,
+                               diag::err_hlsl_number_literal_overflow)
+        << /*float type*/ 1;
+    return std::nullopt;
+  }
+
+  if (Negated)
+    Val = -Val;
+
+  double DoubleVal = Val.convertToDouble();
+  if (FLT_MAX < DoubleVal || DoubleVal < -FLT_MAX) {
+    // Report that the value has overflowed
+    PP.getDiagnostics().Report(CurToken.TokLoc,
+                               diag::err_hlsl_number_literal_overflow)
+        << /*float type*/ 1;
+    return std::nullopt;
+  }
+
+  return static_cast<float>(DoubleVal);
+}
+
 bool RootSignatureParser::verifyZeroFlag() {
   assert(CurToken.TokKind == TokenKind::int_literal);
   auto X = handleUIntLiteral();
diff --git a/clang/unittests/Lex/LexHLSLRootSignatureTest.cpp b/clang/unittests/Lex/LexHLSLRootSignatureTest.cpp
index 3e38c281f4fb1..b610b8f10f8da 100644
--- a/clang/unittests/Lex/LexHLSLRootSignatureTest.cpp
+++ b/clang/unittests/Lex/LexHLSLRootSignatureTest.cpp
@@ -136,6 +136,8 @@ TEST_F(LexHLSLRootSignatureTest, ValidLexAllTokensTest) {
     space visibility flags
     numDescriptors offset
 
+    mipLODBias
+
     unbounded
     DESCRIPTOR_RANGE_OFFSET_APPEND
 
diff --git a/clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp b/clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp
index 14c3101f3eafa..539232e0bf2c2 100644
--- a/clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp
+++ b/clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp
@@ -225,7 +225,7 @@ TEST_F(ParseHLSLRootSignatureTest, ValidParseDTClausesTest) {
 
 TEST_F(ParseHLSLRootSignatureTest, ValidParseStaticSamplerTest) {
   const llvm::StringLiteral Source = R"cc(
-    StaticSampler(s0)
+    StaticSampler(s0, mipLODBias = 0)
   )cc";
 
   TrivialModuleLoader ModLoader;
@@ -247,6 +247,82 @@ TEST_F(ParseHLSLRootSignatureTest, ValidParseStaticSamplerTest) {
   ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
   ASSERT_EQ(std::get<StaticSampler>(Elem).Reg.ViewType, RegisterType::SReg);
   ASSERT_EQ(std::get<StaticSampler>(Elem).Reg.Number, 0u);
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 0.f);
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
+TEST_F(ParseHLSLRootSignatureTest, ValidParseFloatsTest) {
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = 0),
+    StaticSampler(s0, mipLODBias = +1),
+    StaticSampler(s0, mipLODBias = -1),
+    StaticSampler(s0, mipLODBias = 42.),
+    StaticSampler(s0, mipLODBias = +4.2),
+    StaticSampler(s0, mipLODBias = -.42),
+    StaticSampler(s0, mipLODBias = .42e+3),
+    StaticSampler(s0, mipLODBias = 42E-12),
+    StaticSampler(s0, mipLODBias = 42.f),
+    StaticSampler(s0, mipLODBias = 4.2F),
+    StaticSampler(s0, mipLODBias = 42.e+10f),
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test no diagnostics produced
+  Consumer->setNoDiag();
+
+  ASSERT_FALSE(Parser.parse());
+
+  RootElement Elem = Elements[0];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 0.f);
+
+  Elem = Elements[1];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 1.f);
+
+  Elem = Elements[2];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, -1.f);
+
+  Elem = Elements[3];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 42.f);
+
+  Elem = Elements[4];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 4.2f);
+
+  Elem = Elements[5];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, -.42f);
+
+  Elem = Elements[6];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 420.f);
+
+  Elem = Elements[7];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 0.000000000042f);
+
+  Elem = Elements[8];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 42.f);
+
+  Elem = Elements[9];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 4.2f);
+
+  Elem = Elements[10];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 420000000000.f);
 
   ASSERT_TRUE(Consumer->isSatisfied());
 }
@@ -688,6 +764,90 @@ TEST_F(ParseHLSLRootSignatureTest, InvalidLexOverflowedNumberTest) {
   ASSERT_TRUE(Consumer->isSatisfied());
 }
 
+TEST_F(ParseHLSLRootSignatureTest, InvalidLexOverflowedFloatTest) {
+  // This test will check that the lexing fails due to a float overflow
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = 3.402823467e+38F)
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test correct diagnostic produced
+  Consumer->setExpected(diag::err_hlsl_number_literal_overflow);
+  ASSERT_TRUE(Parser.parse());
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
+TEST_F(ParseHLSLRootSignatureTest, InvalidLexNegOverflowedFloatTest) {
+  // This test will check that the lexing fails due to negative float overflow
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = -3.402823467e+38F)
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test correct diagnostic produced
+  Consumer->setExpected(diag::err_hlsl_number_literal_overflow);
+  ASSERT_TRUE(Parser.parse());
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
+TEST_F(ParseHLSLRootSignatureTest, InvalidLexOverflowedDoubleTest) {
+  // This test will check that the lexing fails due to an overflow of double
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = 1.e+500)
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test correct diagnostic produced
+  Consumer->setExpected(diag::err_hlsl_number_literal_overflow);
+  ASSERT_TRUE(Parser.parse());
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
+TEST_F(ParseHLSLRootSignatureTest, InvalidLexUnderflowFloatTest) {
+  // This test will check that the lexing fails due to double underflow
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = 10e-309)
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test correct diagnostic produced
+  Consumer->setExpected(diag::err_hlsl_number_literal_underflow);
+  ASSERT_TRUE(Parser.parse());
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
 TEST_F(ParseHLSLRootSignatureTest, InvalidNonZeroFlagsTest) {
   // This test will check that parsing fails when a non-zero integer literal
   // is given to flags
diff --git a/llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h b/llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h
index 25df9a7235ef3..6b4da48a302bc 100644
--- a/llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h
+++ b/llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h
@@ -160,6 +160,7 @@ struct DescriptorTableClause {
 
 struct StaticSampler {
   Register Reg;
+  float MipLODBias = 0.f;
 };
 
 /// Models RootElement : RootFlags | RootConstants | RootParam

@llvmbot
Copy link
Member

llvmbot commented May 16, 2025

@llvm/pr-subscribers-clang

Author: Finn Plummer (inbelic)

Changes
  • defines in-memory representaiton of MipLODBias to allow for testing of a float parameter
  • defines handleInt and handleFloat to handle converting a token's NumSpelling into a valid float
  • plugs this into parseFloatParam to fill in the MipLODBias param

The parsing of floats is required to match the behaviour of DXC. This behaviour is outlined as follows:

  • if the number is an integer then convert it using _atoi64, check for overflow and static_cast this to a float
  • if the number is a float then convert it using strtod, check for float overflow and static_cast this to a float, this will implicitly also check for double over/underflow and if the string is malformed then it will return an error

This pr matches this behaviour by parsing as, uint/int accordingly and then casting, or, by using the correct APFloat semantics/rounding mode with NumericLiteralParser.

  • adds testing of error diagnostics and valid float param values to demonstrate functionality

Part 2 of #126574


Full diff: https://github.com/llvm/llvm-project/pull/140181.diff

7 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+5-1)
  • (modified) clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def (+3)
  • (modified) clang/include/clang/Parse/ParseHLSLRootSignature.h (+7)
  • (modified) clang/lib/Parse/ParseHLSLRootSignature.cpp (+143-3)
  • (modified) clang/unittests/Lex/LexHLSLRootSignatureTest.cpp (+2)
  • (modified) clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp (+161-1)
  • (modified) llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h (+1)
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index fd525140d0482..554d70de86902 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1856,7 +1856,11 @@ def err_hlsl_unexpected_end_of_params
     : Error<"expected %0 to denote end of parameters, or, another valid parameter of %1">;
 def err_hlsl_rootsig_repeat_param : Error<"specified the same parameter '%0' multiple times">;
 def err_hlsl_rootsig_missing_param : Error<"did not specify mandatory parameter '%0'">;
-def err_hlsl_number_literal_overflow : Error<"integer literal is too large to be represented as a 32-bit %select{signed |}0 integer type">;
+def err_hlsl_number_literal_overflow : Error<
+  "%select{integer|float}0 literal is too large to be represented as a "
+  "%select{32-bit %select{signed|}1 integer|float}0 type">;
+def err_hlsl_number_literal_underflow : Error<
+  "float literal has a magnitude that is too small to be represented as a float type">;
 def err_hlsl_rootsig_non_zero_flag : Error<"flag value is neither a literal 0 nor a named value">;
 
 } // end of Parser diagnostics
diff --git a/clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def b/clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def
index ddebe82987197..5d16eaa5b72f6 100644
--- a/clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def
+++ b/clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def
@@ -100,6 +100,9 @@ KEYWORD(flags)
 KEYWORD(numDescriptors)
 KEYWORD(offset)
 
+// StaticSampler Keywords:
+KEYWORD(mipLODBias)
+
 // Unbounded Enum:
 UNBOUNDED_ENUM(unbounded, "unbounded")
 
diff --git a/clang/include/clang/Parse/ParseHLSLRootSignature.h b/clang/include/clang/Parse/ParseHLSLRootSignature.h
index 80fedc2f16574..c12b022a030ef 100644
--- a/clang/include/clang/Parse/ParseHLSLRootSignature.h
+++ b/clang/include/clang/Parse/ParseHLSLRootSignature.h
@@ -111,12 +111,14 @@ class RootSignatureParser {
 
   struct ParsedStaticSamplerParams {
     std::optional<llvm::hlsl::rootsig::Register> Reg;
+    std::optional<float> MipLODBias;
   };
   std::optional<ParsedStaticSamplerParams> parseStaticSamplerParams();
 
   // Common parsing methods
   std::optional<uint32_t> parseUIntParam();
   std::optional<llvm::hlsl::rootsig::Register> parseRegister();
+  std::optional<float> parseFloatParam();
 
   /// Parsing methods of various enums
   std::optional<llvm::hlsl::rootsig::ShaderVisibility> parseShaderVisibility();
@@ -128,6 +130,11 @@ class RootSignatureParser {
   /// Use NumericLiteralParser to convert CurToken.NumSpelling into a unsigned
   /// 32-bit integer
   std::optional<uint32_t> handleUIntLiteral();
+  /// Use NumericLiteralParser to convert CurToken.NumSpelling into a signed
+  /// 32-bit integer
+  std::optional<int32_t> handleIntLiteral(bool Negated);
+  /// Use NumericLiteralParser to convert CurToken.NumSpelling into a float
+  std::optional<float> handleFloatLiteral(bool Negated);
 
   /// Flags may specify the value of '0' to denote that there should be no
   /// flags set.
diff --git a/clang/lib/Parse/ParseHLSLRootSignature.cpp b/clang/lib/Parse/ParseHLSLRootSignature.cpp
index 6e4bb4d59e109..c3589fa190432 100644
--- a/clang/lib/Parse/ParseHLSLRootSignature.cpp
+++ b/clang/lib/Parse/ParseHLSLRootSignature.cpp
@@ -6,6 +6,8 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <float.h>
+
 #include "clang/Parse/ParseHLSLRootSignature.h"
 
 #include "clang/Lex/LiteralSupport.h"
@@ -378,6 +380,10 @@ std::optional<StaticSampler> RootSignatureParser::parseStaticSampler() {
 
   Sampler.Reg = Params->Reg.value();
 
+  // Fill in optional values
+  if (Params->MipLODBias.has_value())
+    Sampler.MipLODBias = Params->MipLODBias.value();
+
   if (consumeExpectedToken(TokenKind::pu_r_paren,
                            diag::err_hlsl_unexpected_end_of_params,
                            /*param of=*/TokenKind::kw_StaticSampler))
@@ -663,6 +669,23 @@ RootSignatureParser::parseStaticSamplerParams() {
         return std::nullopt;
       Params.Reg = Reg;
     }
+
+    // `mipLODBias` `=` NUMBER
+    if (tryConsumeExpectedToken(TokenKind::kw_mipLODBias)) {
+      if (Params.MipLODBias.has_value()) {
+        getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param)
+            << CurToken.TokKind;
+        return std::nullopt;
+      }
+
+      if (consumeExpectedToken(TokenKind::pu_equal))
+        return std::nullopt;
+
+      auto MipLODBias = parseFloatParam();
+      if (!MipLODBias.has_value())
+        return std::nullopt;
+      Params.MipLODBias = (float)*MipLODBias;
+    }
   } while (tryConsumeExpectedToken(TokenKind::pu_comma));
 
   return Params;
@@ -711,6 +734,35 @@ std::optional<Register> RootSignatureParser::parseRegister() {
   return Reg;
 }
 
+std::optional<float> RootSignatureParser::parseFloatParam() {
+  assert(CurToken.TokKind == TokenKind::pu_equal &&
+         "Expects to only be invoked starting at given keyword");
+  // Consume sign modifier
+  bool Signed =
+      tryConsumeExpectedToken({TokenKind::pu_plus, TokenKind::pu_minus});
+  bool Negated = Signed && CurToken.TokKind == TokenKind::pu_minus;
+
+  // Handle an uint and interpret it as a float
+  if (!Signed && tryConsumeExpectedToken(TokenKind::int_literal)) {
+    auto UInt = handleUIntLiteral();
+    if (!UInt.has_value())
+      return std::nullopt;
+    return (float)UInt.value();
+  } else if (tryConsumeExpectedToken(TokenKind::int_literal)) {
+    auto Int = handleIntLiteral(Negated);
+    if (!Int.has_value())
+      return std::nullopt;
+    return (float)Int.value();
+  } else if (tryConsumeExpectedToken(TokenKind::float_literal)) {
+    auto Float = handleFloatLiteral(Negated);
+    if (!Float.has_value())
+      return std::nullopt;
+    return Float.value();
+  }
+
+  return std::nullopt;
+}
+
 std::optional<llvm::hlsl::rootsig::ShaderVisibility>
 RootSignatureParser::parseShaderVisibility() {
   assert(CurToken.TokKind == TokenKind::pu_equal &&
@@ -821,22 +873,110 @@ std::optional<uint32_t> RootSignatureParser::handleUIntLiteral() {
                                       PP.getSourceManager(), PP.getLangOpts(),
                                       PP.getTargetInfo(), PP.getDiagnostics());
   if (Literal.hadError)
-    return true; // Error has already been reported so just return
+    return std::nullopt; // Error has already been reported so just return
 
-  assert(Literal.isIntegerLiteral() && "IsNumberChar will only support digits");
+  assert(Literal.isIntegerLiteral() &&
+         "NumSpelling can only consist of digits");
 
   llvm::APSInt Val = llvm::APSInt(32, false);
   if (Literal.GetIntegerValue(Val)) {
     // Report that the value has overflowed
     PP.getDiagnostics().Report(CurToken.TokLoc,
                                diag::err_hlsl_number_literal_overflow)
-        << 0 << CurToken.NumSpelling;
+        << /*integer type*/ 0 << /*is signed*/ 0;
     return std::nullopt;
   }
 
   return Val.getExtValue();
 }
 
+std::optional<int32_t> RootSignatureParser::handleIntLiteral(bool Negated) {
+  // Parse the numeric value and do semantic checks on its specification
+  clang::NumericLiteralParser Literal(CurToken.NumSpelling, CurToken.TokLoc,
+                                      PP.getSourceManager(), PP.getLangOpts(),
+                                      PP.getTargetInfo(), PP.getDiagnostics());
+  if (Literal.hadError)
+    return std::nullopt; // Error has already been reported so just return
+
+  assert(Literal.isIntegerLiteral() &&
+         "NumSpelling can only consist of digits");
+
+  llvm::APSInt Val = llvm::APSInt(32, true);
+  if (Literal.GetIntegerValue(Val) || INT32_MAX < Val.getExtValue()) {
+    // Report that the value has overflowed
+    PP.getDiagnostics().Report(CurToken.TokLoc,
+                               diag::err_hlsl_number_literal_overflow)
+        << /*integer type*/ 0 << /*is signed*/ 1;
+    return std::nullopt;
+  }
+
+  if (Negated)
+    Val = -Val;
+
+  return static_cast<int32_t>(Val.getExtValue());
+}
+
+std::optional<float> RootSignatureParser::handleFloatLiteral(bool Negated) {
+  // Parse the numeric value and do semantic checks on its specification
+  clang::NumericLiteralParser Literal(CurToken.NumSpelling, CurToken.TokLoc,
+                                      PP.getSourceManager(), PP.getLangOpts(),
+                                      PP.getTargetInfo(), PP.getDiagnostics());
+  if (Literal.hadError)
+    return std::nullopt; // Error has already been reported so just return
+
+  assert(Literal.isFloatingLiteral() &&
+         "NumSpelling consists only of [0-9.ef+-]. Any malformed NumSpelling "
+         "will be caught and reported by NumericLiteralParser.");
+
+  // DXC used `strtod` to convert the token string to a float which corresponds
+  // to:
+  auto DXCSemantics = llvm::APFloat::Semantics::S_IEEEdouble;
+  auto DXCRoundingMode = llvm::RoundingMode::NearestTiesToEven;
+
+  llvm::APFloat Val =
+      llvm::APFloat(llvm::APFloat::EnumToSemantics(DXCSemantics));
+  llvm::APFloat::opStatus Status = Literal.GetFloatValue(Val, DXCRoundingMode);
+
+  // Note: we do not error when opStatus::opInexact by itself as this just
+  // denotes that rounding occured but not that it is invalid
+  assert(!(Status & llvm::APFloat::opStatus::opInvalidOp) &&
+         "NumSpelling consists only of [0-9.ef+-]. Any malformed NumSpelling "
+         "will be caught and reported by NumericLiteralParser.");
+
+  assert(!(Status & llvm::APFloat::opStatus::opDivByZero) &&
+         "It is not possible for a division to be performed when "
+         "constructing an APFloat from a string");
+
+  if (Status & llvm::APFloat::opStatus::opUnderflow) {
+    // Report that the value has underflowed
+    PP.getDiagnostics().Report(CurToken.TokLoc,
+                               diag::err_hlsl_number_literal_underflow);
+    return std::nullopt;
+  }
+
+  if (Status & llvm::APFloat::opStatus::opOverflow) {
+    // Report that the value has overflowed
+    PP.getDiagnostics().Report(CurToken.TokLoc,
+                               diag::err_hlsl_number_literal_overflow)
+        << /*float type*/ 1;
+    return std::nullopt;
+  }
+
+  if (Negated)
+    Val = -Val;
+
+  double DoubleVal = Val.convertToDouble();
+  if (FLT_MAX < DoubleVal || DoubleVal < -FLT_MAX) {
+    // Report that the value has overflowed
+    PP.getDiagnostics().Report(CurToken.TokLoc,
+                               diag::err_hlsl_number_literal_overflow)
+        << /*float type*/ 1;
+    return std::nullopt;
+  }
+
+  return static_cast<float>(DoubleVal);
+}
+
 bool RootSignatureParser::verifyZeroFlag() {
   assert(CurToken.TokKind == TokenKind::int_literal);
   auto X = handleUIntLiteral();
diff --git a/clang/unittests/Lex/LexHLSLRootSignatureTest.cpp b/clang/unittests/Lex/LexHLSLRootSignatureTest.cpp
index 3e38c281f4fb1..b610b8f10f8da 100644
--- a/clang/unittests/Lex/LexHLSLRootSignatureTest.cpp
+++ b/clang/unittests/Lex/LexHLSLRootSignatureTest.cpp
@@ -136,6 +136,8 @@ TEST_F(LexHLSLRootSignatureTest, ValidLexAllTokensTest) {
     space visibility flags
     numDescriptors offset
 
+    mipLODBias
+
     unbounded
     DESCRIPTOR_RANGE_OFFSET_APPEND
 
diff --git a/clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp b/clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp
index 14c3101f3eafa..539232e0bf2c2 100644
--- a/clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp
+++ b/clang/unittests/Parse/ParseHLSLRootSignatureTest.cpp
@@ -225,7 +225,7 @@ TEST_F(ParseHLSLRootSignatureTest, ValidParseDTClausesTest) {
 
 TEST_F(ParseHLSLRootSignatureTest, ValidParseStaticSamplerTest) {
   const llvm::StringLiteral Source = R"cc(
-    StaticSampler(s0)
+    StaticSampler(s0, mipLODBias = 0)
   )cc";
 
   TrivialModuleLoader ModLoader;
@@ -247,6 +247,82 @@ TEST_F(ParseHLSLRootSignatureTest, ValidParseStaticSamplerTest) {
   ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
   ASSERT_EQ(std::get<StaticSampler>(Elem).Reg.ViewType, RegisterType::SReg);
   ASSERT_EQ(std::get<StaticSampler>(Elem).Reg.Number, 0u);
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 0.f);
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
+TEST_F(ParseHLSLRootSignatureTest, ValidParseFloatsTest) {
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = 0),
+    StaticSampler(s0, mipLODBias = +1),
+    StaticSampler(s0, mipLODBias = -1),
+    StaticSampler(s0, mipLODBias = 42.),
+    StaticSampler(s0, mipLODBias = +4.2),
+    StaticSampler(s0, mipLODBias = -.42),
+    StaticSampler(s0, mipLODBias = .42e+3),
+    StaticSampler(s0, mipLODBias = 42E-12),
+    StaticSampler(s0, mipLODBias = 42.f),
+    StaticSampler(s0, mipLODBias = 4.2F),
+    StaticSampler(s0, mipLODBias = 42.e+10f),
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test no diagnostics produced
+  Consumer->setNoDiag();
+
+  ASSERT_FALSE(Parser.parse());
+
+  RootElement Elem = Elements[0];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 0.f);
+
+  Elem = Elements[1];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 1.f);
+
+  Elem = Elements[2];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, -1.f);
+
+  Elem = Elements[3];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 42.f);
+
+  Elem = Elements[4];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 4.2f);
+
+  Elem = Elements[5];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, -.42f);
+
+  Elem = Elements[6];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 420.f);
+
+  Elem = Elements[7];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 0.000000000042f);
+
+  Elem = Elements[8];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 42.f);
+
+  Elem = Elements[9];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 4.2f);
+
+  Elem = Elements[10];
+  ASSERT_TRUE(std::holds_alternative<StaticSampler>(Elem));
+  ASSERT_EQ(std::get<StaticSampler>(Elem).MipLODBias, 420000000000.f);
 
   ASSERT_TRUE(Consumer->isSatisfied());
 }
@@ -688,6 +764,90 @@ TEST_F(ParseHLSLRootSignatureTest, InvalidLexOverflowedNumberTest) {
   ASSERT_TRUE(Consumer->isSatisfied());
 }
 
+TEST_F(ParseHLSLRootSignatureTest, InvalidLexOverflowedFloatTest) {
+  // This test will check that the lexing fails due to a float overflow
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = 3.402823467e+38F)
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test correct diagnostic produced
+  Consumer->setExpected(diag::err_hlsl_number_literal_overflow);
+  ASSERT_TRUE(Parser.parse());
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
+TEST_F(ParseHLSLRootSignatureTest, InvalidLexNegOverflowedFloatTest) {
+  // This test will check that the lexing fails due to negative float overflow
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = -3.402823467e+38F)
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test correct diagnostic produced
+  Consumer->setExpected(diag::err_hlsl_number_literal_overflow);
+  ASSERT_TRUE(Parser.parse());
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
+TEST_F(ParseHLSLRootSignatureTest, InvalidLexOverflowedDoubleTest) {
+  // This test will check that the lexing fails due to an overflow of double
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = 1.e+500)
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test correct diagnostic produced
+  Consumer->setExpected(diag::err_hlsl_number_literal_overflow);
+  ASSERT_TRUE(Parser.parse());
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
+TEST_F(ParseHLSLRootSignatureTest, InvalidLexUnderflowFloatTest) {
+  // This test will check that the lexing fails due to double underflow
+  const llvm::StringLiteral Source = R"cc(
+    StaticSampler(s0, mipLODBias = 10e-309)
+  )cc";
+
+  TrivialModuleLoader ModLoader;
+  auto PP = createPP(Source, ModLoader);
+  auto TokLoc = SourceLocation();
+
+  hlsl::RootSignatureLexer Lexer(Source, TokLoc);
+  SmallVector<RootElement> Elements;
+  hlsl::RootSignatureParser Parser(Elements, Lexer, *PP);
+
+  // Test correct diagnostic produced
+  Consumer->setExpected(diag::err_hlsl_number_literal_underflow);
+  ASSERT_TRUE(Parser.parse());
+
+  ASSERT_TRUE(Consumer->isSatisfied());
+}
+
 TEST_F(ParseHLSLRootSignatureTest, InvalidNonZeroFlagsTest) {
   // This test will check that parsing fails when a non-zero integer literal
   // is given to flags
diff --git a/llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h b/llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h
index 25df9a7235ef3..6b4da48a302bc 100644
--- a/llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h
+++ b/llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h
@@ -160,6 +160,7 @@ struct DescriptorTableClause {
 
 struct StaticSampler {
   Register Reg;
+  float MipLODBias = 0.f;
 };
 
 /// Models RootElement : RootFlags | RootConstants | RootParam

auto MipLODBias = parseFloatParam();
if (!MipLODBias.has_value())
return std::nullopt;
Params.MipLODBias = (float)*MipLODBias;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
Params.MipLODBias = (float)*MipLODBias;
Params.MipLODBias = MipLODBias;

can remove lingering cast from previous version

@inbelic inbelic linked an issue May 16, 2025 that may be closed by this pull request
4 tasks
@inbelic inbelic force-pushed the inbelic/rs-parse-floats-sampler branch from 6c48b1f to 158ca53 Compare May 26, 2025 22:31
// doesn't overflow as a signed 32-bit int.
int64_t MaxMagnitude =
-static_cast<int64_t>(std::numeric_limits<int32_t>::min());
if (Literal.GetIntegerValue(Val) || MaxMagnitude < Val.getExtValue()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this work correctly for positive 2147483648? int32_t's min is -2147483648, so MaxMagnitude == 2147483648 here, but int32_t max is 2147483647, so I think we'll fail to report the overflow here.

Copy link
Contributor

@Icohedron Icohedron May 27, 2025

Choose a reason for hiding this comment

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

2147483648 would be parsed by handleUIntLiteral instead. But yes, if handleIntLiteral was called instead for some reason then it would fail to report an overflow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated such that handleIntLiteral will correctly handle overflow for a positive signed integer int32_t.

The only caveat is that it doesn't have a test case as this can't currently be invoked through the current public parser api.

@inbelic inbelic changed the base branch from users/inbelic/pr-140180 to main May 28, 2025 17:35
@inbelic inbelic force-pushed the inbelic/rs-parse-floats-sampler branch from 158ca53 to 12e972c Compare May 28, 2025 17:36
@inbelic inbelic merged commit a926c61 into llvm:main May 29, 2025
12 checks passed
svkeerthy pushed a commit that referenced this pull request May 29, 2025
- defines in-memory representaiton of MipLODBias to allow for testing of
a float parameter
- defines `handleInt` and `handleFloat` to handle converting a token's
`NumSpelling` into a valid float
- plugs this into `parseFloatParam` to fill in the MipLODBias param

The parsing of floats is required to match the behaviour of DXC. This
behaviour is outlined as follows:
- if the number is an integer then convert it using `_atoi64`, check for
overflow and static_cast this to a float
- if the number is a float then convert it using `strtod`, check for
float overflow and static_cast this to a float, this will implicitly
also check for double over/underflow and if the string is malformed then
it will return an error

This pr matches this behaviour by parsing as, uint/int accordingly and
then casting, or, by using the correct APFloat semantics/rounding mode
with `NumericLiteralParser`.

- adds testing of error diagnostics and valid float param values to
demonstrate functionality

Part 2 of #126574
google-yfyang pushed a commit to google-yfyang/llvm-project that referenced this pull request May 29, 2025
…40181)

- defines in-memory representaiton of MipLODBias to allow for testing of
a float parameter
- defines `handleInt` and `handleFloat` to handle converting a token's
`NumSpelling` into a valid float
- plugs this into `parseFloatParam` to fill in the MipLODBias param

The parsing of floats is required to match the behaviour of DXC. This
behaviour is outlined as follows:
- if the number is an integer then convert it using `_atoi64`, check for
overflow and static_cast this to a float
- if the number is a float then convert it using `strtod`, check for
float overflow and static_cast this to a float, this will implicitly
also check for double over/underflow and if the string is malformed then
it will return an error

This pr matches this behaviour by parsing as, uint/int accordingly and
then casting, or, by using the correct APFloat semantics/rounding mode
with `NumericLiteralParser`.

- adds testing of error diagnostics and valid float param values to
demonstrate functionality

Part 2 of llvm#126574
@inbelic inbelic deleted the inbelic/rs-parse-floats-sampler branch June 2, 2025 20:33
sivan-shani pushed a commit to sivan-shani/llvm-project that referenced this pull request Jun 3, 2025
…40181)

- defines in-memory representaiton of MipLODBias to allow for testing of
a float parameter
- defines `handleInt` and `handleFloat` to handle converting a token's
`NumSpelling` into a valid float
- plugs this into `parseFloatParam` to fill in the MipLODBias param

The parsing of floats is required to match the behaviour of DXC. This
behaviour is outlined as follows:
- if the number is an integer then convert it using `_atoi64`, check for
overflow and static_cast this to a float
- if the number is a float then convert it using `strtod`, check for
float overflow and static_cast this to a float, this will implicitly
also check for double over/underflow and if the string is malformed then
it will return an error

This pr matches this behaviour by parsing as, uint/int accordingly and
then casting, or, by using the correct APFloat semantics/rounding mode
with `NumericLiteralParser`.

- adds testing of error diagnostics and valid float param values to
demonstrate functionality

Part 2 of llvm#126574
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[HLSL] Implement Root Signature Parsing of Static Samplers
5 participants