Files
TheRock/rocm-libraries/shared/rocroller/lib/source/Expression_evaluate_unary.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

556 lines
18 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <cmath>
#include <rocRoller/Expression.hpp>
#include <rocRoller/Expression_evaluate_detail.hpp>
#include <rocRoller/Utilities/RTTI.hpp>
#include <rocRoller/Utilities/Random.hpp>
#include <libdivide.h>
namespace rocRoller::Expression::EvaluateDetail
{
template <CUnary UnaryExpr, DataType DESTTYPE = DataType::None>
struct UnaryEvaluatorVisitor
{
UnaryExpr expr;
using TheEvaluator = OperationEvaluatorVisitor<UnaryExpr, DESTTYPE>;
template <CCommandArgumentValue ARG>
requires CCanEvaluateUnary<TheEvaluator, ARG>
CommandArgumentValue operator()(ARG const& arg) const
{
auto evaluator = static_cast<TheEvaluator const*>(this);
return evaluator->evaluate(arg);
}
template <CCommandArgumentValue ARG>
requires(!CCanEvaluateUnary<TheEvaluator, ARG>) CommandArgumentValue
operator()(ARG const& arg) const
{
if constexpr(CHasTypeInfo<ARG>)
{
Throw<FatalError>("Incompatible type ",
TypeInfo<ARG>::Name(),
" for expression ",
typeName<UnaryExpr>());
}
else
{
Throw<FatalError>("Incompatible type ",
ShowValue(ARG()),
typeName<ARG>(),
" for expression ",
typeName<UnaryExpr>());
}
}
CommandArgumentValue call(CommandArgumentValue const& arg) const
{
return std::visit(*this, arg);
}
};
template <>
struct OperationEvaluatorVisitor<MagicMultiple> : public UnaryEvaluatorVisitor<MagicMultiple>
{
uint32_t evaluate(uint32_t const& arg) const
{
assertNonNullPointer(arg);
if(arg == 0)
return std::numeric_limits<uint32_t>::max() / 2;
// When the divisor is 1, this constant has no use, so return 0.
if(arg == 1)
return 0;
auto magic = libdivide::libdivide_u32_branchfree_gen(arg);
return magic.magic;
}
int32_t evaluate(int32_t const& arg) const
{
assertNonNullPointer(arg);
if(arg == 0)
return std::numeric_limits<int32_t>::max() / 2;
auto magic = libdivide::libdivide_s32_branchfree_gen(arg);
return magic.magic;
}
int64_t evaluate(int64_t const& arg) const
{
assertNonNullPointer(arg);
if(arg == 0)
return std::numeric_limits<int64_t>::max() / 2;
auto magic = libdivide::libdivide_s64_branchfree_gen(arg);
return magic.magic;
}
};
template <>
struct OperationEvaluatorVisitor<Convert>
{
Convert exp;
template <int Idx = 0>
CommandArgumentValue call(CommandArgumentValue const& arg)
{
constexpr auto IdxType = static_cast<DataType>(Idx);
if constexpr(IdxType == DataType::None)
{
Throw<FatalError>("Cannot convert to None type.");
return 0;
}
else if constexpr(IdxType == DataType::Count)
{
Throw<FatalError>("Cannot convert to Count type.");
return 0;
}
else
{
using ResultType = typename EnumTypeInfo<IdxType>::Type;
if(exp.destinationType == IdxType)
{
if constexpr(!CCommandArgumentValue<ResultType>)
{
Throw<FatalError>("Cannot convert to ", friendlyTypeName<ResultType>());
return 0;
}
else
{
auto theVisitor = [](auto value) -> ResultType {
using ExistingType = std::decay_t<decltype(value)>;
if constexpr(CCanStaticCastTo<ResultType, ExistingType>)
{
return static_cast<ResultType>(value);
}
else
{
Throw<FatalError>("Cannot cast ",
friendlyTypeName<ExistingType>(),
" to ",
friendlyTypeName<ResultType>());
return ResultType{};
}
};
return std::visit(theVisitor, arg);
}
}
else
{
return call<Idx + 1>(arg);
}
}
}
};
template <>
struct OperationEvaluatorVisitor<Reinterpret>
{
Reinterpret exp;
template <CCommandArgumentValue FromType, int Idx = 0>
CommandArgumentValue reinterpret(FromType const& value, DataType targetDataType)
{
constexpr auto IdxType = static_cast<DataType>(Idx);
if constexpr(IdxType == DataType::None || IdxType == DataType::Count)
{
Throw<FatalError>("Unsupported reinterpret to type: ", toString(targetDataType));
return 0;
}
else
{
using ToType = typename EnumTypeInfo<IdxType>::Type;
if(targetDataType == IdxType)
{
AssertFatal(std::is_trivially_copyable_v<FromType>,
"FromType must be trivially copyable");
AssertFatal(std::is_trivially_copyable_v<ToType>,
"ToType must be trivially copyable");
if constexpr(!CCommandArgumentValue<
ToType> || sizeof(ToType) != sizeof(FromType))
{
Throw<FatalError>("Cannot reinterpret to ",
friendlyTypeName<ToType>(),
" from ",
friendlyTypeName<FromType>());
return 0;
}
else
{
return std::bit_cast<ToType>(value);
}
}
else
{
return reinterpret<FromType, Idx + 1>(value, targetDataType);
}
}
}
CommandArgumentValue call(CommandArgumentValue const& arg)
{
return std::visit(
[this](auto const& val) -> CommandArgumentValue {
using FromType = std::decay_t<decltype(val)>;
return reinterpret<FromType, 0>(val, exp.destinationType);
},
arg);
}
};
template <CCommandArgumentValue FromType, int Idx = 0>
CommandArgumentValue reinterpretTruncateValue(FromType const& value,
DataType targetDataType,
std::endian endianness = std::endian::native)
{
constexpr auto IdxType = static_cast<DataType>(Idx);
AssertFatal(
endianness == std::endian::little || endianness == std::endian::big,
"Unsupported or mixed endianness: only pure little- or big-endian are supported.");
if constexpr(IdxType == DataType::None || IdxType == DataType::Count)
{
Throw<FatalError>("Unsupported reinterpretTruncate to type: ",
toString(targetDataType));
return 0;
}
else
{
using ToType = typename EnumTypeInfo<IdxType>::Type;
AssertFatal(std::is_trivially_copyable_v<FromType>,
"FromType must be trivially copyable");
AssertFatal(std::is_trivially_copyable_v<ToType>, "ToType must be trivially copyable");
if(targetDataType == IdxType)
{
if constexpr(!CCommandArgumentValue<ToType>)
{
Throw<FatalError>("Cannot reinterpret to ", friendlyTypeName<ToType>());
return 0;
}
else
{
if constexpr(sizeof(ToType) == sizeof(FromType))
{
return std::bit_cast<ToType>(value);
}
// Truncate
else if constexpr(sizeof(ToType) < sizeof(FromType))
{
constexpr std::size_t N = sizeof(ToType);
// Source and destination as bytes
const auto src_bytes
= std::bit_cast<std::array<std::byte, sizeof(FromType)>>(value);
std::array<std::byte, sizeof(ToType)> dst_bytes{};
dst_bytes.fill(std::byte{0});
if(endianness == std::endian::little)
{
// Keep least-significant bytes: at low addresses.
std::copy_n(src_bytes.data(), N, dst_bytes.data());
}
else
{
// Big-endian: least-significant bytes live at high addresses.
std::copy_n(
src_bytes.data() + (sizeof(FromType) - N), N, dst_bytes.data());
}
return std::bit_cast<ToType>(dst_bytes);
}
else
{
Throw<FatalError>("Cannot truncate to wider type ",
friendlyTypeName<ToType>(),
" from ",
friendlyTypeName<FromType>());
return 0;
}
}
}
else
{
return reinterpretTruncateValue<FromType, Idx + 1>(
value, targetDataType, endianness);
}
}
}
CommandArgumentValue reinterpretTruncateValue(CommandArgumentValue const& value,
DataType targetDataType,
std::endian endianness)
{
return std::visit(
[targetDataType, endianness](auto const& val) -> CommandArgumentValue {
using FromType = std::decay_t<decltype(val)>;
return reinterpretTruncateValue<FromType>(val, targetDataType, endianness);
},
value);
}
template <>
struct OperationEvaluatorVisitor<BitFieldExtract>
{
BitFieldExtract expr;
template <CCommandArgumentValue ARG>
requires(CIntegral<ARG>) CommandArgumentValue operator()(ARG const& arg) const
{
auto argBits = resultVariableType(arg).getElementSize() * 8u;
AssertFatal(argBits >= expr.offset + expr.width,
"BitFieldExtract out of bounds: offset={} + width={} > argBits={}",
expr.offset,
expr.width,
argBits);
using UnsignedARG = std::make_unsigned_t<ARG>;
UnsignedARG unsignedArg = static_cast<UnsignedARG>(arg);
UnsignedARG mask = 0;
if(argBits == this->expr.width)
{
mask = std::numeric_limits<UnsignedARG>::max();
}
else
{
mask = (static_cast<UnsignedARG>(1) << this->expr.width) - 1;
}
UnsignedARG result = (unsignedArg >> this->expr.offset) & mask;
// Sign extend if needed
if constexpr(std::is_signed_v<ARG>)
{
bool signBit = (result >> (this->expr.width - 1)) & 1;
if(signBit)
{
UnsignedARG signExtensionMask = ~mask;
result |= signExtensionMask;
}
}
AssertFatal(expr.width <= DataTypeInfo::Get(expr.outputDataType).elementBits,
fmt::format("BitFieldExtract: width {} exceeds output type size {} bits",
expr.width,
DataTypeInfo::Get(expr.outputDataType).elementBits));
return reinterpretTruncateValue(static_cast<ARG>(result), expr.outputDataType);
}
CommandArgumentValue operator()(Raw32 const& arg) const
{
return call(arg.value);
}
template <typename ARG>
CommandArgumentValue operator()(ARG const&) const
{
Throw<FatalError>("BitFieldExtract: unsupported argument type ", ShowValue(expr));
}
CommandArgumentValue call(CommandArgumentValue const& arg) const
{
return std::visit(*this, arg);
}
};
template <>
struct OperationEvaluatorVisitor<MagicShifts> : public UnaryEvaluatorVisitor<MagicShifts>
{
int evaluate(uint32_t const& arg) const
{
assertNonNullPointer(arg);
if(arg == 0)
return 0;
// When the divisor is 1, we set the MSB of MagicShift to 1 so we can detect this case
// by checking the MSB.
if(arg == 1)
return 1 << 31;
auto magic = libdivide::libdivide_u32_branchfree_gen(arg);
return magic.more & libdivide::LIBDIVIDE_32_SHIFT_MASK;
}
};
template <>
struct OperationEvaluatorVisitor<MagicShiftAndSign>
: public UnaryEvaluatorVisitor<MagicShiftAndSign>
{
static_assert(libdivide::LIBDIVIDE_32_SHIFT_MASK == 31,
"magicNumberDivision assumes this is true.");
static_assert(libdivide::LIBDIVIDE_64_SHIFT_MASK == 63,
"magicNumberDivision assumes this is true.");
static_assert(libdivide::LIBDIVIDE_NEGATIVE_DIVISOR == 1 << 7,
"magicNumberDivision assumes this is true.");
uint32_t evaluate(int32_t const& arg) const
{
assertNonNullPointer(arg);
if(arg == 0)
return 0;
auto magic = libdivide::libdivide_s32_branchfree_gen(arg);
return static_cast<uint32_t>(magic.more);
}
uint32_t evaluate(int64_t const& arg) const
{
assertNonNullPointer(arg);
if(arg == 0)
return 0;
auto magic = libdivide::libdivide_s64_branchfree_gen(arg);
return static_cast<uint32_t>(magic.more);
}
};
template <>
struct OperationEvaluatorVisitor<BitwiseNegate> : public UnaryEvaluatorVisitor<BitwiseNegate>
{
template <typename T>
requires(CIntegral<T> or std::is_same_v<T, Raw32>) constexpr T evaluate(T const& arg) const
{
return ~arg;
}
};
template <>
struct OperationEvaluatorVisitor<Exponential2> : public UnaryEvaluatorVisitor<Exponential2>
{
template <isFP32 T>
constexpr T evaluate(T const& arg) const
{
return std::exp2(arg);
}
};
template <>
struct OperationEvaluatorVisitor<Exponential> : public UnaryEvaluatorVisitor<Exponential>
{
template <isFP32 T>
constexpr T evaluate(T const& arg) const
{
return std::exp(arg);
}
};
template <>
struct OperationEvaluatorVisitor<Negate> : public UnaryEvaluatorVisitor<Negate>
{
template <typename T>
requires(std::floating_point<T> || std::signed_integral<T>) constexpr T
evaluate(T const& arg) const
{
return -arg;
}
};
template <>
struct OperationEvaluatorVisitor<LogicalNot> : public UnaryEvaluatorVisitor<LogicalNot>
{
template <typename T>
constexpr bool evaluate(T const& arg) const
{
return !arg;
}
};
template <>
struct OperationEvaluatorVisitor<RandomNumber> : public UnaryEvaluatorVisitor<RandomNumber>
{
template <typename T>
requires(!std::same_as<bool, T> && std::unsigned_integral<T>) constexpr T
evaluate(T const& arg) const
{
return LFSRRandomNumberGenerator(arg);
}
};
template <>
struct OperationEvaluatorVisitor<ToScalar> : public UnaryEvaluatorVisitor<ToScalar>
{
template <typename T>
constexpr T evaluate(T arg) const
{
return arg;
}
};
struct ToBoolVisitor
{
template <typename T>
constexpr bool operator()(T const& val)
{
Throw<FatalError>("Invalid bool type: ", typeName<T>());
return 0;
}
template <std::integral T>
constexpr bool operator()(T const& val)
{
return val != 0;
}
bool call(CommandArgumentValue const& val)
{
return std::visit(*this, val);
}
};
template <CUnary T>
__attribute__((noinline)) CommandArgumentValue evaluateOp(T const& op,
CommandArgumentValue const& arg)
{
OperationEvaluatorVisitor<T> visitor{op};
return visitor.call(arg);
}
#define INSTANTIATE_UNARY_OP(Op) \
template CommandArgumentValue evaluateOp<Op>(Op const& op, CommandArgumentValue const& arg)
INSTANTIATE_UNARY_OP(MagicMultiple);
INSTANTIATE_UNARY_OP(MagicShifts);
INSTANTIATE_UNARY_OP(MagicShiftAndSign);
INSTANTIATE_UNARY_OP(Negate);
INSTANTIATE_UNARY_OP(BitwiseNegate);
INSTANTIATE_UNARY_OP(LogicalNot);
INSTANTIATE_UNARY_OP(Exponential2);
INSTANTIATE_UNARY_OP(Exponential);
INSTANTIATE_UNARY_OP(RandomNumber);
INSTANTIATE_UNARY_OP(ToScalar);
INSTANTIATE_UNARY_OP(BitFieldExtract);
INSTANTIATE_UNARY_OP(Convert);
INSTANTIATE_UNARY_OP(Reinterpret);
}