tint: Add unary-ops to the intrinsic table

• Declare all the unary ops in the intrinsics.def file.
• Reimplement the bulk of Resolver::UnaryOp() with the IntrinsicTable.

This will simplify maintenance of the operators, and will greatly
simplify the [AbstractInt -> i32|u32] [AbstractFloat -> f32|f16] logic.

Bug: tint:1504
Change-Id: Ifc646d086fc93cfbe3f3f861b8c447178664c1f7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/89028
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
Ben Clayton 2022-05-09 21:22:24 +00:00 committed by Dawn LUCI CQ
parent 7378612ca5
commit b61e0452f8
11 changed files with 2617 additions and 2364 deletions

View File

@ -109,6 +109,7 @@ type __frexp_result
////////////////////////////////////////////////////////////////////////////////
match fiu32: f32 | i32 | u32
match fi32: f32 | i32
match iu32: i32 | u32
match scalar: f32 | i32 | u32 | bool
@ -572,6 +573,18 @@ fn textureLoad(texture: texture_external, coords: vec2<i32>) -> vec4<f32>
// //
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Unary Operators //
////////////////////////////////////////////////////////////////////////////////
op ! (bool) -> bool
op ! <N: num> (vec<N, bool>) -> vec<N, bool>
op ~ <T: iu32>(T) -> T
op ~ <T: iu32, N: num> (vec<N, T>) -> vec<N, T>
op - <T: fi32>(T) -> T
op - <T: fi32, N: num> (vec<N, T>) -> vec<N, T>
////////////////////////////////////////////////////////////////////////////////
// Binary Operators //
////////////////////////////////////////////////////////////////////////////////

View File

@ -828,6 +828,8 @@ class Impl : public IntrinsicTable {
const std::vector<const sem::Type*>& args,
const Source& source) override;
UnaryOperator Lookup(ast::UnaryOp op, const sem::Type* arg, const Source& source) override;
BinaryOperator Lookup(ast::BinaryOp op,
const sem::Type* lhs,
const sem::Type* rhs,
@ -945,6 +947,61 @@ const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type,
return nullptr;
}
IntrinsicTable::UnaryOperator Impl::Lookup(ast::UnaryOp op,
const sem::Type* arg,
const Source& source) {
// The list of failed matches that had promise.
std::vector<Candidate> candidates;
auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<uint32_t, const char*> {
switch (op) {
case ast::UnaryOp::kComplement:
return {kOperatorComplement, "operator ~ "};
case ast::UnaryOp::kNegation:
return {kOperatorMinus, "operator - "};
case ast::UnaryOp::kNot:
return {kOperatorNot, "operator ! "};
default:
return {0, "<unknown>"};
}
}();
auto& builtin = kOperators[intrinsic_index];
for (uint32_t o = 0; o < builtin.num_overloads; o++) {
int match_score = 1000;
auto& overload = builtin.overloads[o];
if (overload.num_parameters == 1) {
auto match = Match(intrinsic_name, intrinsic_index, overload, {arg}, match_score);
if (match.return_type) {
return UnaryOperator{match.return_type, match.parameters[0].type};
}
if (match_score > 0) {
candidates.emplace_back(Candidate{&overload, match_score});
}
}
}
// Sort the candidates with the most promising first
std::stable_sort(candidates.begin(), candidates.end(),
[](const Candidate& a, const Candidate& b) { return a.score > b.score; });
// Generate an error message
std::stringstream ss;
ss << "no matching overload for " << CallSignature(builder, intrinsic_name, {arg}) << std::endl;
if (!candidates.empty()) {
ss << std::endl;
ss << candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
<< std::endl;
for (auto& candidate : candidates) {
ss << " ";
PrintOverload(ss, *candidate.overload, intrinsic_name);
ss << std::endl;
}
}
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
return {};
}
IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
const sem::Type* lhs,
const sem::Type* rhs,
@ -1000,13 +1057,15 @@ IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
for (uint32_t o = 0; o < builtin.num_overloads; o++) {
int match_score = 1000;
auto& overload = builtin.overloads[o];
auto match = Match(intrinsic_name, intrinsic_index, overload, {lhs, rhs}, match_score);
if (match.return_type) {
return BinaryOperator{match.return_type, match.parameters[0].type,
match.parameters[1].type};
}
if (match_score > 0) {
candidates.emplace_back(Candidate{&overload, match_score});
if (overload.num_parameters == 2) {
auto match = Match(intrinsic_name, intrinsic_index, overload, {lhs, rhs}, match_score);
if (match.return_type) {
return BinaryOperator{match.return_type, match.parameters[0].type,
match.parameters[1].type};
}
if (match_score > 0) {
candidates.emplace_back(Candidate{&overload, match_score});
}
}
}

View File

@ -38,6 +38,14 @@ class IntrinsicTable {
/// Destructor
virtual ~IntrinsicTable();
/// UnaryOperator describes a resolved unary operator
struct UnaryOperator {
/// The result type of the unary operator
const sem::Type* result;
/// The type of the arg of the unary operator
const sem::Type* arg;
};
/// BinaryOperator describes a resolved binary operator
struct BinaryOperator {
/// The result type of the binary operator
@ -58,6 +66,15 @@ class IntrinsicTable {
const std::vector<const sem::Type*>& args,
const Source& source) = 0;
/// Lookup looks for the unary op overload with the given signature, raising an error
/// diagnostic if the operator was not found.
/// @param op the unary operator
/// @param arg the type of the expression passed to the operator
/// @param source the source of the operator call
/// @return the operator call target signature. If the operator was not found
/// UnaryOperator::result will be nullptr.
virtual UnaryOperator Lookup(ast::UnaryOp op, const sem::Type* arg, const Source& source) = 0;
/// Lookup looks for the binary op overload with the given signature, raising an error
/// diagnostic if the operator was not found.
/// @param op the binary operator

File diff suppressed because it is too large Load Diff

View File

@ -427,7 +427,9 @@ Matchers::~Matchers() = default;
{{- else if eq . "&&" -}}LogicalAnd
{{- else if eq . "||" -}}LogicalOr
{{- else if eq . "==" -}}Equal
{{- else if eq . "!" -}}Not
{{- else if eq . "!=" -}}NotEqual
{{- else if eq . "~" -}}Complement
{{- else if eq . "<" -}}LessThan
{{- else if eq . ">" -}}GreaterThan
{{- else if eq . "<=" -}}LessThanEqual

View File

@ -576,6 +576,27 @@ TEST_F(IntrinsicTableTest, SameOverloadReturnsSameBuiltinPointer) {
EXPECT_NE(b, c);
}
TEST_F(IntrinsicTableTest, MatchUnaryOp) {
auto* i32 = create<sem::I32>();
auto* vec3_i32 = create<sem::Vector>(i32, 3u);
auto result = table->Lookup(ast::UnaryOp::kNegation, vec3_i32, Source{{12, 34}});
EXPECT_EQ(result.result, vec3_i32);
EXPECT_EQ(result.result, vec3_i32);
EXPECT_EQ(Diagnostics().str(), "");
}
TEST_F(IntrinsicTableTest, MismatchUnaryOp) {
auto* bool_ = create<sem::Bool>();
auto result = table->Lookup(ast::UnaryOp::kNegation, bool_, Source{{12, 34}});
ASSERT_EQ(result.result, nullptr);
EXPECT_EQ(Diagnostics().str(), R"(12:34 error: no matching overload for operator - (bool)
2 candidate operators:
operator - (T) -> T where: T is f32 or i32
operator - (vecN<T>) -> vecN<T> where: T is f32 or i32
)");
}
TEST_F(IntrinsicTableTest, MatchBinaryOp) {
auto* i32 = create<sem::I32>();
auto* vec3_i32 = create<sem::Vector>(i32, 3u);

View File

@ -1771,38 +1771,6 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
const sem::Variable* source_var = nullptr;
switch (unary->op) {
case ast::UnaryOp::kNot:
// Result type matches the deref'd inner type.
ty = expr_ty->UnwrapRef();
if (!ty->Is<sem::Bool>() && !ty->is_bool_vector()) {
AddError("cannot logical negate expression of type '" + sem_.TypeNameOf(expr_ty),
unary->expr->source);
return nullptr;
}
break;
case ast::UnaryOp::kComplement:
// Result type matches the deref'd inner type.
ty = expr_ty->UnwrapRef();
if (!ty->is_integer_scalar_or_vector()) {
AddError(
"cannot bitwise complement expression of type '" + sem_.TypeNameOf(expr_ty),
unary->expr->source);
return nullptr;
}
break;
case ast::UnaryOp::kNegation:
// Result type matches the deref'd inner type.
ty = expr_ty->UnwrapRef();
if (!(ty->IsAnyOf<sem::F32, sem::I32>() || ty->is_signed_integer_vector() ||
ty->is_float_vector())) {
AddError("cannot negate expression of type '" + sem_.TypeNameOf(expr_ty),
unary->expr->source);
return nullptr;
}
break;
case ast::UnaryOp::kAddressOf:
if (auto* ref = expr_ty->As<sem::Reference>()) {
if (ref->StoreType()->UnwrapRef()->is_handle()) {
@ -1840,6 +1808,13 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
return nullptr;
}
break;
default: {
ty = intrinsic_table_->Lookup(unary->op, expr_ty, unary->source).result;
if (!ty) {
return nullptr;
}
}
}
auto val = EvaluateConstantValue(unary, ty);

View File

@ -1932,7 +1932,7 @@ TEST_F(ResolverTest, UnaryOp_Not) {
WrapInFunction(der);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: cannot logical negate expression of type 'vec4<f32>");
EXPECT_THAT(r()->error(), HasSubstr("error: no matching overload for operator ! (vec4<f32>)"));
}
TEST_F(ResolverTest, UnaryOp_Complement) {
@ -1942,7 +1942,7 @@ TEST_F(ResolverTest, UnaryOp_Complement) {
WrapInFunction(der);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: cannot bitwise complement expression of type 'vec4<f32>");
EXPECT_THAT(r()->error(), HasSubstr("error: no matching overload for operator ~ (vec4<f32>)"));
}
TEST_F(ResolverTest, UnaryOp_Negation) {
@ -1952,7 +1952,7 @@ TEST_F(ResolverTest, UnaryOp_Negation) {
WrapInFunction(der);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: cannot negate expression of type 'u32");
EXPECT_THAT(r()->error(), HasSubstr("error: no matching overload for operator - (u32)"));
}
TEST_F(ResolverTest, TextureSampler_TextureSample) {

View File

@ -72,6 +72,8 @@ func (l *lexer) lex() error {
l.tok(1, tok.Modulo)
case '^':
l.tok(1, tok.Xor)
case '~':
l.tok(1, tok.Complement)
case '"':
start := l.loc
l.next() // Skip opening quote
@ -105,6 +107,7 @@ func (l *lexer) lex() error {
case l.match("||", tok.OrOr):
case l.match("|", tok.Or):
case l.match("!=", tok.NotEqual):
case l.match("!", tok.Not):
case l.match("==", tok.Equal):
case l.match("=", tok.Assign):
case l.match("<<", tok.Shl):

View File

@ -91,6 +91,9 @@ func TestLexTokens(t *testing.T) {
{"|", tok.Token{Kind: tok.Or, Runes: []rune("|"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"!", tok.Token{Kind: tok.Not, Runes: []rune("!"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"!=", tok.Token{Kind: tok.NotEqual, Runes: []rune("!="), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},

View File

@ -32,34 +32,36 @@ const (
Operator Kind = "op"
Type Kind = "type"
Enum Kind = "enum"
Colon Kind = ":"
Comma Kind = ","
Shl Kind = "<<"
Shr Kind = ">>"
Lt Kind = "<"
Le Kind = "<="
Gt Kind = ">"
Ge Kind = ">="
Lbrace Kind = "{"
Rbrace Kind = "}"
Ldeco Kind = "[["
Rdeco Kind = "]]"
Lparen Kind = "("
Rparen Kind = ")"
Or Kind = "|"
Arrow Kind = "->"
Star Kind = "*"
Divide Kind = "/"
Modulo Kind = "%"
Xor Kind = "^"
Plus Kind = "+"
Minus Kind = "-"
And Kind = "&"
AndAnd Kind = "&&"
OrOr Kind = "||"
NotEqual Kind = "!="
Equal Kind = "=="
Arrow Kind = "->"
Assign Kind = "="
Colon Kind = ":"
Comma Kind = ","
Complement Kind = "~"
Divide Kind = "/"
Equal Kind = "=="
Ge Kind = ">="
Gt Kind = ">"
Lbrace Kind = "{"
Ldeco Kind = "[["
Le Kind = "<="
Lparen Kind = "("
Lt Kind = "<"
Minus Kind = "-"
Modulo Kind = "%"
Not Kind = "!"
NotEqual Kind = "!="
Or Kind = "|"
OrOr Kind = "||"
Plus Kind = "+"
Rbrace Kind = "}"
Rdeco Kind = "]]"
Rparen Kind = ")"
Shl Kind = "<<"
Shr Kind = ">>"
Star Kind = "*"
Xor Kind = "^"
)
// Invalid represents an invalid token