Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tools/clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,8 @@ def warn_hlsl_effect_technique : Warning <
def warn_hlsl_semantic_identifier_collision : Warning <
"'%0' interpreted as semantic; previous definition(s) ignored">,
InGroup< HLSLSemanticIdentifierCollision >;
def err_hlsl_register_is_misspelled : Error <
"Syntax similar to : register() was used but unexpected keyword '%0' was used instead.">;
def err_hlsl_enum : Error<
"enum is unsupported in HLSL before 2017">;
def warn_hlsl_new_feature : Warning <
Expand Down
3 changes: 3 additions & 0 deletions tools/clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -2098,6 +2098,9 @@ class Parser : public CodeCompletionHandler {

// HLSL Change Starts: Parse HLSL Attributes and append them to Declarator Object
bool MaybeParseHLSLAttributes(std::vector<hlsl::UnusualAnnotation *> &target);
bool ConsumeRegisterAssignment(
hlsl::RegisterAssignment
&asg); // Make sure it starts with register( first
inline bool MaybeParseHLSLAttributes(Declarator &D) {
return MaybeParseHLSLAttributes(D.UnusualAnnotations);
}
Expand Down
271 changes: 152 additions & 119 deletions tools/clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,143 @@ static void ParseSpaceForHLSL(const StringRef name,
}
}

bool Parser::ConsumeRegisterAssignment(hlsl::RegisterAssignment &r) {

ASTContext &context = getActions().getASTContext();

if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}

StringRef identifierText = Tok.getIdentifierInfo()->getName();
if (IsShaderProfileLike(identifierText) ||
IsShaderProfileShort(identifierText)) {
r.ShaderProfile = Tok.getIdentifierInfo()->getName();
ConsumeToken(); // consume shader model
if (ExpectAndConsume(tok::comma, diag::err_expected)) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}
}

if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}

DXASSERT(Tok.is(tok::identifier),
"otherwise previous code should have failed");
unsigned diagId;

bool hasOnlySpace = false;
identifierText = Tok.getIdentifierInfo()->getName();
if (identifierText.substr(0, sizeof("space") - 1).equals("space")) {
hasOnlySpace = true;
} else {
ParseRegisterNumberForHLSL(Tok.getIdentifierInfo()->getName(),
&r.RegisterType, &r.RegisterNumber, &diagId);
if (diagId == 0) {
r.setIsValid(true);
} else {
r.setIsValid(false);
Diag(Tok.getLocation(), diagId);
}

ConsumeToken(); // consume register (type'#')

ExprResult subcomponentResult;
if (Tok.is(tok::l_square)) {
BalancedDelimiterTracker brackets(*this, tok::l_square);
brackets.consumeOpen();

ExprResult result;
if (Tok.isNot(tok::r_square)) {
subcomponentResult = ParseConstantExpression();
r.IsValid = r.IsValid && !subcomponentResult.isInvalid();
Expr::EvalResult evalResult;
if (!subcomponentResult.get()->EvaluateAsRValue(evalResult, context) ||
evalResult.hasSideEffects() ||
(!evalResult.Val.isInt() && !evalResult.Val.isFloat())) {
Diag(Tok.getLocation(),
diag::err_hlsl_unsupported_register_noninteger);
r.setIsValid(false);
} else {
llvm::APSInt intResult;
if (evalResult.Val.isFloat()) {
bool isExact;
// TODO: consider what to do when convertToInteger fails
evalResult.Val.getFloat().convertToInteger(
intResult, llvm::APFloat::roundingMode::rmTowardZero, &isExact);
} else {
DXASSERT(
evalResult.Val.isInt(),
"otherwise prior test in this function should have failed");
intResult = evalResult.Val.getInt();
}

if (intResult.isNegative()) {
Diag(Tok.getLocation(),
diag::err_hlsl_unsupported_register_noninteger);
r.setIsValid(false);
} else {
r.RegisterOffset = intResult.getLimitedValue();
}
}
} else {
Diag(Tok.getLocation(), diag::err_expected_expression);
r.setIsValid(false);
}

if (brackets.consumeClose()) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}
}
}
if (hasOnlySpace) {
uint32_t RegisterSpaceValue = 0;
ParseSpaceForHLSL(Tok.getIdentifierInfo()->getName(), &RegisterSpaceValue,
&diagId);
if (diagId != 0) {
Diag(Tok.getLocation(), diagId);
r.setIsValid(false);
} else {
r.RegisterSpace = RegisterSpaceValue;
r.setIsValid(true);
}
ConsumeToken(); // consume identifier
} else {
if (Tok.is(tok::comma)) {
ConsumeToken(); // consume comma
if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}
unsigned RegisterSpaceVal = 0;
ParseSpaceForHLSL(Tok.getIdentifierInfo()->getName(), &RegisterSpaceVal,
&diagId);
if (diagId != 0) {
Diag(Tok.getLocation(), diagId);
r.setIsValid(false);
} else {
r.RegisterSpace = RegisterSpaceVal;
}
ConsumeToken(); // consume identifier
}
}

if (ExpectAndConsume(tok::r_paren, diag::err_expected)) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}

return false;
}

bool Parser::MaybeParseHLSLAttributes(std::vector<hlsl::UnusualAnnotation *> &target)
{
if (!getLangOpts().HLSL) {
Expand Down Expand Up @@ -428,127 +565,9 @@ bool Parser::MaybeParseHLSLAttributes(std::vector<hlsl::UnusualAnnotation *> &ta
if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after, "register")) {
return true;
}
if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}

StringRef identifierText = Tok.getIdentifierInfo()->getName();
if (IsShaderProfileLike(identifierText) || IsShaderProfileShort(identifierText)) {
r.ShaderProfile = Tok.getIdentifierInfo()->getName();
ConsumeToken(); // consume shader model
if (ExpectAndConsume(tok::comma, diag::err_expected)) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}
}

if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}

DXASSERT(Tok.is(tok::identifier), "otherwise previous code should have failed");
unsigned diagId;

bool hasOnlySpace = false;
identifierText = Tok.getIdentifierInfo()->getName();
if (identifierText.substr(0, sizeof("space")-1).equals("space")) {
hasOnlySpace = true;
} else {
ParseRegisterNumberForHLSL(
Tok.getIdentifierInfo()->getName(), &r.RegisterType, &r.RegisterNumber, &diagId);
if (diagId == 0) {
r.setIsValid(true);
} else {
r.setIsValid(false);
Diag(Tok.getLocation(), diagId);
}

ConsumeToken(); // consume register (type'#')

ExprResult subcomponentResult;
if (Tok.is(tok::l_square)) {
BalancedDelimiterTracker brackets(*this, tok::l_square);
brackets.consumeOpen();

ExprResult result;
if (Tok.isNot(tok::r_square)) {
subcomponentResult = ParseConstantExpression();
r.IsValid = r.IsValid && !subcomponentResult.isInvalid();
Expr::EvalResult evalResult;
if (!subcomponentResult.get()->EvaluateAsRValue(evalResult, context) ||
evalResult.hasSideEffects() ||
(!evalResult.Val.isInt() && !evalResult.Val.isFloat())) {
Diag(Tok.getLocation(), diag::err_hlsl_unsupported_register_noninteger);
r.setIsValid(false);
} else {
llvm::APSInt intResult;
if (evalResult.Val.isFloat()) {
bool isExact;
// TODO: consider what to do when convertToInteger fails
evalResult.Val.getFloat().convertToInteger(intResult, llvm::APFloat::roundingMode::rmTowardZero, &isExact);
} else {
DXASSERT(evalResult.Val.isInt(), "otherwise prior test in this function should have failed");
intResult = evalResult.Val.getInt();
}

if (intResult.isNegative()) {
Diag(Tok.getLocation(), diag::err_hlsl_unsupported_register_noninteger);
r.setIsValid(false);
} else {
r.RegisterOffset = intResult.getLimitedValue();
}
}
} else {
Diag(Tok.getLocation(), diag::err_expected_expression);
r.setIsValid(false);
}

if (brackets.consumeClose()) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}
}
}
if (hasOnlySpace) {
uint32_t RegisterSpaceValue = 0;
ParseSpaceForHLSL(Tok.getIdentifierInfo()->getName(), &RegisterSpaceValue, &diagId);
if (diagId != 0) {
Diag(Tok.getLocation(), diagId);
r.setIsValid(false);
} else {
r.RegisterSpace = RegisterSpaceValue;
r.setIsValid(true);
}
ConsumeToken(); // consume identifier
} else {
if (Tok.is(tok::comma)) {
ConsumeToken(); // consume comma
if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return true;
}
unsigned RegisterSpaceVal = 0;
ParseSpaceForHLSL(Tok.getIdentifierInfo()->getName(), &RegisterSpaceVal, &diagId);
if (diagId != 0) {
Diag(Tok.getLocation(), diagId);
r.setIsValid(false);
}
else {
r.RegisterSpace = RegisterSpaceVal;
}
ConsumeToken(); // consume identifier
}
}

if (ExpectAndConsume(tok::r_paren, diag::err_expected)) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
if (ConsumeRegisterAssignment(r))
return true;
}

target.push_back(new (context) hlsl::RegisterAssignment(r));
}
Expand Down Expand Up @@ -633,6 +652,20 @@ bool Parser::MaybeParseHLSLAttributes(std::vector<hlsl::UnusualAnnotation *> &ta
Actions.DiagnoseSemanticDecl(pUA);
ConsumeToken(); // consume semantic

// Likely a misspell of register() or a mismatching macro:
// registers() would cause a crash without this fix.

if (Tok.is(tok::l_paren)) {

Diag(Tok.getLocation(), diag::err_hlsl_register_is_misspelled)
<< semanticName;

ConsumeParen();
hlsl::RegisterAssignment dummy;
if (ConsumeRegisterAssignment(dummy)) // Skip invalid syntax
return true;
}

target.push_back(pUA);
}
else {
Expand Down
11 changes: 11 additions & 0 deletions tools/clang/test/SemaHLSL/register-mistype-errors.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// RUN: %dxc -T lib_6_3 -verify %s

// expected-error@+1 {{Syntax similar to : register() was used but unexpected keyword 'registers' was used instead.}}
RWStructuredBuffer<float4> uav1 : registers(u3);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this also work with : register (u3); ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also would you be able to add a case for an incorrectly spelled pack offset?

Copy link
Contributor Author

@Nielsbishere Nielsbishere Nov 5, 2025

Choose a reason for hiding this comment

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

Wow I think you're correct there, I've never heard of that syntax before. Will fix it and add a test for it.
: register (u3) uses the same parsing as before (I only moved it), so that should still work. I can still add a test for it though.

The pack offset is pretty annoying, because from what I saw in #hlsl code horrors... I'll let the code speak for itself:

struct PSInput {
	float4 color : COLOR;
};

cbuffer test {
  Texture2D horrors : register(t0);  
};

float4 PSMain(PSInput input) : SV_TARGET {
	return input.color * horrors[0.xx];
}

So I can't automatically exclude this error when it's parsing a constant buffer. I guess the way would be to fallback to parsing that syntax (packoffset syntax) if it fails to be parsed as a register...

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah yes, HLSL horrors beyond comprehension.
I meant adding a test case for packoffset, not exactly sure what you mean by fix it.
You might consider making the error diagnostic more generic, like "unexpected identifier %s, expected an hlsl attribute " or something to that effect. That way if the packoffset parsing fails you can reuse the same error.
I'm not sure falling back would work either because we only attempt to parse the register if the next token is detected to be a register token.

Copy link
Contributor Author

@Nielsbishere Nielsbishere Nov 5, 2025

Choose a reason for hiding this comment

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

and... packoffset allows unions apparently.

cbuffer ohno {
  float a : packoffset(c0.x);
  float b : packoffset(c0.x);
};

Copy link
Contributor Author

@Nielsbishere Nielsbishere Nov 5, 2025

Choose a reason for hiding this comment

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

What I mean is that packoffset likely will get unintentionally parsed as register() as you suggested. I think I might be able to correctly identify it's misspelled packoffset as long as the rest of the syntax is similar.


[shader("pixel")]
float4 main(): SV_Target
{
uav1[0] = 2;
return 0.xxxx;
}