blob: 0caceeebe37edd248000ffee416da3baaa598fa7 [file] [log] [blame]
// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/core/intrinsic/table.h"
#include <algorithm>
#include <limits>
#include <ostream>
#include <utility>
#include "src/tint/lang/core/evaluation_stage.h"
#include "src/tint/lang/core/intrinsic/table_data.h"
#include "src/tint/lang/core/type/invalid.h"
#include "src/tint/lang/core/type/manager.h"
#include "src/tint/lang/core/type/void.h"
#include "src/tint/utils/containers/slice.h"
#include "src/tint/utils/ice/ice.h"
#include "src/tint/utils/macros/defer.h"
#include "src/tint/utils/text/string_stream.h"
#include "src/tint/utils/text/styled_text.h"
#include "src/tint/utils/text/text_style.h"
namespace tint::core::intrinsic {
const Number Number::any{Number::kAny};
const Number Number::invalid{Number::kInvalid};
Any::Any() : Base(0u, core::type::Flags{}) {}
Any::~Any() = default;
bool Any::Equals(const core::type::UniqueNode&) const {
return false;
}
std::string Any::FriendlyName() const {
return "<any>";
}
core::type::Type* Any::Clone(core::type::CloneContext&) const {
return nullptr;
}
namespace {
/// The Vector `N` template argument value for arrays of parameters.
constexpr const size_t kNumFixedParameters = Overload::kNumFixedParameters;
/// The Vector `N` template argument value for arrays of overload candidates.
constexpr const size_t kNumFixedCandidates = 8;
/// A list of candidates
using Candidates = Vector<Candidate, kNumFixedCandidates>;
/// Callback function when no overloads match.
using OnNoMatch = std::function<StyledText(VectorRef<Candidate>)>;
/// Sorts the candidates based on their score, with the lowest (best-ranking) scores first.
static inline void SortCandidates(Candidates& candidates) {
std::stable_sort(candidates.begin(), candidates.end(),
[&](const Candidate& a, const Candidate& b) { return a.score < b.score; });
}
/// Prints a list of types.
static void PrintTypeList(StyledText& ss, VectorRef<const core::type::Type*> types) {
bool first = true;
for (auto* arg : types) {
if (!first) {
ss << ", ";
}
first = false;
ss << style::Type(arg->FriendlyName());
}
}
/// Attempts to find a single intrinsic overload that matches the provided argument types.
/// @param context the intrinsic context
/// @param intrinsic the intrinsic being called
/// @param intrinsic_name the name of the intrinsic
/// @param template_args the template argument types
/// @param args the argument types
/// @param on_no_match an error callback when no intrinsic overloads matched the provided
/// arguments.
/// @returns the matched intrinsic
Result<Overload, StyledText> MatchIntrinsic(Context& context,
const IntrinsicInfo& intrinsic,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args,
EvaluationStage earliest_eval_stage,
const OnNoMatch& on_no_match);
/// The scoring mode for ScoreOverload()
enum class ScoreMode {
/// If the overload doesn't match, then the returned Candidate will simply have a score of 1.
/// No other fields will be populated.
kEarlyReject,
/// A more expensive score calculations will be made for the overload, which can be used
/// to rank potential overloads
kFull
};
/// Evaluates the single overload for the provided argument types.
/// @param context the intrinsic context
/// @param overload the overload being considered
/// @param template_args the template argument types
/// @param args the argument types
/// @tparam MODE the scoring mode to use. Passed as a template argument to ensure that the
/// extremely-hot function is specialized without scoring logic for the common code path.
/// @returns the evaluated Candidate information.
template <ScoreMode MODE>
Candidate ScoreOverload(Context& context,
const OverloadInfo& overload,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args,
EvaluationStage earliest_eval_stage);
/// Performs overload resolution given the list of candidates, by ranking the conversions of
/// arguments to the each of the candidate's parameter types.
/// @param context the intrinsic context
/// @param candidates the list of candidate overloads
/// @param intrinsic_name the name of the intrinsic
/// @param template_args the template argument types
/// @param args the argument types
/// @see https://www.w3.org/TR/WGSL/#overload-resolution-section
/// @returns the resolved Candidate.
Result<Candidate, StyledText> ResolveCandidate(Context& context,
Candidates&& candidates,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args);
// Prints the list of candidates for emitting diagnostics
void PrintCandidates(StyledText& err,
Context& context,
VectorRef<Candidate> candidates,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args);
/// Raises an ICE when no overload is a clear winner of overload resolution
StyledText ErrAmbiguousOverload(Context& context,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args,
VectorRef<Candidate> candidates);
/// @return a string representing a call to a builtin with the given argument types.
StyledText CallSignature(std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args) {
StyledText out;
out << style::Code << style::Function(intrinsic_name);
if (!template_args.IsEmpty()) {
out << "<";
PrintTypeList(out, template_args);
out << ">";
}
out << "(";
PrintTypeList(out, args);
out << ")";
return out;
}
Result<Overload, StyledText> MatchIntrinsic(Context& context,
const IntrinsicInfo& intrinsic,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args,
EvaluationStage earliest_eval_stage,
const OnNoMatch& on_no_match) {
const size_t num_overloads = static_cast<size_t>(intrinsic.num_overloads);
size_t num_matched = 0;
size_t match_idx = 0;
Vector<Candidate, kNumFixedCandidates> candidates;
candidates.Reserve(intrinsic.num_overloads);
for (size_t overload_idx = 0; overload_idx < num_overloads; overload_idx++) {
auto& overload = context.data[intrinsic.overloads + overload_idx];
auto candidate = ScoreOverload<ScoreMode::kEarlyReject>(context, overload, template_args,
args, earliest_eval_stage);
if (candidate.score == 0) {
match_idx = overload_idx;
num_matched++;
}
candidates.Push(std::move(candidate));
}
// How many candidates matched?
if (TINT_UNLIKELY(num_matched == 0)) {
// Perform the full scoring of each overload
for (size_t overload_idx = 0; overload_idx < num_overloads; overload_idx++) {
auto& overload = context.data[intrinsic.overloads + overload_idx];
candidates[overload_idx] = ScoreOverload<ScoreMode::kFull>(
context, overload, template_args, args, earliest_eval_stage);
}
// Sort the candidates with the most promising first
SortCandidates(candidates);
return on_no_match(std::move(candidates));
}
Candidate match;
if (num_matched == 1) {
match = std::move(candidates[match_idx]);
} else {
auto result =
ResolveCandidate(context, std::move(candidates), intrinsic_name, template_args, args);
if (TINT_UNLIKELY(result != Success)) {
return result.Failure();
}
match = result.Get();
}
// Build the return type
const core::type::Type* return_type = nullptr;
if (auto* matcher_indices = context.data[match.overload->return_matcher_indices]) {
Any any;
return_type =
context.Match(match.templates, *match.overload, matcher_indices, earliest_eval_stage)
.Type(&any);
if (TINT_UNLIKELY(!return_type)) {
StyledText err;
err << "MatchState.MatchState() returned null";
TINT_ICE() << err.Plain();
}
} else {
return_type = context.types.void_();
}
return Overload{match.overload, return_type, std::move(match.parameters),
context.data[match.overload->const_eval_fn]};
}
template <ScoreMode MODE>
Candidate ScoreOverload(Context& context,
const OverloadInfo& overload,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args,
EvaluationStage earliest_eval_stage) {
#define MATCH_FAILURE(PENALTY) \
do { \
if constexpr (MODE == ScoreMode::kEarlyReject) { \
return Candidate{1}; \
} else { \
score += PENALTY; \
} \
} while (false)
// Penalty weights for overload mismatching.
// This scoring is used to order the suggested overloads in diagnostic on overload mismatch, and
// has no impact for a correct program.
// The overloads with the lowest score will be displayed first (top-most).
constexpr int kMismatchedExplicitTemplateCountPenalty = 10;
constexpr int kMismatchedParamCountPenalty = 3;
constexpr int kMismatchedParamTypePenalty = 2;
constexpr int kMismatchedExplicitTemplateTypePenalty = 1;
constexpr int kMismatchedImplicitTemplateTypePenalty = 1;
constexpr int kMismatchedImplicitTemplateNumberPenalty = 1;
const size_t num_parameters = static_cast<size_t>(overload.num_parameters);
const size_t num_arguments = static_cast<size_t>(args.Length());
size_t score = 0;
if (num_parameters != num_arguments) {
MATCH_FAILURE(kMismatchedParamCountPenalty * (std::max(num_parameters, num_arguments) -
std::min(num_parameters, num_arguments)));
}
// Check that all of the template arguments provided are actually expected by the overload.
const size_t expected_templates = overload.num_explicit_templates;
const size_t provided_templates = template_args.Length();
if (provided_templates != expected_templates) {
MATCH_FAILURE(kMismatchedExplicitTemplateCountPenalty *
(std::max(expected_templates, provided_templates) -
std::min(expected_templates, provided_templates)));
}
TemplateState templates;
// Check that the explicit template arguments match the constraint if specified, otherwise
// just set the template type.
auto num_tmpl_args = std::min<size_t>(overload.num_explicit_templates, template_args.Length());
for (size_t i = 0; i < num_tmpl_args; ++i) {
auto& tmpl = context.data[overload.templates + i];
auto* type = template_args[i];
if (auto* matcher_indices = context.data[tmpl.matcher_indices]) {
// Ensure type matches the template's matcher.
type =
context.Match(templates, overload, matcher_indices, earliest_eval_stage).Type(type);
if (!type) {
MATCH_FAILURE(kMismatchedExplicitTemplateTypePenalty);
continue;
}
}
templates.SetType(i, type);
}
// Invoke the matchers for each parameter <-> argument pair.
// If any arguments cannot be matched, then `score` will be increased.
// If the overload has any template types or numbers then these will be set based on the
// argument types. Template types may be refined by constraining with later argument types. For
// example calling `F<T>(T, T)` with the argument types (abstract-int, i32) will first set T to
// abstract-int when matching the first argument, and then constrained down to i32 when matching
// the second argument.
// Note that inferred template types are not tested against their matchers at this point.
auto num_params = std::min(num_parameters, num_arguments);
for (size_t p = 0; p < num_params; p++) {
auto& parameter = context.data[overload.parameters + p];
auto* matcher_indices = context.data[parameter.matcher_indices];
if (!context.Match(templates, overload, matcher_indices, earliest_eval_stage)
.Type(args[p])) {
MATCH_FAILURE(kMismatchedParamTypePenalty);
}
}
// Check each of the inferred types and numbers for the implicit templates match their
// respective matcher.
for (size_t i = overload.num_explicit_templates; i < overload.num_templates; i++) {
auto& tmpl = context.data[overload.templates + i];
auto* matcher_indices = context.data[tmpl.matcher_indices];
if (!matcher_indices) {
continue;
}
auto matcher = context.Match(templates, overload, matcher_indices, earliest_eval_stage);
switch (tmpl.kind) {
case TemplateInfo::Kind::kType: {
// Check all constrained template types matched their constraint matchers.
// If the template type *does not* match any of the types in the constraint
// matcher, then `score` is incremented and the template is assigned an invalid
// type. If the template type *does* match a type, then the template type is
// replaced with the first matching type. The order of types in the template matcher
// is important here, which can be controlled with the [[precedence(N)]] decorations
// on the types in the def file.
if (auto* type = templates.Type(i)) {
if (auto* ty = matcher.Type(type)) {
// Template type matched one of the types in the template type's
// matcher. Replace the template type with this type.
templates.SetType(i, ty);
continue;
}
}
templates.SetType(i, context.types.invalid());
MATCH_FAILURE(kMismatchedImplicitTemplateTypePenalty);
break;
}
case TemplateInfo::Kind::kNumber: {
// Checking that the inferred number matches the constraints on the
// template. Increments `score` and assigns the template an invalid number if the
// template numbers do not match their constraint matchers.
auto number = templates.Num(i);
if (!number.IsValid() || !matcher.Num(number).IsValid()) {
templates.SetNum(i, Number::invalid);
MATCH_FAILURE(kMismatchedImplicitTemplateNumberPenalty);
}
}
}
}
// Now that all the template types have been finalized, we can construct the parameters.
Vector<Overload::Parameter, kNumFixedParameters> parameters;
parameters.Reserve(num_params);
for (size_t p = 0; p < num_params; p++) {
auto& parameter = context.data[overload.parameters + p];
auto* matcher_indices = context.data[parameter.matcher_indices];
auto* ty =
context.Match(templates, overload, matcher_indices, earliest_eval_stage).Type(args[p]);
parameters.Emplace(ty, parameter.usage);
}
return Candidate{score, &overload, templates, parameters};
#undef MATCH_FAILURE
}
Result<Candidate, StyledText> ResolveCandidate(Context& context,
Candidates&& candidates,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args) {
Vector<uint32_t, kNumFixedParameters> best_ranks;
best_ranks.Resize(args.Length(), 0xffffffff);
size_t num_matched = 0;
Candidate* best = nullptr;
for (auto& candidate : candidates) {
if (candidate.score > 0) {
continue; // Candidate has already been ruled out.
}
bool some_won = false; // An argument ranked less than the 'best' overload's argument
bool some_lost = false; // An argument ranked more than the 'best' overload's argument
for (size_t i = 0; i < args.Length(); i++) {
auto rank = core::type::Type::ConversionRank(args[i], candidate.parameters[i].type);
if (best_ranks[i] > rank) {
best_ranks[i] = rank;
some_won = true;
} else if (best_ranks[i] < rank) {
some_lost = true;
}
}
// If no arguments of this candidate ranked worse than the previous best candidate, then
// this candidate becomes the new best candidate.
// If no arguments of this candidate ranked better than the previous best candidate, then
// this candidate is removed from the list of matches.
// If neither of the above apply, then we have two candidates with no clear winner, which
// results in an ambiguous overload error. In this situation the loop ends with
// `num_matched > 1`.
if (some_won) {
// One or more arguments of this candidate ranked better than the previous best
// candidate's argument(s).
num_matched++;
if (!some_lost) {
// All arguments were at as-good or better than the previous best.
if (best) {
// Mark the previous best candidate as no longer being in the running, by
// setting its score to a non-zero value. We pick 1 as this is the closest to 0
// (match) as we can get.
best->score = 1;
num_matched--;
}
// This candidate is the new best.
best = &candidate;
}
} else {
// No arguments ranked better than the current best.
// Change the score of this candidate to a non-zero value, so that it's not considered a
// match.
candidate.score = 1;
}
}
if (num_matched > 1) {
// Re-sort the candidates with the most promising first
SortCandidates(candidates);
// Raise an error
return ErrAmbiguousOverload(context, intrinsic_name, template_args, args, candidates);
}
return std::move(*best);
}
void PrintCandidates(StyledText& ss,
Context& context,
VectorRef<Candidate> candidates,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args) {
for (auto& candidate : candidates) {
ss << " • ";
PrintCandidate(ss, context, candidate, intrinsic_name, template_args, args);
ss << "\n";
}
}
StyledText ErrAmbiguousOverload(Context& context,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args,
VectorRef<Candidate> candidates) {
StyledText err;
err << "ambiguous overload while attempting to match "
<< CallSignature(intrinsic_name, template_args, args) << "\n";
for (auto& candidate : candidates) {
if (candidate.score == 0) {
err << " ";
PrintCandidate(err, context, candidate, intrinsic_name, template_args, args);
err << "\n";
}
}
TINT_ICE() << err.Plain();
}
} // namespace
void PrintCandidate(StyledText& ss,
Context& context,
const Candidate& candidate,
std::string_view intrinsic_name,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args) {
// Restore old style before returning.
auto prev_style = ss.Style();
TINT_DEFER(ss << prev_style);
auto& overload = *candidate.overload;
TemplateState templates = candidate.templates;
// TODO(crbug.com/tint/1730): Use input evaluation stage to output only relevant overloads.
auto earliest_eval_stage = EvaluationStage::kConstant;
ss << style::Code << style::Function(intrinsic_name);
if (overload.num_explicit_templates > 0) {
ss << "<";
for (size_t i = 0; i < overload.num_explicit_templates; i++) {
const auto& tmpl = context.data[overload.templates + i];
bool matched = false;
if (i < template_args.Length()) {
auto* matcher_indices = context.data[tmpl.matcher_indices];
matched = !matcher_indices ||
context.Match(templates, overload, matcher_indices, earliest_eval_stage)
.Type(template_args[i]);
}
if (i > 0) {
ss << ", ";
}
ss << style::Type(tmpl.name) << " ";
if (matched) {
ss << (style::Code + style::Match)(" ✓ ");
} else {
ss << (style::Code + style::Mismatch)(" ✗ ");
}
}
ss << ">";
}
bool all_params_match = true;
ss << "(";
for (size_t i = 0; i < overload.num_parameters; i++) {
const auto& parameter = context.data[overload.parameters + i];
auto* matcher_indices = context.data[parameter.matcher_indices];
bool matched = false;
if (i < args.Length()) {
matched = context.Match(templates, overload, matcher_indices, earliest_eval_stage)
.Type(args[i]);
}
all_params_match = all_params_match && matched;
if (i > 0) {
ss << ", ";
}
if (parameter.usage != ParameterUsage::kNone) {
ss << style::Variable(parameter.usage, ": ");
}
context.Match(templates, overload, matcher_indices, earliest_eval_stage).PrintType(ss);
ss << style::Code << " ";
if (matched) {
ss << (style::Code + style::Match)(" ✓ ");
} else {
ss << (style::Code + style::Mismatch)(" ✗ ");
}
}
ss << ")";
if (overload.return_matcher_indices.IsValid()) {
ss << " -> ";
auto* matcher_indices = context.data[overload.return_matcher_indices];
context.Match(templates, overload, matcher_indices, earliest_eval_stage).PrintType(ss);
}
bool first = true;
auto separator = [&] {
ss << style::Plain(first ? " where:\n " : "\n ");
first = false;
};
if (all_params_match && args.Length() > overload.num_parameters) {
separator();
ss << style::Mismatch(" ✗ ")
<< style::Plain(" overload expects ", static_cast<int>(overload.num_parameters),
" argument", overload.num_parameters != 1 ? "s" : "", ", call passed ",
args.Length(), " argument", args.Length() != 1 ? "s" : "");
}
if (all_params_match && template_args.Length() > overload.num_explicit_templates) {
separator();
ss << style::Mismatch(" ✗ ")
<< style::Plain(" overload expects ", static_cast<int>(overload.num_explicit_templates),
" template argument", overload.num_explicit_templates != 1 ? "s" : "",
", call passed ", template_args.Length(), " argument",
template_args.Length() != 1 ? "s" : "");
}
for (size_t i = 0; i < overload.num_templates; i++) {
auto& tmpl = context.data[overload.templates + i];
if (auto* matcher_indices = context.data[tmpl.matcher_indices]) {
separator();
bool matched = false;
if (tmpl.kind == TemplateInfo::Kind::kType) {
if (auto* ty = templates.Type(i)) {
matched =
context.Match(templates, overload, matcher_indices, earliest_eval_stage)
.Type(ty);
}
} else {
matched = context.Match(templates, overload, matcher_indices, earliest_eval_stage)
.Num(templates.Num(i))
.IsValid();
}
if (matched) {
ss << style::Match(" ✓ ") << style::Plain(" ");
} else {
ss << style::Mismatch(" ✗ ") << style::Plain(" ");
}
ss << style::Type(tmpl.name) << style::Plain(" is ");
if (tmpl.kind == TemplateInfo::Kind::kType) {
context.Match(templates, overload, matcher_indices, earliest_eval_stage)
.PrintType(ss);
} else {
context.Match(templates, overload, matcher_indices, earliest_eval_stage)
.PrintNum(ss);
}
}
}
}
Result<Overload, StyledText> LookupFn(Context& context,
std::string_view intrinsic_name,
size_t function_id,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args,
EvaluationStage earliest_eval_stage) {
// Generates an error when no overloads match the provided arguments
auto on_no_match = [&](VectorRef<Candidate> candidates) {
StyledText err;
err << "no matching call to " << CallSignature(intrinsic_name, template_args, args) << "\n";
if (!candidates.IsEmpty()) {
err << "\n"
<< candidates.Length() << " candidate function"
<< (candidates.Length() > 1 ? "s:" : ":") << "\n";
PrintCandidates(err, context, candidates, intrinsic_name, template_args, args);
}
return err;
};
// Resolve the intrinsic overload
return MatchIntrinsic(context, context.data.builtins[function_id], intrinsic_name,
template_args, args, earliest_eval_stage, on_no_match);
}
Result<Overload, StyledText> LookupUnary(Context& context,
core::UnaryOp op,
const core::type::Type* arg,
EvaluationStage earliest_eval_stage) {
const IntrinsicInfo* intrinsic_info = nullptr;
std::string_view intrinsic_name;
switch (op) {
case core::UnaryOp::kComplement:
intrinsic_info = &context.data.unary_complement;
intrinsic_name = "operator ~ ";
break;
case core::UnaryOp::kNegation:
intrinsic_info = &context.data.unary_minus;
intrinsic_name = "operator - ";
break;
case core::UnaryOp::kAddressOf:
intrinsic_info = &context.data.unary_and;
intrinsic_name = "operator & ";
break;
case core::UnaryOp::kIndirection:
intrinsic_info = &context.data.unary_star;
intrinsic_name = "operator * ";
break;
case core::UnaryOp::kNot:
intrinsic_info = &context.data.unary_not;
intrinsic_name = "operator ! ";
break;
}
Vector args{arg};
// Generates an error when no overloads match the provided arguments
auto on_no_match = [&, name = intrinsic_name](VectorRef<Candidate> candidates) {
StyledText err;
err << "no matching overload for " << CallSignature(name, Empty, args) << "\n";
if (!candidates.IsEmpty()) {
err << "\n"
<< candidates.Length() << " candidate operator"
<< (candidates.Length() > 1 ? "s:" : ":") << "\n";
PrintCandidates(err, context, candidates, name, Empty, Vector{arg});
}
return err;
};
// Resolve the intrinsic overload
return MatchIntrinsic(context, *intrinsic_info, intrinsic_name, Empty, args,
earliest_eval_stage, on_no_match);
}
Result<Overload, StyledText> LookupBinary(Context& context,
core::BinaryOp op,
const core::type::Type* lhs,
const core::type::Type* rhs,
EvaluationStage earliest_eval_stage,
bool is_compound) {
const IntrinsicInfo* intrinsic_info = nullptr;
std::string_view intrinsic_name;
switch (op) {
case core::BinaryOp::kAnd:
intrinsic_info = &context.data.binary_and;
intrinsic_name = is_compound ? "operator &= " : "operator & ";
break;
case core::BinaryOp::kOr:
intrinsic_info = &context.data.binary_or;
intrinsic_name = is_compound ? "operator |= " : "operator | ";
break;
case core::BinaryOp::kXor:
intrinsic_info = &context.data.binary_xor;
intrinsic_name = is_compound ? "operator ^= " : "operator ^ ";
break;
case core::BinaryOp::kLogicalAnd:
intrinsic_info = &context.data.binary_logical_and;
intrinsic_name = "operator && ";
break;
case core::BinaryOp::kLogicalOr:
intrinsic_info = &context.data.binary_logical_or;
intrinsic_name = "operator || ";
break;
case core::BinaryOp::kEqual:
intrinsic_info = &context.data.binary_equal;
intrinsic_name = "operator == ";
break;
case core::BinaryOp::kNotEqual:
intrinsic_info = &context.data.binary_not_equal;
intrinsic_name = "operator != ";
break;
case core::BinaryOp::kLessThan:
intrinsic_info = &context.data.binary_less_than;
intrinsic_name = "operator < ";
break;
case core::BinaryOp::kGreaterThan:
intrinsic_info = &context.data.binary_greater_than;
intrinsic_name = "operator > ";
break;
case core::BinaryOp::kLessThanEqual:
intrinsic_info = &context.data.binary_less_than_equal;
intrinsic_name = "operator <= ";
break;
case core::BinaryOp::kGreaterThanEqual:
intrinsic_info = &context.data.binary_greater_than_equal;
intrinsic_name = "operator >= ";
break;
case core::BinaryOp::kShiftLeft:
intrinsic_info = &context.data.binary_shift_left;
intrinsic_name = is_compound ? "operator <<= " : "operator << ";
break;
case core::BinaryOp::kShiftRight:
intrinsic_info = &context.data.binary_shift_right;
intrinsic_name = is_compound ? "operator >>= " : "operator >> ";
break;
case core::BinaryOp::kAdd:
intrinsic_info = &context.data.binary_plus;
intrinsic_name = is_compound ? "operator += " : "operator + ";
break;
case core::BinaryOp::kSubtract:
intrinsic_info = &context.data.binary_minus;
intrinsic_name = is_compound ? "operator -= " : "operator - ";
break;
case core::BinaryOp::kMultiply:
intrinsic_info = &context.data.binary_star;
intrinsic_name = is_compound ? "operator *= " : "operator * ";
break;
case core::BinaryOp::kDivide:
intrinsic_info = &context.data.binary_divide;
intrinsic_name = is_compound ? "operator /= " : "operator / ";
break;
case core::BinaryOp::kModulo:
intrinsic_info = &context.data.binary_modulo;
intrinsic_name = is_compound ? "operator %= " : "operator % ";
break;
}
Vector args{lhs, rhs};
// Generates an error when no overloads match the provided arguments
auto on_no_match = [&, name = intrinsic_name](VectorRef<Candidate> candidates) {
StyledText err;
err << "no matching overload for " << CallSignature(name, Empty, args) << "\n";
if (!candidates.IsEmpty()) {
err << "\n"
<< candidates.Length() << " candidate operator"
<< (candidates.Length() > 1 ? "s:" : ":") << "\n";
PrintCandidates(err, context, candidates, name, Empty, args);
}
return err;
};
// Resolve the intrinsic overload
return MatchIntrinsic(context, *intrinsic_info, intrinsic_name, Empty, args,
earliest_eval_stage, on_no_match);
}
Result<Overload, StyledText> LookupCtorConv(Context& context,
std::string_view type_name,
size_t type_id,
VectorRef<const core::type::Type*> template_args,
VectorRef<const core::type::Type*> args,
EvaluationStage earliest_eval_stage) {
// Generates an error when no overloads match the provided arguments
auto on_no_match = [&](VectorRef<Candidate> candidates) {
StyledText err;
err << "no matching constructor for " << CallSignature(type_name, template_args, args)
<< "\n";
Candidates ctor, conv;
for (auto candidate : candidates) {
if (candidate.overload->flags.Contains(OverloadFlag::kIsConstructor)) {
ctor.Push(candidate);
} else {
conv.Push(candidate);
}
}
if (!ctor.IsEmpty()) {
err << "\n"
<< ctor.Length() << " candidate constructor" << (ctor.Length() > 1 ? "s:" : ":")
<< "\n";
PrintCandidates(err, context, ctor, type_name, template_args, args);
}
if (!conv.IsEmpty()) {
err << "\n"
<< conv.Length() << " candidate conversion" << (conv.Length() > 1 ? "s:" : ":")
<< "\n";
PrintCandidates(err, context, conv, type_name, template_args, args);
}
return err;
};
// Resolve the intrinsic overload
return MatchIntrinsic(context, context.data.ctor_conv[type_id], type_name, template_args, args,
earliest_eval_stage, on_no_match);
}
} // namespace tint::core::intrinsic
/// TypeInfo for the Any type declared in the anonymous namespace above
TINT_INSTANTIATE_TYPEINFO(tint::core::intrinsic::Any);