[resolver]: Begin constant value evaluation
Move the bulk of the constant evaulation logic out of transform::FoldConstants and into Resolver and sem::Expression. transform::FoldConstants now replace TypeConstructor nodes that have a constant value on the expression. This is ground work to: * Cleaning up the HLSL uniform buffer indexing, which is `/` and `%` arithmatic heavy * Prepares us to handle `constexpr` when it lands in the spec * Provide a centralized place to do constant evaluation, instead of the having similar logic scattered around the codebase. Change-Id: I3e2f542be692046a8d243b62a82556db519953e7 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57426 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Antonio Maiorano <amaiorano@google.com> Reviewed-by: James Price <jrprice@google.com>
This commit is contained in:
parent
c6bcab02fd
commit
71f619b6f1
|
@ -493,6 +493,7 @@ libtint_source_set("libtint_core_all_src") {
|
|||
"reader/reader.cc",
|
||||
"reader/reader.h",
|
||||
"resolver/resolver.cc",
|
||||
"resolver/resolver_constants.cc",
|
||||
"resolver/resolver.h",
|
||||
"scope_stack.h",
|
||||
"sem/array.h",
|
||||
|
@ -503,6 +504,8 @@ libtint_source_set("libtint_core_all_src") {
|
|||
"sem/bool_type.h",
|
||||
"sem/call.h",
|
||||
"sem/call_target.h",
|
||||
"sem/constant.cc",
|
||||
"sem/constant.h",
|
||||
"sem/depth_texture_type.cc",
|
||||
"sem/depth_texture_type.h",
|
||||
"sem/expression.h",
|
||||
|
|
|
@ -240,6 +240,7 @@ set(TINT_LIB_SRCS
|
|||
reader/reader.cc
|
||||
reader/reader.h
|
||||
resolver/resolver.cc
|
||||
resolver/resolver_constants.cc
|
||||
resolver/resolver.h
|
||||
scope_stack.h
|
||||
sem/array.cc
|
||||
|
@ -253,6 +254,8 @@ set(TINT_LIB_SRCS
|
|||
sem/call_target.h
|
||||
sem/call.cc
|
||||
sem/call.h
|
||||
sem/constant.cc
|
||||
sem/constant.h
|
||||
sem/expression.cc
|
||||
sem/expression.h
|
||||
sem/function.cc
|
||||
|
@ -633,6 +636,7 @@ if(${TINT_BUILD_TESTS})
|
|||
resolver/is_storeable_test.cc
|
||||
resolver/ptr_ref_test.cc
|
||||
resolver/ptr_ref_validation_test.cc
|
||||
resolver/resolver_constants_test.cc
|
||||
resolver/pipeline_overridable_constant_test.cc
|
||||
resolver/resolver_test_helper.cc
|
||||
resolver/resolver_test_helper.h
|
||||
|
|
|
@ -30,6 +30,7 @@ Matrix::Matrix(ProgramID program_id,
|
|||
subtype_(subtype),
|
||||
rows_(rows),
|
||||
columns_(columns) {
|
||||
TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
|
||||
TINT_ASSERT(AST, rows > 1);
|
||||
TINT_ASSERT(AST, rows < 5);
|
||||
TINT_ASSERT(AST, columns > 1);
|
||||
|
|
|
@ -26,6 +26,7 @@ Vector::Vector(ProgramID program_id,
|
|||
Type const* subtype,
|
||||
uint32_t size)
|
||||
: Base(program_id, source), subtype_(subtype), size_(size) {
|
||||
TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
|
||||
TINT_ASSERT(AST, size_ > 1);
|
||||
TINT_ASSERT(AST, size_ < 5);
|
||||
}
|
||||
|
|
|
@ -2067,7 +2067,7 @@ bool Resolver::ArrayAccessor(ast::ArrayAccessorExpression* expr) {
|
|||
ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
|
||||
ref->Access());
|
||||
}
|
||||
SetType(expr, ret);
|
||||
SetExprInfo(expr, ret);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2085,7 +2085,7 @@ bool Resolver::Bitcast(ast::BitcastExpression* expr) {
|
|||
AddError("cannot cast to a pointer", expr->source());
|
||||
return false;
|
||||
}
|
||||
SetType(expr, ty, expr->type()->FriendlyName(builder_->Symbols()));
|
||||
SetExprInfo(expr, ty, expr->type()->FriendlyName(builder_->Symbols()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2167,7 +2167,7 @@ bool Resolver::IntrinsicCall(ast::CallExpression* call,
|
|||
|
||||
builder_->Sem().Add(
|
||||
call, builder_->create<sem::Call>(call, result, current_statement_));
|
||||
SetType(call, result->ReturnType());
|
||||
SetExprInfo(call, result->ReturnType());
|
||||
|
||||
current_function_->intrinsic_calls.emplace_back(
|
||||
IntrinsicCallInfo{call, result});
|
||||
|
@ -2239,7 +2239,7 @@ bool Resolver::FunctionCall(const ast::CallExpression* call) {
|
|||
|
||||
function_calls_.emplace(call,
|
||||
FunctionCallInfo{callee_func, current_statement_});
|
||||
SetType(call, callee_func->return_type, callee_func->return_type_name);
|
||||
SetExprInfo(call, callee_func->return_type, callee_func->return_type_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2258,7 +2258,7 @@ bool Resolver::Constructor(ast::ConstructorExpression* expr) {
|
|||
return false;
|
||||
}
|
||||
|
||||
SetType(expr, type, type_ctor->type()->FriendlyName(builder_->Symbols()));
|
||||
auto type_name = type_ctor->type()->FriendlyName(builder_->Symbols());
|
||||
|
||||
// Now that the argument types have been determined, make sure that they
|
||||
// obey the constructor type rules laid out in
|
||||
|
@ -2267,35 +2267,40 @@ bool Resolver::Constructor(ast::ConstructorExpression* expr) {
|
|||
AddError("cannot cast to a pointer", expr->source());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
if (auto* vec_type = type->As<sem::Vector>()) {
|
||||
return ValidateVectorConstructor(type_ctor, vec_type);
|
||||
ok = ValidateVectorConstructor(type_ctor, vec_type, type_name);
|
||||
} else if (auto* mat_type = type->As<sem::Matrix>()) {
|
||||
ok = ValidateMatrixConstructor(type_ctor, mat_type, type_name);
|
||||
} else if (type->is_scalar()) {
|
||||
ok = ValidateScalarConstructor(type_ctor, type, type_name);
|
||||
} else if (auto* arr_type = type->As<sem::Array>()) {
|
||||
ok = ValidateArrayConstructor(type_ctor, arr_type);
|
||||
} else if (auto* struct_type = type->As<sem::Struct>()) {
|
||||
ok = ValidateStructureConstructor(type_ctor, struct_type);
|
||||
}
|
||||
if (auto* mat_type = type->As<sem::Matrix>()) {
|
||||
return ValidateMatrixConstructor(type_ctor, mat_type);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
if (type->is_scalar()) {
|
||||
return ValidateScalarConstructor(type_ctor, type);
|
||||
SetExprInfo(expr, type, type_name);
|
||||
return true;
|
||||
}
|
||||
if (auto* arr_type = type->As<sem::Array>()) {
|
||||
return ValidateArrayConstructor(type_ctor, arr_type);
|
||||
}
|
||||
if (auto* struct_type = type->As<sem::Struct>()) {
|
||||
return ValidateStructureConstructor(type_ctor, struct_type);
|
||||
}
|
||||
} else if (auto* scalar_ctor = expr->As<ast::ScalarConstructorExpression>()) {
|
||||
|
||||
if (auto* scalar_ctor = expr->As<ast::ScalarConstructorExpression>()) {
|
||||
Mark(scalar_ctor->literal());
|
||||
auto* type = TypeOf(scalar_ctor->literal());
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
SetType(expr, type);
|
||||
} else {
|
||||
TINT_ICE(Resolver, diagnostics_)
|
||||
<< "unexpected constructor expression type";
|
||||
}
|
||||
SetExprInfo(expr, type);
|
||||
return true;
|
||||
}
|
||||
|
||||
TINT_ICE(Resolver, diagnostics_) << "unexpected constructor expression type";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Resolver::ValidateStructureConstructor(
|
||||
const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Struct* struct_type) {
|
||||
|
@ -2366,7 +2371,8 @@ bool Resolver::ValidateArrayConstructor(
|
|||
|
||||
bool Resolver::ValidateVectorConstructor(
|
||||
const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Vector* vec_type) {
|
||||
const sem::Vector* vec_type,
|
||||
const std::string& type_name) {
|
||||
auto& values = ctor->values();
|
||||
auto* elem_type = vec_type->type();
|
||||
size_t value_cardinality_sum = 0;
|
||||
|
@ -2423,7 +2429,7 @@ bool Resolver::ValidateVectorConstructor(
|
|||
}
|
||||
const Source& values_start = values[0]->source();
|
||||
const Source& values_end = values[values.size() - 1]->source();
|
||||
AddError("attempted to construct '" + TypeNameOf(ctor) + "' with " +
|
||||
AddError("attempted to construct '" + type_name + "' with " +
|
||||
std::to_string(value_cardinality_sum) + " component(s)",
|
||||
Source::Combine(values_start, values_end));
|
||||
return false;
|
||||
|
@ -2450,7 +2456,8 @@ bool Resolver::ValidateMatrix(const sem::Matrix* ty, const Source& source) {
|
|||
|
||||
bool Resolver::ValidateMatrixConstructor(
|
||||
const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Matrix* matrix_type) {
|
||||
const sem::Matrix* matrix_type,
|
||||
const std::string& type_name) {
|
||||
auto& values = ctor->values();
|
||||
// Zero Value expression
|
||||
if (values.empty()) {
|
||||
|
@ -2467,8 +2474,8 @@ bool Resolver::ValidateMatrixConstructor(
|
|||
const Source& values_end = values[values.size() - 1]->source();
|
||||
AddError("expected " + std::to_string(matrix_type->columns()) + " '" +
|
||||
VectorPretty(matrix_type->rows(), elem_type) +
|
||||
"' arguments in '" + TypeNameOf(ctor) +
|
||||
"' constructor, found " + std::to_string(values.size()),
|
||||
"' arguments in '" + type_name + "' constructor, found " +
|
||||
std::to_string(values.size()),
|
||||
Source::Combine(values_start, values_end));
|
||||
return false;
|
||||
}
|
||||
|
@ -2481,8 +2488,8 @@ bool Resolver::ValidateMatrixConstructor(
|
|||
elem_type != value_vec->type()) {
|
||||
AddError("expected argument type '" +
|
||||
VectorPretty(matrix_type->rows(), elem_type) + "' in '" +
|
||||
TypeNameOf(ctor) + "' constructor, found '" +
|
||||
TypeNameOf(value) + "'",
|
||||
type_name + "' constructor, found '" + TypeNameOf(value) +
|
||||
"'",
|
||||
value->source());
|
||||
return false;
|
||||
}
|
||||
|
@ -2493,7 +2500,8 @@ bool Resolver::ValidateMatrixConstructor(
|
|||
|
||||
bool Resolver::ValidateScalarConstructor(
|
||||
const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Type* type) {
|
||||
const sem::Type* type,
|
||||
const std::string& type_name) {
|
||||
if (ctor->values().size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
@ -2519,8 +2527,8 @@ bool Resolver::ValidateScalarConstructor(
|
|||
(type->Is<U32>() && value_type->IsAnyOf<I32, U32, F32>()) ||
|
||||
(type->Is<F32>() && value_type->IsAnyOf<I32, U32, F32>());
|
||||
if (!is_valid) {
|
||||
AddError("cannot construct '" + TypeNameOf(ctor) +
|
||||
"' with a value of type '" + TypeNameOf(value) + "'",
|
||||
AddError("cannot construct '" + type_name + "' with a value of type '" +
|
||||
TypeNameOf(value) + "'",
|
||||
ctor->source());
|
||||
|
||||
return false;
|
||||
|
@ -2533,7 +2541,7 @@ bool Resolver::Identifier(ast::IdentifierExpression* expr) {
|
|||
auto symbol = expr->symbol();
|
||||
VariableInfo* var;
|
||||
if (variable_stack_.get(symbol, &var)) {
|
||||
SetType(expr, var->type, var->type_name);
|
||||
SetExprInfo(expr, var->type, var->type_name);
|
||||
|
||||
var->users.push_back(expr);
|
||||
set_referenced_from_function_if_needed(var, true);
|
||||
|
@ -2707,7 +2715,7 @@ bool Resolver::MemberAccessor(ast::MemberAccessorExpression* expr) {
|
|||
return false;
|
||||
}
|
||||
|
||||
SetType(expr, ret);
|
||||
SetExprInfo(expr, ret);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2745,17 +2753,17 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
// Binary logical expressions
|
||||
if (expr->IsLogicalAnd() || expr->IsLogicalOr()) {
|
||||
if (matching_types && lhs_type->Is<Bool>()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (expr->IsOr() || expr->IsAnd()) {
|
||||
if (matching_types && lhs_type->Is<Bool>()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
if (matching_types && lhs_vec_elem_type && lhs_vec_elem_type->Is<Bool>()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2764,14 +2772,14 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
if (expr->IsArithmetic()) {
|
||||
// Binary arithmetic expressions over scalars
|
||||
if (matching_types && lhs_type->is_numeric_scalar()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Binary arithmetic expressions over vectors
|
||||
if (matching_types && lhs_vec_elem_type &&
|
||||
lhs_vec_elem_type->is_numeric_scalar()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2779,22 +2787,22 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
if (lhs_vec_elem_type && (lhs_vec_elem_type == rhs_type)) {
|
||||
if (expr->IsModulo()) {
|
||||
if (rhs_type->is_integer_scalar()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
} else if (rhs_type->is_numeric_scalar()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (rhs_vec_elem_type && (rhs_vec_elem_type == lhs_type)) {
|
||||
if (expr->IsModulo()) {
|
||||
if (lhs_type->is_integer_scalar()) {
|
||||
SetType(expr, rhs_type);
|
||||
SetExprInfo(expr, rhs_type);
|
||||
return true;
|
||||
}
|
||||
} else if (lhs_type->is_numeric_scalar()) {
|
||||
SetType(expr, rhs_type);
|
||||
SetExprInfo(expr, rhs_type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2811,19 +2819,19 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
rhs_mat_elem_type->Is<F32>() &&
|
||||
(lhs_mat->columns() == rhs_mat->columns()) &&
|
||||
(lhs_mat->rows() == rhs_mat->rows())) {
|
||||
SetType(expr, rhs_type);
|
||||
SetExprInfo(expr, rhs_type);
|
||||
return true;
|
||||
}
|
||||
if (expr->IsMultiply()) {
|
||||
// Multiplication of a matrix and a scalar
|
||||
if (lhs_type->Is<F32>() && rhs_mat_elem_type &&
|
||||
rhs_mat_elem_type->Is<F32>()) {
|
||||
SetType(expr, rhs_type);
|
||||
SetExprInfo(expr, rhs_type);
|
||||
return true;
|
||||
}
|
||||
if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
|
||||
rhs_type->Is<F32>()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2831,7 +2839,7 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
if (lhs_vec_elem_type && lhs_vec_elem_type->Is<F32>() &&
|
||||
rhs_mat_elem_type && rhs_mat_elem_type->Is<F32>() &&
|
||||
(lhs_vec->size() == rhs_mat->rows())) {
|
||||
SetType(expr, builder_->create<sem::Vector>(lhs_vec->type(),
|
||||
SetExprInfo(expr, builder_->create<sem::Vector>(lhs_vec->type(),
|
||||
rhs_mat->columns()));
|
||||
return true;
|
||||
}
|
||||
|
@ -2840,8 +2848,8 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
|
||||
rhs_vec_elem_type && rhs_vec_elem_type->Is<F32>() &&
|
||||
(lhs_mat->columns() == rhs_vec->size())) {
|
||||
SetType(expr,
|
||||
builder_->create<sem::Vector>(rhs_vec->type(), lhs_mat->rows()));
|
||||
SetExprInfo(expr, builder_->create<sem::Vector>(rhs_vec->type(),
|
||||
lhs_mat->rows()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2849,7 +2857,7 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
|
||||
rhs_mat_elem_type && rhs_mat_elem_type->Is<F32>() &&
|
||||
(lhs_mat->columns() == rhs_mat->rows())) {
|
||||
SetType(expr, builder_->create<sem::Matrix>(
|
||||
SetExprInfo(expr, builder_->create<sem::Matrix>(
|
||||
builder_->create<sem::Vector>(lhs_mat_elem_type,
|
||||
lhs_mat->rows()),
|
||||
rhs_mat->columns()));
|
||||
|
@ -2862,13 +2870,13 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
if (matching_types) {
|
||||
// Special case for bools: only == and !=
|
||||
if (lhs_type->Is<Bool>() && (expr->IsEqual() || expr->IsNotEqual())) {
|
||||
SetType(expr, builder_->create<sem::Bool>());
|
||||
SetExprInfo(expr, builder_->create<sem::Bool>());
|
||||
return true;
|
||||
}
|
||||
|
||||
// For the rest, we can compare i32, u32, and f32
|
||||
if (lhs_type->IsAnyOf<I32, U32, F32>()) {
|
||||
SetType(expr, builder_->create<sem::Bool>());
|
||||
SetExprInfo(expr, builder_->create<sem::Bool>());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2877,13 +2885,13 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
if (matching_vec_elem_types) {
|
||||
if (lhs_vec_elem_type->Is<Bool>() &&
|
||||
(expr->IsEqual() || expr->IsNotEqual())) {
|
||||
SetType(expr, builder_->create<sem::Vector>(
|
||||
SetExprInfo(expr, builder_->create<sem::Vector>(
|
||||
builder_->create<sem::Bool>(), lhs_vec->size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhs_vec_elem_type->is_numeric_scalar()) {
|
||||
SetType(expr, builder_->create<sem::Vector>(
|
||||
SetExprInfo(expr, builder_->create<sem::Vector>(
|
||||
builder_->create<sem::Bool>(), lhs_vec->size()));
|
||||
return true;
|
||||
}
|
||||
|
@ -2893,7 +2901,7 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
// Binary bitwise operations
|
||||
if (expr->IsBitwise()) {
|
||||
if (matching_types && lhs_type->is_integer_scalar_or_vector()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2905,13 +2913,13 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
// logical depending on lhs type).
|
||||
|
||||
if (lhs_type->IsAnyOf<I32, U32>() && rhs_type->Is<U32>()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhs_vec_elem_type && lhs_vec_elem_type->IsAnyOf<I32, U32>() &&
|
||||
rhs_vec_elem_type && rhs_vec_elem_type->Is<U32>()) {
|
||||
SetType(expr, lhs_type);
|
||||
SetExprInfo(expr, lhs_type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -3002,7 +3010,7 @@ bool Resolver::UnaryOp(ast::UnaryOpExpression* unary) {
|
|||
break;
|
||||
}
|
||||
|
||||
SetType(unary, type);
|
||||
SetExprInfo(unary, type);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3131,18 +3139,20 @@ sem::Type* Resolver::TypeOf(const ast::Literal* lit) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void Resolver::SetType(const ast::Expression* expr, const sem::Type* type) {
|
||||
SetType(expr, type, type->FriendlyName(builder_->Symbols()));
|
||||
}
|
||||
|
||||
void Resolver::SetType(const ast::Expression* expr,
|
||||
void Resolver::SetExprInfo(const ast::Expression* expr,
|
||||
const sem::Type* type,
|
||||
const std::string& type_name) {
|
||||
std::string type_name) {
|
||||
if (expr_info_.count(expr)) {
|
||||
TINT_ICE(Resolver, diagnostics_)
|
||||
<< "SetType() called twice for the same expression";
|
||||
<< "SetExprInfo() called twice for the same expression";
|
||||
}
|
||||
expr_info_.emplace(expr, ExpressionInfo{type, type_name, current_statement_});
|
||||
if (type_name.empty()) {
|
||||
type_name = type->FriendlyName(builder_->Symbols());
|
||||
}
|
||||
auto constant_value = EvaluateConstantValue(expr, type);
|
||||
expr_info_.emplace(
|
||||
expr, ExpressionInfo{type, std::move(type_name), current_statement_,
|
||||
std::move(constant_value)});
|
||||
}
|
||||
|
||||
bool Resolver::ValidatePipelineStages() {
|
||||
|
@ -3313,10 +3323,11 @@ void Resolver::CreateSemanticNodes() const {
|
|||
// Create semantic node for the identifier expression if necessary
|
||||
auto* sem_expr = sem.Get(user);
|
||||
if (sem_expr == nullptr) {
|
||||
auto* type = expr_info_.at(user).type;
|
||||
auto* stmt = expr_info_.at(user).statement;
|
||||
auto* sem_user =
|
||||
builder_->create<sem::VariableUser>(user, type, stmt, sem_var);
|
||||
auto& expr_info = expr_info_.at(user);
|
||||
auto* type = expr_info.type;
|
||||
auto* stmt = expr_info.statement;
|
||||
auto* sem_user = builder_->create<sem::VariableUser>(
|
||||
user, type, stmt, sem_var, expr_info.constant_value);
|
||||
sem_var->AddUser(sem_user);
|
||||
sem.Add(user, sem_user);
|
||||
} else {
|
||||
|
@ -3372,9 +3383,9 @@ void Resolver::CreateSemanticNodes() const {
|
|||
// Expression has already been assigned a semantic node
|
||||
continue;
|
||||
}
|
||||
sem.Add(expr,
|
||||
builder_->create<sem::Expression>(
|
||||
const_cast<ast::Expression*>(expr), info.type, info.statement));
|
||||
sem.Add(expr, builder_->create<sem::Expression>(
|
||||
const_cast<ast::Expression*>(expr), info.type,
|
||||
info.statement, info.constant_value));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "src/scope_stack.h"
|
||||
#include "src/sem/binding_point.h"
|
||||
#include "src/sem/block_statement.h"
|
||||
#include "src/sem/constant.h"
|
||||
#include "src/sem/function.h"
|
||||
#include "src/sem/struct.h"
|
||||
#include "src/utils/unique_vector.h"
|
||||
|
@ -152,6 +153,7 @@ class Resolver {
|
|||
sem::Type const* type;
|
||||
std::string const type_name; // Declared type name
|
||||
sem::Statement* statement;
|
||||
sem::Constant constant_value;
|
||||
};
|
||||
|
||||
/// Structure holding semantic information about a call expression to an
|
||||
|
@ -282,8 +284,6 @@ class Resolver {
|
|||
bool ValidateInterpolateDecoration(const ast::InterpolateDecoration* deco,
|
||||
const sem::Type* storage_type);
|
||||
bool ValidateMatrix(const sem::Matrix* ty, const Source& source);
|
||||
bool ValidateMatrixConstructor(const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Matrix* matrix_type);
|
||||
bool ValidateFunctionParameter(const ast::Function* func,
|
||||
const VariableInfo* info);
|
||||
bool ValidateNoDuplicateDefinition(Symbol sym,
|
||||
|
@ -305,9 +305,14 @@ class Resolver {
|
|||
const std::string& rhs_type_name);
|
||||
bool ValidateVector(const sem::Vector* ty, const Source& source);
|
||||
bool ValidateVectorConstructor(const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Vector* vec_type);
|
||||
const sem::Vector* vec_type,
|
||||
const std::string& type_name);
|
||||
bool ValidateMatrixConstructor(const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Matrix* matrix_type,
|
||||
const std::string& type_name);
|
||||
bool ValidateScalarConstructor(const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Type* type);
|
||||
const sem::Type* type,
|
||||
const std::string& type_name);
|
||||
bool ValidateArrayConstructor(const ast::TypeConstructorExpression* ctor,
|
||||
const sem::Array* arr_type);
|
||||
bool ValidateTypeDecl(const ast::TypeDecl* named_type) const;
|
||||
|
@ -380,21 +385,14 @@ class Resolver {
|
|||
/// @param lit the literal
|
||||
sem::Type* TypeOf(const ast::Literal* lit);
|
||||
|
||||
/// Creates a sem::Expression node with the resolved type `type`, and
|
||||
/// assigns this semantic node to the expression `expr`.
|
||||
/// @param expr the expression
|
||||
/// @param type the resolved type
|
||||
void SetType(const ast::Expression* expr, const sem::Type* type);
|
||||
|
||||
/// Creates a sem::Expression node with the resolved type `type`, the declared
|
||||
/// type name `type_name` and assigns this semantic node to the expression
|
||||
/// `expr`.
|
||||
/// Records the semantic information for the expression node with the resolved
|
||||
/// type `type` and optional declared type name `type_name`.
|
||||
/// @param expr the expression
|
||||
/// @param type the resolved type
|
||||
/// @param type_name the declared type name
|
||||
void SetType(const ast::Expression* expr,
|
||||
void SetExprInfo(const ast::Expression* expr,
|
||||
const sem::Type* type,
|
||||
const std::string& type_name);
|
||||
std::string type_name = "");
|
||||
|
||||
/// Resolve the value of a scalar const_expr.
|
||||
/// @param expr the expression
|
||||
|
@ -435,6 +433,26 @@ class Resolver {
|
|||
FunctionInfo* to,
|
||||
CALLBACK&& callback) const;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// Constant value evaluation methods
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// @return the Constant value of the given Expression
|
||||
sem::Constant ConstantValueOf(const ast::Expression* expr);
|
||||
|
||||
/// Cast `Value` to `target_type`
|
||||
/// @return the casted value
|
||||
sem::Constant ConstantCast(const sem::Constant& value,
|
||||
const sem::Type* target_elem_type);
|
||||
|
||||
sem::Constant EvaluateConstantValue(const ast::Expression* expr,
|
||||
const sem::Type* type);
|
||||
sem::Constant EvaluateConstantValue(
|
||||
const ast::ScalarConstructorExpression* scalar_ctor,
|
||||
const sem::Type* type);
|
||||
sem::Constant EvaluateConstantValue(
|
||||
const ast::TypeConstructorExpression* type_ctor,
|
||||
const sem::Type* type);
|
||||
|
||||
ProgramBuilder* const builder_;
|
||||
diag::List& diagnostics_;
|
||||
std::unique_ptr<IntrinsicTable> const intrinsic_table_;
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/resolver/resolver.h"
|
||||
|
||||
#include "src/sem/constant.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using i32 = ProgramBuilder::i32;
|
||||
using u32 = ProgramBuilder::u32;
|
||||
using f32 = ProgramBuilder::f32;
|
||||
|
||||
} // namespace
|
||||
|
||||
sem::Constant Resolver::ConstantCast(const sem::Constant& value,
|
||||
const sem::Type* target_elem_type) {
|
||||
if (value.ElementType() == target_elem_type) {
|
||||
return value;
|
||||
}
|
||||
|
||||
sem::Constant::Scalars elems;
|
||||
for (size_t i = 0; i < value.Elements().size(); ++i) {
|
||||
if (target_elem_type->Is<sem::I32>()) {
|
||||
elems.emplace_back(
|
||||
value.WithScalarAt(i, [](auto&& s) { return static_cast<i32>(s); }));
|
||||
} else if (target_elem_type->Is<sem::U32>()) {
|
||||
elems.emplace_back(
|
||||
value.WithScalarAt(i, [](auto&& s) { return static_cast<u32>(s); }));
|
||||
} else if (target_elem_type->Is<sem::F32>()) {
|
||||
elems.emplace_back(
|
||||
value.WithScalarAt(i, [](auto&& s) { return static_cast<f32>(s); }));
|
||||
} else if (target_elem_type->Is<sem::Bool>()) {
|
||||
elems.emplace_back(
|
||||
value.WithScalarAt(i, [](auto&& s) { return static_cast<bool>(s); }));
|
||||
}
|
||||
}
|
||||
|
||||
auto* target_type =
|
||||
value.Type()->Is<sem::Vector>()
|
||||
? builder_->create<sem::Vector>(target_elem_type,
|
||||
static_cast<uint32_t>(elems.size()))
|
||||
: target_elem_type;
|
||||
|
||||
return sem::Constant(target_type, elems);
|
||||
}
|
||||
|
||||
sem::Constant Resolver::ConstantValueOf(const ast::Expression* expr) {
|
||||
auto it = expr_info_.find(expr);
|
||||
if (it != expr_info_.end()) {
|
||||
return it->second.constant_value;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
sem::Constant Resolver::EvaluateConstantValue(const ast::Expression* expr,
|
||||
const sem::Type* type) {
|
||||
if (auto* e = expr->As<ast::ScalarConstructorExpression>()) {
|
||||
return EvaluateConstantValue(e, type);
|
||||
}
|
||||
if (auto* e = expr->As<ast::TypeConstructorExpression>()) {
|
||||
return EvaluateConstantValue(e, type);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
sem::Constant Resolver::EvaluateConstantValue(
|
||||
const ast::ScalarConstructorExpression* scalar_ctor,
|
||||
const sem::Type* type) {
|
||||
auto* literal = scalar_ctor->literal();
|
||||
if (auto* lit = literal->As<ast::SintLiteral>()) {
|
||||
return {type, {lit->value_as_i32()}};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::UintLiteral>()) {
|
||||
return {type, {lit->value_as_u32()}};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::FloatLiteral>()) {
|
||||
return {type, {lit->value()}};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::BoolLiteral>()) {
|
||||
return {type, {lit->IsTrue()}};
|
||||
}
|
||||
TINT_UNREACHABLE(Resolver, builder_->Diagnostics());
|
||||
return {};
|
||||
}
|
||||
|
||||
sem::Constant Resolver::EvaluateConstantValue(
|
||||
const ast::TypeConstructorExpression* type_ctor,
|
||||
const sem::Type* type) {
|
||||
auto& ctor_values = type_ctor->values();
|
||||
auto* vec = type->As<sem::Vector>();
|
||||
|
||||
// For now, only fold scalars and vectors
|
||||
if (!type->is_scalar() && !vec) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* elem_type = vec ? vec->type() : type;
|
||||
int result_size = vec ? static_cast<int>(vec->size()) : 1;
|
||||
|
||||
// For zero value init, return 0s
|
||||
if (ctor_values.empty()) {
|
||||
if (elem_type->Is<sem::I32>()) {
|
||||
return sem::Constant(type, sem::Constant::Scalars(result_size, 0));
|
||||
}
|
||||
if (elem_type->Is<sem::U32>()) {
|
||||
return sem::Constant(type, sem::Constant::Scalars(result_size, 0u));
|
||||
}
|
||||
if (elem_type->Is<sem::F32>()) {
|
||||
return sem::Constant(type, sem::Constant::Scalars(result_size, 0.f));
|
||||
}
|
||||
if (elem_type->Is<sem::Bool>()) {
|
||||
return sem::Constant(type, sem::Constant::Scalars(result_size, false));
|
||||
}
|
||||
}
|
||||
|
||||
// Build value for type_ctor from each child value by casting to
|
||||
// type_ctor's type.
|
||||
sem::Constant::Scalars elems;
|
||||
for (auto* cv : ctor_values) {
|
||||
auto value = ConstantValueOf(cv);
|
||||
if (!value.IsValid()) {
|
||||
return {};
|
||||
}
|
||||
auto cast = ConstantCast(value, elem_type);
|
||||
elems.insert(elems.end(), cast.Elements().begin(), cast.Elements().end());
|
||||
}
|
||||
|
||||
// Splat single-value initializers
|
||||
if (elems.size() == 1) {
|
||||
for (int i = 0; i < result_size - 1; ++i) {
|
||||
elems.emplace_back(elems[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return sem::Constant(type, std::move(elems));
|
||||
}
|
||||
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
|
@ -0,0 +1,433 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/resolver/resolver.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "src/resolver/resolver_test_helper.h"
|
||||
#include "src/sem/expression.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using Scalar = sem::Constant::Scalar;
|
||||
|
||||
using ResolverConstantsTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverConstantsTest, Scalar_i32) {
|
||||
auto* expr = Expr(99);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Scalar_u32) {
|
||||
auto* expr = Expr(99u);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Scalar_f32) {
|
||||
auto* expr = Expr(9.9f);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Scalar_bool) {
|
||||
auto* expr = Expr(true);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_ZeroInit_i32) {
|
||||
auto* expr = vec3<i32>();
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 0);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 0);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 0);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_ZeroInit_u32) {
|
||||
auto* expr = vec3<u32>();
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 0u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 0u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 0u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_ZeroInit_f32) {
|
||||
auto* expr = vec3<f32>();
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 0u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 0u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 0u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_ZeroInit_bool) {
|
||||
auto* expr = vec3<bool>();
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, false);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, false);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Splat_i32) {
|
||||
auto* expr = vec3<i32>(99);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 99);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 99);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Splat_u32) {
|
||||
auto* expr = vec3<u32>(99u);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 99u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 99u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Splat_f32) {
|
||||
auto* expr = vec3<f32>(9.9f);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 9.9f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 9.9f);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Splat_bool) {
|
||||
auto* expr = vec3<bool>(true);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, true);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_FullConstruct_i32) {
|
||||
auto* expr = vec3<i32>(1, 2, 3);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_FullConstruct_u32) {
|
||||
auto* expr = vec3<u32>(1u, 2u, 3u);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_FullConstruct_f32) {
|
||||
auto* expr = vec3<f32>(1.f, 2.f, 3.f);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_FullConstruct_bool) {
|
||||
auto* expr = vec3<bool>(true, false, true);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_MixConstruct_i32) {
|
||||
auto* expr = vec3<i32>(1, vec2<i32>(2, 3));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_MixConstruct_u32) {
|
||||
auto* expr = vec3<u32>(vec2<u32>(1u, 2u), 3u);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_MixConstruct_f32) {
|
||||
auto* expr = vec3<f32>(1.f, vec2<f32>(2.f, 3.f));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_MixConstruct_bool) {
|
||||
auto* expr = vec3<bool>(vec2<bool>(true, false), true);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Cast_f32_to_32) {
|
||||
auto* expr = vec3<i32>(vec3<f32>(1.1f, 2.2f, 3.3f));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Cast_u32_to_f32) {
|
||||
auto* expr = vec3<f32>(vec3<u32>(10u, 20u, 30u));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 10.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 20.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 30.f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
|
@ -22,7 +22,8 @@ namespace sem {
|
|||
Call::Call(const ast::Expression* declaration,
|
||||
const CallTarget* target,
|
||||
Statement* statement)
|
||||
: Base(declaration, target->ReturnType(), statement), target_(target) {}
|
||||
: Base(declaration, target->ReturnType(), statement, Constant{}),
|
||||
target_(target) {}
|
||||
|
||||
Call::~Call() = default;
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0(the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/sem/constant.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "src/debug.h"
|
||||
#include "src/program_builder.h"
|
||||
#include "src/sem/type.h"
|
||||
|
||||
namespace tint {
|
||||
namespace sem {
|
||||
|
||||
namespace {
|
||||
|
||||
const Type* ElemType(const Type* ty, size_t num_elements) {
|
||||
diag::List diag;
|
||||
if (ty->is_scalar()) {
|
||||
if (num_elements != 1) {
|
||||
TINT_ICE(Semantic, diag)
|
||||
<< "sem::Constant() type <-> num_element mismatch. type: '"
|
||||
<< ty->type_name() << "' num_elements: " << num_elements;
|
||||
}
|
||||
return ty;
|
||||
}
|
||||
if (auto* vec = ty->As<Vector>()) {
|
||||
if (num_elements != vec->size()) {
|
||||
TINT_ICE(Semantic, diag)
|
||||
<< "sem::Constant() type <-> num_element mismatch. type: '"
|
||||
<< ty->type_name() << "' num_elements: " << num_elements;
|
||||
}
|
||||
TINT_ASSERT(Semantic, vec->type()->is_scalar());
|
||||
return vec->type();
|
||||
}
|
||||
TINT_UNREACHABLE(Semantic, diag) << "Unsupported sem::Constant type";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Constant::Constant() {}
|
||||
|
||||
Constant::Constant(const sem::Type* ty, Scalars els)
|
||||
: type_(ty), elem_type_(ElemType(ty, els.size())), elems_(std::move(els)) {}
|
||||
|
||||
Constant::Constant(const Constant&) = default;
|
||||
|
||||
Constant::~Constant() = default;
|
||||
|
||||
} // namespace sem
|
||||
} // namespace tint
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0(the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SRC_SEM_CONSTANT_H_
|
||||
#define SRC_SEM_CONSTANT_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "src/program_builder.h"
|
||||
#include "src/sem/type.h"
|
||||
|
||||
namespace tint {
|
||||
namespace sem {
|
||||
|
||||
/// A Constant is compile-time known expression value, expressed as a flattened
|
||||
/// list of scalar values. Value may be of a scalar or vector type.
|
||||
class Constant {
|
||||
using i32 = ProgramBuilder::i32;
|
||||
using u32 = ProgramBuilder::u32;
|
||||
using f32 = ProgramBuilder::f32;
|
||||
|
||||
public:
|
||||
/// Scalar holds a single constant scalar value, as a union of an i32, u32,
|
||||
/// f32 or boolean.
|
||||
union Scalar {
|
||||
/// The scalar value as a i32
|
||||
int32_t i32;
|
||||
/// The scalar value as a u32
|
||||
uint32_t u32;
|
||||
/// The scalar value as a f32
|
||||
float f32;
|
||||
/// The scalar value as a bool
|
||||
bool bool_;
|
||||
|
||||
/// Constructs the scalar with the i32 value `v`
|
||||
/// @param v the value of the Scalar
|
||||
Scalar(ProgramBuilder::i32 v) : i32(v) {} // NOLINT
|
||||
|
||||
/// Constructs the scalar with the u32 value `v`
|
||||
/// @param v the value of the Scalar
|
||||
Scalar(ProgramBuilder::u32 v) : u32(v) {} // NOLINT
|
||||
|
||||
/// Constructs the scalar with the f32 value `v`
|
||||
/// @param v the value of the Scalar
|
||||
Scalar(ProgramBuilder::f32 v) : f32(v) {} // NOLINT
|
||||
|
||||
/// Constructs the scalar with the bool value `v`
|
||||
/// @param v the value of the Scalar
|
||||
Scalar(bool v) : bool_(v) {} // NOLINT
|
||||
};
|
||||
|
||||
/// Scalars is a list of scalar values
|
||||
using Scalars = std::vector<Scalar>;
|
||||
|
||||
/// Constructs an invalid Constant
|
||||
Constant();
|
||||
|
||||
/// Constructs a Constant of the given type and element values
|
||||
/// @param ty the Constant type
|
||||
/// @param els the Constant element values
|
||||
Constant(const Type* ty, Scalars els);
|
||||
|
||||
/// Copy constructor
|
||||
Constant(const Constant&);
|
||||
|
||||
/// Destructor
|
||||
~Constant();
|
||||
|
||||
/// @returns true if the Constant has been initialized
|
||||
bool IsValid() const { return type_ != nullptr; }
|
||||
|
||||
/// @return true if the Constant has been initialized
|
||||
operator bool() const { return IsValid(); }
|
||||
|
||||
/// @returns the type of the Constant
|
||||
const sem::Type* Type() const { return type_; }
|
||||
|
||||
/// @returns the element type of the Constant
|
||||
const sem::Type* ElementType() const { return elem_type_; }
|
||||
|
||||
/// @returns the constant's scalar elements
|
||||
const Scalars& Elements() const { return elems_; }
|
||||
|
||||
/// Calls `func(s)` with s being the current scalar value at `index`.
|
||||
/// `func` is typically a lambda of the form '[](auto&& s)'.
|
||||
/// @param index the index of the scalar value
|
||||
/// @param func a function with signature `T(S)`
|
||||
/// @return the value returned by func.
|
||||
template <typename Func>
|
||||
auto WithScalarAt(size_t index, Func&& func) const {
|
||||
auto* elem_type = ElementType();
|
||||
if (elem_type->Is<I32>()) {
|
||||
return func(elems_[index].i32);
|
||||
}
|
||||
if (elem_type->Is<U32>()) {
|
||||
return func(elems_[index].u32);
|
||||
}
|
||||
if (elem_type->Is<F32>()) {
|
||||
return func(elems_[index].f32);
|
||||
}
|
||||
if (elem_type->Is<Bool>()) {
|
||||
return func(elems_[index].bool_);
|
||||
}
|
||||
diag::List diags;
|
||||
TINT_UNREACHABLE(Semantic, diags)
|
||||
<< "invalid scalar type " << type_->type_name();
|
||||
return func(~0);
|
||||
}
|
||||
|
||||
private:
|
||||
const sem::Type* type_ = nullptr;
|
||||
const sem::Type* elem_type_ = nullptr;
|
||||
Scalars elems_;
|
||||
};
|
||||
|
||||
} // namespace sem
|
||||
} // namespace tint
|
||||
|
||||
#endif // SRC_SEM_CONSTANT_H_
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include "src/sem/expression.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
TINT_INSTANTIATE_TYPEINFO(tint::sem::Expression);
|
||||
|
||||
namespace tint {
|
||||
|
@ -21,10 +23,16 @@ namespace sem {
|
|||
|
||||
Expression::Expression(const ast::Expression* declaration,
|
||||
const sem::Type* type,
|
||||
Statement* statement)
|
||||
: declaration_(declaration), type_(type), statement_(statement) {
|
||||
Statement* statement,
|
||||
Constant constant)
|
||||
: declaration_(declaration),
|
||||
type_(type),
|
||||
statement_(statement),
|
||||
constant_(std::move(constant)) {
|
||||
TINT_ASSERT(Semantic, type_);
|
||||
}
|
||||
|
||||
Expression::~Expression() = default;
|
||||
|
||||
} // namespace sem
|
||||
} // namespace tint
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#define SRC_SEM_EXPRESSION_H_
|
||||
|
||||
#include "src/ast/expression.h"
|
||||
#include "src/sem/constant.h"
|
||||
#include "src/sem/node.h"
|
||||
|
||||
namespace tint {
|
||||
|
@ -31,9 +32,14 @@ class Expression : public Castable<Expression, Node> {
|
|||
/// @param declaration the AST node
|
||||
/// @param type the resolved type of the expression
|
||||
/// @param statement the statement that owns this expression
|
||||
/// @param constant the constant value of the expression. May be invalid
|
||||
Expression(const ast::Expression* declaration,
|
||||
const sem::Type* type,
|
||||
Statement* statement);
|
||||
Statement* statement,
|
||||
Constant constant);
|
||||
|
||||
/// Destructor
|
||||
~Expression() override;
|
||||
|
||||
/// @return the resolved type of the expression
|
||||
sem::Type* Type() const { return const_cast<sem::Type*>(type_); }
|
||||
|
@ -41,6 +47,9 @@ class Expression : public Castable<Expression, Node> {
|
|||
/// @return the statement that owns this expression
|
||||
Statement* Stmt() const { return statement_; }
|
||||
|
||||
/// @return the constant value of this expression
|
||||
const Constant& ConstantValue() const { return constant_; }
|
||||
|
||||
/// @returns the AST node
|
||||
ast::Expression* Declaration() const {
|
||||
return const_cast<ast::Expression*>(declaration_);
|
||||
|
@ -50,6 +59,7 @@ class Expression : public Castable<Expression, Node> {
|
|||
const ast::Expression* declaration_;
|
||||
const sem::Type* const type_;
|
||||
Statement* const statement_;
|
||||
Constant const constant_;
|
||||
};
|
||||
|
||||
} // namespace sem
|
||||
|
|
|
@ -28,7 +28,7 @@ MemberAccessorExpression::MemberAccessorExpression(
|
|||
ast::MemberAccessorExpression* declaration,
|
||||
const sem::Type* type,
|
||||
Statement* statement)
|
||||
: Base(declaration, type, statement) {}
|
||||
: Base(declaration, type, statement, Constant{}) {}
|
||||
|
||||
MemberAccessorExpression::~MemberAccessorExpression() = default;
|
||||
|
||||
|
|
|
@ -52,7 +52,6 @@ class Type : public Castable<Type, Node> {
|
|||
/// @returns the inner type if this is a reference, `this` otherwise
|
||||
const Type* UnwrapRef() const;
|
||||
|
||||
|
||||
/// @returns true if this type is a scalar
|
||||
bool is_scalar() const;
|
||||
/// @returns true if this type is a numeric scalar
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include "src/sem/variable.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "src/ast/identifier_expression.h"
|
||||
#include "src/ast/variable.h"
|
||||
|
||||
|
@ -50,8 +52,10 @@ Variable::~Variable() = default;
|
|||
VariableUser::VariableUser(ast::IdentifierExpression* declaration,
|
||||
const sem::Type* type,
|
||||
Statement* statement,
|
||||
sem::Variable* variable)
|
||||
: Base(declaration, type, statement), variable_(variable) {}
|
||||
sem::Variable* variable,
|
||||
Constant constant_value)
|
||||
: Base(declaration, type, statement, std::move(constant_value)),
|
||||
variable_(variable) {}
|
||||
|
||||
} // namespace sem
|
||||
} // namespace tint
|
||||
|
|
|
@ -109,10 +109,12 @@ class VariableUser : public Castable<VariableUser, Expression> {
|
|||
/// @param type the resolved type of the expression
|
||||
/// @param statement the statement that owns this expression
|
||||
/// @param variable the semantic variable
|
||||
/// @param constant_value the constant value for the variable. May be invalid
|
||||
VariableUser(ast::IdentifierExpression* declaration,
|
||||
const sem::Type* type,
|
||||
Statement* statement,
|
||||
sem::Variable* variable);
|
||||
sem::Variable* variable,
|
||||
Constant constant_value);
|
||||
|
||||
/// @returns the variable that this expression refers to
|
||||
const sem::Variable* Variable() const { return variable_; }
|
||||
|
|
|
@ -19,271 +19,43 @@
|
|||
#include <vector>
|
||||
|
||||
#include "src/program_builder.h"
|
||||
#include "src/sem/expression.h"
|
||||
|
||||
TINT_INSTANTIATE_TYPEINFO(tint::transform::FoldConstants);
|
||||
|
||||
namespace tint {
|
||||
namespace transform {
|
||||
|
||||
namespace {
|
||||
FoldConstants::FoldConstants() = default;
|
||||
|
||||
using i32 = ProgramBuilder::i32;
|
||||
using u32 = ProgramBuilder::u32;
|
||||
using f32 = ProgramBuilder::f32;
|
||||
FoldConstants::~FoldConstants() = default;
|
||||
|
||||
/// A Value is a sequence of scalars
|
||||
struct Value {
|
||||
enum class Type {
|
||||
i32, //
|
||||
u32,
|
||||
f32,
|
||||
bool_
|
||||
};
|
||||
|
||||
union Scalar {
|
||||
ProgramBuilder::i32 i32;
|
||||
ProgramBuilder::u32 u32;
|
||||
ProgramBuilder::f32 f32;
|
||||
bool bool_;
|
||||
|
||||
Scalar(ProgramBuilder::i32 v) : i32(v) {} // NOLINT
|
||||
Scalar(ProgramBuilder::u32 v) : u32(v) {} // NOLINT
|
||||
Scalar(ProgramBuilder::f32 v) : f32(v) {} // NOLINT
|
||||
Scalar(bool v) : bool_(v) {} // NOLINT
|
||||
};
|
||||
|
||||
using Elems = std::vector<Scalar>;
|
||||
|
||||
Type type;
|
||||
Elems elems;
|
||||
|
||||
Value() {}
|
||||
|
||||
Value(ProgramBuilder::i32 v) : type(Type::i32), elems{v} {} // NOLINT
|
||||
Value(ProgramBuilder::u32 v) : type(Type::u32), elems{v} {} // NOLINT
|
||||
Value(ProgramBuilder::f32 v) : type(Type::f32), elems{v} {} // NOLINT
|
||||
Value(bool v) : type(Type::bool_), elems{v} {} // NOLINT
|
||||
|
||||
explicit Value(Type t, Elems e = {}) : type(t), elems(std::move(e)) {}
|
||||
|
||||
bool Valid() const { return elems.size() != 0; }
|
||||
operator bool() const { return Valid(); }
|
||||
|
||||
void Append(const Value& value) {
|
||||
TINT_ASSERT(Transform, value.type == type);
|
||||
elems.insert(elems.end(), value.elems.begin(), value.elems.end());
|
||||
void FoldConstants::Run(CloneContext& ctx, const DataMap&, DataMap&) {
|
||||
ctx.ReplaceAll([&](ast::Expression* expr) -> ast::Expression* {
|
||||
auto* sem = ctx.src->Sem().Get(expr);
|
||||
if (!sem) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Calls `func`(s) with s being the current scalar value at `index`.
|
||||
/// `func` is typically a lambda of the form '[](auto&& s)'.
|
||||
template <typename Func>
|
||||
auto WithScalarAt(size_t index, Func&& func) const {
|
||||
switch (type) {
|
||||
case Value::Type::i32: {
|
||||
return func(elems[index].i32);
|
||||
}
|
||||
case Value::Type::u32: {
|
||||
return func(elems[index].u32);
|
||||
}
|
||||
case Value::Type::f32: {
|
||||
return func(elems[index].f32);
|
||||
}
|
||||
case Value::Type::bool_: {
|
||||
return func(elems[index].bool_);
|
||||
}
|
||||
}
|
||||
TINT_ASSERT(Transform, false && "Unreachable");
|
||||
return func(~0);
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns the Value::Type that maps to the ast::Type*
|
||||
Value::Type AstToValueType(ast::Type* t) {
|
||||
if (t->Is<ast::I32>()) {
|
||||
return Value::Type::i32;
|
||||
} else if (t->Is<ast::U32>()) {
|
||||
return Value::Type::u32;
|
||||
} else if (t->Is<ast::F32>()) {
|
||||
return Value::Type::f32;
|
||||
} else if (t->Is<ast::Bool>()) {
|
||||
return Value::Type::bool_;
|
||||
}
|
||||
TINT_ASSERT(Transform, false && "Invalid type");
|
||||
return {};
|
||||
auto value = sem->ConstantValue();
|
||||
if (!value.IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Cast `Value` to `target_type`
|
||||
/// @return the casted value
|
||||
Value Cast(const Value& value, Value::Type target_type) {
|
||||
if (value.type == target_type) {
|
||||
return value;
|
||||
auto* ty = sem->Type();
|
||||
|
||||
auto* ctor = expr->As<ast::TypeConstructorExpression>();
|
||||
if (!ctor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Value result(target_type);
|
||||
for (size_t i = 0; i < value.elems.size(); ++i) {
|
||||
switch (target_type) {
|
||||
case Value::Type::i32:
|
||||
result.Append(value.WithScalarAt(
|
||||
i, [](auto&& s) { return static_cast<i32>(s); }));
|
||||
break;
|
||||
|
||||
case Value::Type::u32:
|
||||
result.Append(value.WithScalarAt(
|
||||
i, [](auto&& s) { return static_cast<u32>(s); }));
|
||||
break;
|
||||
|
||||
case Value::Type::f32:
|
||||
result.Append(value.WithScalarAt(
|
||||
i, [](auto&& s) { return static_cast<f32>(s); }));
|
||||
break;
|
||||
|
||||
case Value::Type::bool_:
|
||||
result.Append(value.WithScalarAt(
|
||||
i, [](auto&& s) { return static_cast<bool>(s); }));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Type that maps `ast::Expression*` to `Value`
|
||||
using ExprToValue = std::unordered_map<const ast::Expression*, Value>;
|
||||
|
||||
/// Adds mapping of `expr` to `value` to `expr_to_value`
|
||||
/// @returns true if add succeded
|
||||
bool AddExpr(ExprToValue& expr_to_value,
|
||||
const ast::Expression* expr,
|
||||
Value value) {
|
||||
auto r = expr_to_value.emplace(expr, std::move(value));
|
||||
return r.second;
|
||||
}
|
||||
|
||||
/// @returns the `Value` in `expr_to_value` at `expr`, leaving it in the map, or
|
||||
/// invalid Value if not in map
|
||||
Value PeekExpr(ExprToValue& expr_to_value, ast::Expression* expr) {
|
||||
auto iter = expr_to_value.find(expr);
|
||||
if (iter != expr_to_value.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/// @returns the `Value` in `expr_to_value` at `expr`, removing it from the map,
|
||||
/// or invalid Value if not in map
|
||||
Value TakeExpr(ExprToValue& expr_to_value, ast::Expression* expr) {
|
||||
auto iter = expr_to_value.find(expr);
|
||||
if (iter != expr_to_value.end()) {
|
||||
auto result = std::move(iter->second);
|
||||
expr_to_value.erase(iter);
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Folds a `ScalarConstructorExpression` into a `Value`
|
||||
Value Fold(const ast::ScalarConstructorExpression* scalar_ctor) {
|
||||
auto* literal = scalar_ctor->literal();
|
||||
if (auto* lit = literal->As<ast::SintLiteral>()) {
|
||||
return {lit->value_as_i32()};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::UintLiteral>()) {
|
||||
return {lit->value_as_u32()};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::FloatLiteral>()) {
|
||||
return {lit->value()};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::BoolLiteral>()) {
|
||||
return {lit->IsTrue()};
|
||||
}
|
||||
TINT_ASSERT(Transform, false && "Unreachable");
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Folds a `TypeConstructorExpression` into a `Value` if possible.
|
||||
/// @returns a valid `Value` with 1 element for scalars, and 2/3/4 elements for
|
||||
/// vectors.
|
||||
Value Fold(const ast::TypeConstructorExpression* type_ctor,
|
||||
ExprToValue& expr_to_value) {
|
||||
auto& ctor_values = type_ctor->values();
|
||||
auto* type = type_ctor->type();
|
||||
auto* vec = type->As<ast::Vector>();
|
||||
|
||||
// For now, only fold scalars and vectors
|
||||
if (!type->is_scalar() && !vec) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* elem_type = vec ? vec->type() : type;
|
||||
int result_size = vec ? static_cast<int>(vec->size()) : 1;
|
||||
|
||||
// For zero value init, return 0s
|
||||
if (ctor_values.empty()) {
|
||||
if (elem_type->Is<ast::I32>()) {
|
||||
return Value(Value::Type::i32, Value::Elems(result_size, 0));
|
||||
} else if (elem_type->Is<ast::U32>()) {
|
||||
return Value(Value::Type::u32, Value::Elems(result_size, 0u));
|
||||
} else if (elem_type->Is<ast::F32>()) {
|
||||
return Value(Value::Type::f32, Value::Elems(result_size, 0.0f));
|
||||
} else if (elem_type->Is<ast::Bool>()) {
|
||||
return Value(Value::Type::bool_, Value::Elems(result_size, false));
|
||||
}
|
||||
}
|
||||
|
||||
// If not all ctor_values are foldable, we can't fold this node
|
||||
for (auto* cv : ctor_values) {
|
||||
if (!PeekExpr(expr_to_value, cv)) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Build value for type_ctor from each child value by casting to
|
||||
// type_ctor's type.
|
||||
Value new_value(AstToValueType(elem_type));
|
||||
for (auto* cv : ctor_values) {
|
||||
auto value = TakeExpr(expr_to_value, cv);
|
||||
new_value.Append(Cast(value, AstToValueType(elem_type)));
|
||||
}
|
||||
|
||||
// Splat single-value initializers
|
||||
if (new_value.elems.size() == 1) {
|
||||
auto first_value = new_value;
|
||||
for (int i = 0; i < result_size - 1; ++i) {
|
||||
new_value.Append(first_value);
|
||||
}
|
||||
}
|
||||
|
||||
return new_value;
|
||||
}
|
||||
|
||||
/// @returns a `ConstructorExpression` to replace `expr` with, or nullptr if we
|
||||
/// shouldn't replace it.
|
||||
ast::ConstructorExpression* Build(CloneContext& ctx,
|
||||
const ast::Expression* expr,
|
||||
const Value& value) {
|
||||
// If original ctor expression had no init values, don't replace the
|
||||
// expression
|
||||
if (auto* ctor = expr->As<ast::TypeConstructorExpression>()) {
|
||||
if (ctor->values().size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto make_ast_type = [&]() -> ast::Type* {
|
||||
switch (value.type) {
|
||||
case Value::Type::i32:
|
||||
return ctx.dst->ty.i32();
|
||||
case Value::Type::u32:
|
||||
return ctx.dst->ty.u32();
|
||||
case Value::Type::f32:
|
||||
return ctx.dst->ty.f32();
|
||||
case Value::Type::bool_:
|
||||
return ctx.dst->ty.bool_();
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (auto* type_ctor = expr->As<ast::TypeConstructorExpression>()) {
|
||||
if (auto* vec = type_ctor->type()->As<ast::Vector>()) {
|
||||
if (auto* vec = ty->As<sem::Vector>()) {
|
||||
uint32_t vec_size = static_cast<uint32_t>(vec->size());
|
||||
|
||||
// We'd like to construct the new vector with the same number of
|
||||
|
@ -294,9 +66,9 @@ ast::ConstructorExpression* Build(CloneContext& ctx,
|
|||
//
|
||||
// In this case, creating a vec3 with 2 args is invalid, so we should
|
||||
// create it with 3. So what we do is construct with vec_size args,
|
||||
// except if the original vector was single-value initialized, in which
|
||||
// case, we only construct with one arg again.
|
||||
uint32_t ctor_size = (type_ctor->values().size() == 1) ? 1 : vec_size;
|
||||
// except if the original vector was single-value initialized, in
|
||||
// which case, we only construct with one arg again.
|
||||
uint32_t ctor_size = (ctor->values().size() == 1) ? 1 : vec_size;
|
||||
|
||||
ast::ExpressionList ctors;
|
||||
for (uint32_t i = 0; i < ctor_size; ++i) {
|
||||
|
@ -304,44 +76,16 @@ ast::ConstructorExpression* Build(CloneContext& ctx,
|
|||
i, [&](auto&& s) { ctors.emplace_back(ctx.dst->Expr(s)); });
|
||||
}
|
||||
|
||||
return ctx.dst->vec(make_ast_type(), vec_size, ctors);
|
||||
} else if (type_ctor->type()->is_scalar()) {
|
||||
auto* el_ty = CreateASTTypeFor(&ctx, vec->type());
|
||||
return ctx.dst->vec(el_ty, vec_size, ctors);
|
||||
}
|
||||
|
||||
if (ty->is_scalar()) {
|
||||
return value.WithScalarAt(0, [&](auto&& s) { return ctx.dst->Expr(s); });
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace transform {
|
||||
|
||||
FoldConstants::FoldConstants() = default;
|
||||
|
||||
FoldConstants::~FoldConstants() = default;
|
||||
|
||||
void FoldConstants::Run(CloneContext& ctx, const DataMap&, DataMap&) {
|
||||
ExprToValue expr_to_value;
|
||||
|
||||
// Visit inner expressions before outer expressions
|
||||
for (auto* node : ctx.src->ASTNodes().Objects()) {
|
||||
if (auto* scalar_ctor = node->As<ast::ScalarConstructorExpression>()) {
|
||||
if (auto v = Fold(scalar_ctor)) {
|
||||
AddExpr(expr_to_value, scalar_ctor, std::move(v));
|
||||
}
|
||||
}
|
||||
if (auto* type_ctor = node->As<ast::TypeConstructorExpression>()) {
|
||||
if (auto v = Fold(type_ctor, expr_to_value)) {
|
||||
AddExpr(expr_to_value, type_ctor, std::move(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& kvp : expr_to_value) {
|
||||
if (auto* ctor_expr = Build(ctx, kvp.first, kvp.second)) {
|
||||
ctx.Replace(kvp.first, ctor_expr);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ctx.Clone();
|
||||
}
|
||||
|
|
|
@ -54,6 +54,10 @@ Output Spirv::Run(const Program* in, const DataMap& data) {
|
|||
manager.Add<ForLoopToLoop>(); // Must come after ZeroInitWorkgroupMemory
|
||||
auto transformedInput = manager.Run(in, data);
|
||||
|
||||
if (transformedInput.program.Diagnostics().contains_errors()) {
|
||||
return transformedInput;
|
||||
}
|
||||
|
||||
auto* cfg = data.Get<Config>();
|
||||
|
||||
ProgramBuilder out;
|
||||
|
|
|
@ -96,8 +96,9 @@ ast::TypeConstructorExpression* AppendVector(ProgramBuilder* b,
|
|||
|
||||
for (uint32_t i = 0; i < packed_size - 1; i++) {
|
||||
auto* zero = buildZero();
|
||||
b->Sem().Add(zero, b->create<sem::Expression>(zero, packed_el_sem_ty,
|
||||
statement));
|
||||
b->Sem().Add(
|
||||
zero, b->create<sem::Expression>(zero, packed_el_sem_ty, statement,
|
||||
sem::Constant{}));
|
||||
packed.emplace_back(zero);
|
||||
}
|
||||
}
|
||||
|
@ -107,16 +108,18 @@ ast::TypeConstructorExpression* AppendVector(ProgramBuilder* b,
|
|||
if (packed_el_sem_ty != b->TypeOf(scalar)->UnwrapRef()) {
|
||||
// Cast scalar to the vector element type
|
||||
auto* scalar_cast = b->Construct(packed_el_ty, scalar);
|
||||
b->Sem().Add(scalar_cast, b->create<sem::Expression>(
|
||||
scalar_cast, packed_el_sem_ty, statement));
|
||||
b->Sem().Add(scalar_cast,
|
||||
b->create<sem::Expression>(scalar_cast, packed_el_sem_ty,
|
||||
statement, sem::Constant{}));
|
||||
packed.emplace_back(scalar_cast);
|
||||
} else {
|
||||
packed.emplace_back(scalar);
|
||||
}
|
||||
|
||||
auto* constructor = b->Construct(packed_ty, std::move(packed));
|
||||
b->Sem().Add(constructor, b->create<sem::Expression>(
|
||||
constructor, packed_sem_ty, statement));
|
||||
b->Sem().Add(constructor,
|
||||
b->create<sem::Expression>(constructor, packed_sem_ty, statement,
|
||||
sem::Constant{}));
|
||||
|
||||
return constructor;
|
||||
}
|
||||
|
|
|
@ -1764,7 +1764,8 @@ bool GeneratorImpl::EmitTextureCall(std::ostream& out,
|
|||
auto* i32 = builder_.create<sem::I32>();
|
||||
auto* zero = builder_.Expr(0);
|
||||
auto* stmt = builder_.Sem().Get(vector)->Stmt();
|
||||
builder_.Sem().Add(zero, builder_.create<sem::Expression>(zero, i32, stmt));
|
||||
builder_.Sem().Add(zero, builder_.create<sem::Expression>(zero, i32, stmt,
|
||||
sem::Constant{}));
|
||||
auto* packed = AppendVector(&builder_, vector, zero);
|
||||
return EmitExpression(out, packed);
|
||||
};
|
||||
|
|
|
@ -243,6 +243,7 @@ tint_unittests_source_set("tint_unittests_core_src") {
|
|||
"../src/resolver/pipeline_overridable_constant_test.cc",
|
||||
"../src/resolver/ptr_ref_test.cc",
|
||||
"../src/resolver/ptr_ref_validation_test.cc",
|
||||
"../src/resolver/resolver_constants_test.cc",
|
||||
"../src/resolver/resolver_test.cc",
|
||||
"../src/resolver/resolver_test_helper.cc",
|
||||
"../src/resolver/resolver_test_helper.h",
|
||||
|
|
Loading…
Reference in New Issue