blob: b71f9a74f7088ac62223796afcf7c365ce6924b1 [file] [edit]
// Copyright 2024 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/ir/const_param_validator.h"
#include <cstdint>
#include <string>
#include <utility>
#include "src/tint/lang/core/enums.h"
#include "src/tint/lang/core/intrinsic/table.h"
#include "src/tint/lang/core/ir/constant.h"
#include "src/tint/lang/core/ir/core_binary.h"
#include "src/tint/lang/core/ir/core_builtin_call.h"
#include "src/tint/lang/core/ir/instruction.h"
#include "src/tint/lang/core/ir/instruction_result.h"
#include "src/tint/lang/core/ir/module.h"
#include "src/tint/lang/core/ir/switch.h"
#include "src/tint/lang/core/ir/type/array_count.h"
#include "src/tint/lang/core/ir/value.h"
#include "src/tint/lang/core/number.h"
#include "src/tint/lang/core/type/type.h"
#include "src/tint/utils/diagnostic/diagnostic.h"
#include "src/tint/utils/internal_limits.h"
#include "src/tint/utils/rtti/switch.h"
namespace tint::core::ir {
namespace {
/// The core IR const param validator.
class ConstParamValidator {
public:
/// Create a core const param validator
/// @param mod the module to be validated
explicit ConstParamValidator(Module& mod);
/// Destructor
~ConstParamValidator();
/// Runs the const param validator over the module provided during construction
/// @returns success or failure
Result<SuccessType> Run();
void CheckSubgroupCall(const CoreBuiltinCall* call);
void CheckBuiltinCall(const BuiltinCall* call);
void CheckCoreBinaryCall(const CoreBinary* inst);
void CheckExtractBitsCall(const CoreBuiltinCall* call);
void CheckInsertBitsCall(const CoreBuiltinCall* call);
void CheckLdexpCall(const CoreBuiltinCall* call);
void CheckQuantizeToF16(const CoreBuiltinCall* call);
void CheckClampCall(const CoreBuiltinCall* call);
void CheckPack2x16float(const CoreBuiltinCall* call);
void CheckSmoothstepCall(const CoreBuiltinCall* call);
void CheckBinaryDivModCall(const CoreBinary* call);
void CheckBinaryShiftCall(const CoreBinary* call);
diag::Diagnostic& AddError(const Instruction& inst);
private:
Module& mod_;
constant::Eval const_eval_;
diag::List diagnostics_;
};
ConstParamValidator::ConstParamValidator(Module& mod)
: mod_(mod), const_eval_(mod.constant_values, diagnostics_) {}
ConstParamValidator::~ConstParamValidator() = default;
diag::Diagnostic& ConstParamValidator::AddError(const Instruction& inst) {
auto src = mod_.SourceOf(&inst);
return diagnostics_.AddError(src);
}
const constant::Value* GetConstArg(const CoreBuiltinCall* call, uint32_t param_index) {
if (call->Args().Length() <= param_index) {
return nullptr;
}
if (call->Args()[param_index] == nullptr) {
return nullptr;
}
if (!call->Args()[param_index]->Is<ir::Constant>()) {
return nullptr;
}
return call->Args()[param_index]->As<ir::Constant>()->Value();
}
void ConstParamValidator::CheckExtractBitsCall(const CoreBuiltinCall* call) {
// This can be u32/i32 or vector of those types.
auto* param0 = call->Args()[0];
auto* const_val_offset = GetConstArg(call, 1);
auto* const_val_count = GetConstArg(call, 2);
if (const_val_count && const_val_offset) {
auto* zero = const_eval_.Zero(param0->Type(), {}, Source{}).Get();
auto fakeArgs = Vector{zero, const_val_offset, const_val_count};
[[maybe_unused]] auto result =
const_eval_.extractBits(param0->Type(), fakeArgs, mod_.SourceOf(call));
}
}
void ConstParamValidator::CheckInsertBitsCall(const CoreBuiltinCall* call) {
// This can be u32/i32 or vector of those types.
auto* param0 = call->Args()[0];
auto* const_val_offset = GetConstArg(call, 2);
auto* const_val_count = GetConstArg(call, 3);
if (const_val_count && const_val_offset) {
auto* zero = const_eval_.Zero(param0->Type(), {}, Source{}).Get();
auto fakeArgs = Vector{zero, zero, const_val_offset, const_val_count};
[[maybe_unused]] auto result =
const_eval_.insertBits(param0->Type(), fakeArgs, mod_.SourceOf(call));
}
}
void ConstParamValidator::CheckLdexpCall(const CoreBuiltinCall* call) {
auto* param0 = call->Args()[0];
if (auto const_val = GetConstArg(call, 1)) {
auto* zero = const_eval_.Zero(param0->Type(), {}, Source{}).Get();
auto fakeArgs = Vector{zero, const_val};
[[maybe_unused]] auto result =
const_eval_.ldexp(param0->Type(), fakeArgs, mod_.SourceOf(call));
}
}
void ConstParamValidator::CheckQuantizeToF16(const CoreBuiltinCall* call) {
if (auto const_val = GetConstArg(call, 0)) {
[[maybe_unused]] auto result = const_eval_.quantizeToF16(
call->Result()->Type(), Vector{const_val}, mod_.SourceOf(call));
}
}
void ConstParamValidator::CheckPack2x16float(const CoreBuiltinCall* call) {
if (auto const_val = GetConstArg(call, 0)) {
[[maybe_unused]] auto result = const_eval_.pack2x16float(
call->Result()->Type(), Vector{const_val}, mod_.SourceOf(call));
}
}
void ConstParamValidator::CheckSubgroupCall(const CoreBuiltinCall* call) {
if (auto const_val = GetConstArg(call, 1)) {
auto as_aint = const_val->ValueAs<AInt>();
// User friendly param name.
std::string paramName = "sourceLaneIndex";
switch (call->Func()) {
case core::BuiltinFn::kSubgroupShuffleXor:
paramName = "mask";
break;
case core::BuiltinFn::kSubgroupShuffleUp:
case core::BuiltinFn::kSubgroupShuffleDown:
paramName = "delta";
break;
default:
break;
}
if (as_aint >= tint::internal_limits::kMaxSubgroupSize) {
AddError(*call) << "The " << paramName << " argument of " << call->FriendlyName()
<< " must be less than " << tint::internal_limits::kMaxSubgroupSize;
} else if (as_aint < 0) {
AddError(*call) << "The " << paramName << " argument of " << call->FriendlyName()
<< " must be greater than or equal to zero";
}
}
}
void ConstParamValidator::CheckClampCall(const CoreBuiltinCall* call) {
auto* const_val_low = GetConstArg(call, 1);
auto* const_val_high = GetConstArg(call, 2);
if (const_val_low && const_val_high) {
auto fakeArgs = Vector{const_val_low, const_val_low, const_val_high};
[[maybe_unused]] auto result =
const_eval_.clamp(call->Result()->Type(), fakeArgs, mod_.SourceOf(call));
}
}
void ConstParamValidator::CheckSmoothstepCall(const CoreBuiltinCall* call) {
auto* const_val_low = GetConstArg(call, 0);
auto* const_val_high = GetConstArg(call, 1);
if (const_val_low && const_val_high) {
auto fakeArgs = Vector{const_val_low, const_val_high, const_val_high};
[[maybe_unused]] auto result =
const_eval_.smoothstep(call->Result()->Type(), fakeArgs, mod_.SourceOf(call));
}
}
void ConstParamValidator::CheckBuiltinCall(const BuiltinCall* inst) {
if (auto* call = inst->As<core::ir::CoreBuiltinCall>()) {
switch (call->Func()) {
case core::BuiltinFn::kSubgroupShuffle:
case core::BuiltinFn::kSubgroupShuffleXor:
case core::BuiltinFn::kSubgroupShuffleUp:
case core::BuiltinFn::kSubgroupShuffleDown:
CheckSubgroupCall(call);
break;
case core::BuiltinFn::kExtractBits:
CheckExtractBitsCall(call);
break;
case core::BuiltinFn::kInsertBits:
CheckInsertBitsCall(call);
break;
case core::BuiltinFn::kLdexp:
CheckLdexpCall(call);
break;
case core::BuiltinFn::kClamp:
CheckClampCall(call);
break;
case core::BuiltinFn::kSmoothstep:
CheckSmoothstepCall(call);
break;
case core::BuiltinFn::kQuantizeToF16:
CheckQuantizeToF16(call);
break;
case core::BuiltinFn::kPack2X16Float:
CheckPack2x16float(call);
break;
default:
break;
}
}
}
void ConstParamValidator::CheckBinaryDivModCall(const CoreBinary* call) {
// Integer division by zero should be checked for the partial evaluation case (only rhs
// is const). FP division by zero is only invalid when the whole expression is
// constant-evaluated.
if (call->RHS()->Type()->IsIntegerScalarOrVector()) {
auto rhs_constant = call->RHS()->As<ir::Constant>();
if (rhs_constant && rhs_constant->Value()->AnyZero()) {
AddError(*call) << "integer division by zero is invalid";
}
}
}
void ConstParamValidator::CheckBinaryShiftCall(const CoreBinary* call) {
// If lhs value is a concrete type, and rhs is a const-expression greater than or equal
// to the bit width of lhs, then it is a shader-creation error.
const auto* elem_type = call->LHS()->Type()->DeepestElement();
const uint32_t bit_width = elem_type->Size() * 8;
if (auto* rhs_val_as_const = call->RHS()->As<ir::Constant>()) {
auto* rhs_as_value = rhs_val_as_const->Value();
for (size_t i = 0, n = rhs_as_value->NumElements(); i < n; i++) {
auto* shift_val = n == 1 ? rhs_as_value : rhs_as_value->Index(i);
if (shift_val->ValueAs<u32>() >= bit_width) {
AddError(*call) << "shift "
<< (call->Op() == core::BinaryOp::kShiftLeft ? "left" : "right")
<< " value must be less than the bit width of the lhs, which is "
<< bit_width;
break;
}
}
}
}
void ConstParamValidator::CheckCoreBinaryCall(const CoreBinary* call) {
switch (call->Op()) {
case core::BinaryOp::kDivide:
case core::BinaryOp::kModulo:
CheckBinaryDivModCall(call);
break;
case core::BinaryOp::kShiftLeft:
case core::BinaryOp::kShiftRight:
CheckBinaryShiftCall(call);
break;
default:
break;
}
}
Result<SuccessType> ConstParamValidator::Run() {
auto instructions = this->mod_.Instructions();
for (auto inst : instructions) {
tint::Switch(
inst, //
[&](const BuiltinCall* c) { CheckBuiltinCall(c); }, //
[&](const CoreBinary* c) { CheckCoreBinaryCall(c); }, //
[&](Default) {});
}
if (diagnostics_.ContainsErrors()) {
return Failure{diagnostics_.Str()};
}
return Success;
}
} // namespace
Result<SuccessType> ValidateConstParam(Module& mod) {
ConstParamValidator v(mod);
return v.Run();
}
} // namespace tint::core::ir