Files
TheRock/rocm-libraries/shared/rocroller/lib/source/ExpressionTransformations/FastDivision.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

571 lines
21 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/AssemblyKernel.hpp>
#include <rocRoller/Expression.hpp>
#include <rocRoller/ExpressionTransformations.hpp>
#include <rocRoller/Utilities/Error.hpp>
#include <rocRoller/Utilities/Logging.hpp>
#include <bit>
#define cast_to_unsigned(N) static_cast<typename std::make_unsigned<T>::type>(N)
namespace rocRoller
{
namespace Expression
{
/**
* Fast Division
*
* Attempt to replace division operations found within an expression with faster
* operations.
*/
std::tuple<ExpressionPtr, ExpressionPtr, ExpressionPtr, ExpressionPtr>
getMagicDivisionParams(ExpressionPtr denominator, ContextPtr context)
{
auto multiple = launchTimeSubExpressions(magicMultiple(denominator), context);
auto resultType = resultVariableType(denominator);
auto typeInfo = DataTypeInfo::Get(resultType);
if(!typeInfo.isSigned)
{
auto shifts = launchTimeSubExpressions(magicShifts(denominator), context);
return {multiple, shifts, nullptr, (shifts & literal(1u << 31u))};
}
auto bitfield = launchTimeSubExpressions(magicShiftAndSign(denominator), context);
auto mask = typeInfo.elementBits - 1;
auto shifts = bitfield & literal(mask);
// Sign is in the most significant bit of the least significant byte
// (i.e. bit 7). We want every bit to match bit 7.
// 1. Shift it left into the most significant bit.
// 2. Arithmetic shift right by the width of the type to sign
// extend into every bit.
int startingBit = 7;
int endingBit = typeInfo.elementBits - 1;
auto sign = (convert(resultType, bitfield) << literal((endingBit - startingBit)))
>> literal(endingBit);
return {multiple, shifts, sign, nullptr};
}
void enableDivideBy(ExpressionPtr expr, ContextPtr context)
{
expr = FastArithmetic(context)(expr);
auto resultType = resultVariableType(expr);
bool isSigned = DataTypeInfo::Get(resultType).isSigned;
AssertFatal(resultType == DataType::Int32 || resultType == DataType::Int64
|| resultType == DataType::UInt32,
ShowValue(resultType),
ShowValue(expr));
auto exprTimes = evaluationTimes(expr);
if(exprTimes[EvaluationTime::Translate])
{
Log::warn(
"Not adding arguments for division by {} due to translate-time evaluation.",
toString(expr));
return;
}
AssertFatal(exprTimes[EvaluationTime::KernelLaunch], ShowValue(exprTimes));
auto const& [magicExpr, numShiftsExpr, signExpr, numShiftsMSBExpr]
= getMagicDivisionParams(expr, context);
auto magicTimes = evaluationTimes(magicExpr);
auto shiftTimes = evaluationTimes(numShiftsExpr);
EvaluationTimes theTimes = magicTimes & shiftTimes;
AssertFatal(theTimes[EvaluationTime::KernelExecute],
ShowValue(magicTimes),
ShowValue(shiftTimes),
ShowValue(magicExpr),
ShowValue(numShiftsExpr));
if(isSigned)
{
AssertFatal(numShiftsMSBExpr == nullptr,
"Signed case does not need the MSB of magicShift");
AssertFatal(signExpr != nullptr);
auto signTimes = evaluationTimes(signExpr);
AssertFatal(signTimes[EvaluationTime::KernelExecute],
ShowValue(signTimes),
ShowValue(signExpr));
}
}
ExpressionPtr magicNumberDivision(ExpressionPtr numerator,
ExpressionPtr denominator,
ContextPtr context)
{
auto numeratorType = resultVariableType(numerator);
auto denominatorType = resultVariableType(denominator);
if(!(denominatorType == DataType::Int32 || denominatorType == DataType::Int64
|| denominatorType == DataType::UInt32))
{
// Unhandled case
return nullptr;
}
AssertFatal(
numeratorType.getElementSize() == denominatorType.getElementSize(),
"Can't mix 32-bit and 64-bit types in fast division, use a Convert expression.",
ShowValue(numeratorType),
ShowValue(denominatorType),
ShowValue(numerator),
ShowValue(denominator));
bool isSigned = DataTypeInfo::Get(denominatorType).isSigned;
auto k = context->kernel();
auto const& [magicExpr, numShiftsExpr, signExpr, numShiftsMSBExpr]
= getMagicDivisionParams(denominator, context);
{
EvaluationTimes evalTimes
= evaluationTimes(magicExpr) & evaluationTimes(numShiftsExpr);
if(!evalTimes[EvaluationTime::KernelExecute]
&& !evalTimes[EvaluationTime::Translate])
{
Log::debug("Returning nullptr from magicNumberDivision expr / shift "
"({})\nmagic: {}\n shift: {}",
toString(evalTimes),
toString(magicExpr),
toString(numShiftsExpr));
return nullptr;
}
}
ExpressionPtr result;
auto one = literal(1, denominatorType);
if(!isSigned)
{
auto q = multiplyHigh(numerator, magicExpr);
setComment(q, "Magic q (unsigned)");
auto t = (arithmeticShiftR(numerator - q, one)) + q;
setComment(t, "Magic t (unsigned)");
result = conditional(
numShiftsMSBExpr == literal(0u), arithmeticShiftR(t, numShiftsExpr), numerator);
setComment(result, "Magic result (unsigned)");
}
else
{
{
EvaluationTimes evalTimes = evaluationTimes(signExpr);
if(!evalTimes[EvaluationTime::KernelExecute]
&& !evalTimes[EvaluationTime::Translate])
{
Log::debug("Returning nullptr from magicNumberDivision sign ({})",
toString(evalTimes));
return nullptr;
}
}
// Create expression that performs division using the new arguments
auto numBytes = denominatorType.getElementSize();
// We are doing signed division; and in particular the
// magic-multiple might be negative. Make sure the
// numerator is signed so that the arithmetic
// generators for the expressions below (q etc) use
// signed instructions.
if(not DataTypeInfo::Get(numeratorType).isSigned)
{
auto signedNumeratorType
= getIntegerType(true, DataTypeInfo::Get(numeratorType).elementBytes);
numerator = std::make_shared<Expression>(
Convert{{.arg{numerator}}, signedNumeratorType});
}
auto q = multiplyHigh(numerator, magicExpr) + numerator;
setComment(q, "Magic q (signed)");
auto signOfQ = arithmeticShiftR(q, literal(numBytes * 8 - 1, denominatorType));
setComment(signOfQ, "Magic signOfQ");
auto magicIsPow2 = conditional(magicExpr == literal(0, denominatorType),
literal(-1, denominatorType),
literal(0, denominatorType));
setComment(magicIsPow2, "Magic isPow2");
auto handleSignOfLHS = q + (signOfQ & ((one << numShiftsExpr) + magicIsPow2));
setComment(handleSignOfLHS, "Magic handleSignOfLHS");
auto shiftedQ = arithmeticShiftR(handleSignOfLHS, numShiftsExpr);
setComment(shiftedQ, "Magic shiftedQ");
result = (shiftedQ ^ signExpr) - signExpr;
setComment(result, "Magic result (signed)");
}
result = launchTimeSubExpressions(simplify(result), context);
{
auto evalTimes = evaluationTimes(result);
AssertFatal(evalTimes[EvaluationTime::KernelExecute], toString(result), evalTimes);
}
return result;
}
template <typename T>
ExpressionPtr powerOfTwoDivision(ExpressionPtr lhs, T rhs)
{
throw std::runtime_error("Power Of 2 Division not supported for this type");
}
// Power Of Two division for unsigned integers
template <>
ExpressionPtr powerOfTwoDivision(ExpressionPtr lhs, unsigned int rhs)
{
uint shiftAmount = std::countr_zero(rhs);
auto new_rhs = literal(shiftAmount);
return arithmeticShiftR(lhs, new_rhs);
}
// Power of Two division for signed integers
template <>
ExpressionPtr powerOfTwoDivision(ExpressionPtr lhs, int rhs)
{
int shiftAmount = std::countr_zero(static_cast<unsigned int>(rhs));
unsigned int signBits = sizeof(int) * 8 - 1;
unsigned int reverseShiftAmount = sizeof(int) * 8 - shiftAmount;
auto shiftAmountExpr = literal(shiftAmount);
auto signBitsExpr = literal(signBits);
auto reverseShiftAmountExpr = literal(reverseShiftAmount);
return (lhs + logicalShiftR(lhs >> signBitsExpr, reverseShiftAmountExpr))
>> shiftAmountExpr;
}
template <typename T>
ExpressionPtr powerOfTwoModulo(ExpressionPtr lhs, T rhs)
{
throw std::runtime_error("Power Of 2 Modulo not supported for this type");
}
// Power of Two Modulo for unsigned integers
template <>
ExpressionPtr powerOfTwoModulo(ExpressionPtr lhs, unsigned int rhs)
{
unsigned int mask = rhs - 1u;
auto new_rhs = literal(mask);
return lhs & new_rhs;
}
// Power of Two Modulo for signed integers
template <>
ExpressionPtr powerOfTwoModulo(ExpressionPtr lhs, int rhs)
{
int shiftAmount = std::countr_zero(static_cast<unsigned int>(rhs));
unsigned int signBits = sizeof(int) * 8 - 1;
unsigned int reverseShiftAmount = sizeof(int) * 8 - shiftAmount;
int mask = ~(rhs - 1);
auto maskExpr = literal(mask);
auto signBitsExpr = literal(signBits);
auto reverseShiftAmountExpr = literal(reverseShiftAmount);
return lhs
- ((lhs + logicalShiftR(lhs >> signBitsExpr, reverseShiftAmountExpr))
& maskExpr);
}
struct DivisionByConstant
{
// Fast Modulo for when the divisor is a constant integer
template <typename T>
std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, ExpressionPtr>
operator()(T rhs)
{
if(rhs == 0)
{
throw std::runtime_error("Attempting to divide by 0 in expression");
}
else if(rhs == 1)
{
return m_lhs;
}
else if(rhs == -1)
{
return std::make_shared<Expression>(Multiply({m_lhs, literal(rhs)}));
}
// Power of 2 Division
else if(std::has_single_bit(cast_to_unsigned(rhs)))
{
return powerOfTwoDivision<T>(m_lhs, cast_to_unsigned(rhs));
}
else
{
auto rhsExpr = literal(rhs);
return magicNumberDivision(m_lhs, rhsExpr, m_context);
}
}
// If the divisor is not an integer, use the original Division operation
template <typename T>
std::enable_if_t<!std::is_integral_v<T> || std::is_same_v<T, bool>, ExpressionPtr>
operator()(T rhs)
{
return m_lhs / literal(rhs);
}
ExpressionPtr call(ExpressionPtr lhs, CommandArgumentValue rhs)
{
m_lhs = lhs;
return visit(*this, rhs);
}
DivisionByConstant(ContextPtr ctx)
: m_context(ctx)
{
}
private:
ContextPtr m_context;
ExpressionPtr m_lhs;
};
struct ModuloByConstant
{
// Fast Modulo for when the divisor is a constant integer
template <typename T>
std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, ExpressionPtr>
operator()(T rhs)
{
if(rhs == 0)
{
Throw<FatalError>("Attempting to perform modulo by 0 in expression");
}
else if(rhs == 1 || rhs == -1)
{
return literal<T>(0);
}
// Power of 2 Modulo
else if(std::has_single_bit(cast_to_unsigned(rhs)))
{
return powerOfTwoModulo(m_lhs, rhs);
}
else
{
auto rhsExpr = literal(rhs);
auto div = magicNumberDivision(m_lhs, rhsExpr, m_context);
return m_lhs - (div * rhsExpr);
}
}
// If the divisor is not an integer, use the original Modulo operation
template <typename T>
std::enable_if_t<!std::is_integral_v<T> || std::is_same_v<T, bool>, ExpressionPtr>
operator()(T rhs)
{
return m_lhs % literal(rhs);
}
ExpressionPtr call(ExpressionPtr lhs, CommandArgumentValue rhs)
{
m_lhs = lhs;
return visit(*this, rhs);
}
ModuloByConstant(ContextPtr ctx)
: m_context(ctx)
{
}
private:
ContextPtr m_context;
ExpressionPtr m_lhs;
};
struct FastDivisionExpressionVisitor
{
FastDivisionExpressionVisitor(ContextPtr cxt)
: m_context(cxt)
{
}
template <CUnary Expr>
ExpressionPtr operator()(Expr const& expr) const
{
Expr cpy = expr;
cpy.arg = call(expr.arg);
return std::make_shared<Expression>(cpy);
}
template <CBinary Expr>
ExpressionPtr operator()(Expr const& expr) const
{
Expr cpy = expr;
cpy.lhs = call(expr.lhs);
cpy.rhs = call(expr.rhs);
return std::make_shared<Expression>(cpy);
}
ExpressionPtr operator()(ScaledMatrixMultiply const& expr) const
{
ScaledMatrixMultiply cpy = expr;
cpy.matA = call(expr.matA);
cpy.matB = call(expr.matB);
cpy.matC = call(expr.matC);
cpy.scaleA = call(expr.scaleA);
cpy.scaleB = call(expr.scaleB);
return std::make_shared<Expression>(cpy);
}
template <CTernary Expr>
ExpressionPtr operator()(Expr const& expr) const
{
Expr cpy = expr;
cpy.lhs = call(expr.lhs);
cpy.r1hs = call(expr.r1hs);
cpy.r2hs = call(expr.r2hs);
return std::make_shared<Expression>(cpy);
}
template <CNary Expr>
ExpressionPtr operator()(Expr const& expr) const
{
auto cpy = expr;
std::ranges::for_each(cpy.operands, [this](auto& op) { op = call(op); });
return std::make_shared<Expression>(std::move(cpy));
}
ExpressionPtr operator()(Divide const& expr) const
{
auto lhs = call(expr.lhs);
auto rhs = call(expr.rhs);
auto rhsEvalTimes = evaluationTimes(rhs);
std::string extraComment;
// Obtain a CommandArgumentValue from rhs. If there is one,
// attempt to replace the division with faster operations.
if(rhsEvalTimes[EvaluationTime::Translate])
{
auto rhsVal = evaluate(rhs);
auto divByConst = DivisionByConstant(m_context);
auto rv = divByConst.call(lhs, rhsVal);
copyComment(rv, expr);
return rv;
}
auto rhsType = resultVariableType(rhs);
if(rhsEvalTimes[EvaluationTime::KernelLaunch])
{
auto div = magicNumberDivision(lhs, rhs, m_context);
if(div)
{
copyComment(div, expr);
return div;
}
if(expr.comment.find("magicNumberDivision") == std::string::npos)
{
extraComment = fmt::format(" (magicNumberDivision by {} returned nullptr)",
toString(rhs));
}
}
return std::make_shared<Expression>(Divide{lhs, rhs, expr.comment + extraComment});
}
ExpressionPtr operator()(Modulo const& expr) const
{
auto lhs = call(expr.lhs);
auto rhs = call(expr.rhs);
auto rhsEvalTimes = evaluationTimes(rhs);
std::string extraComment;
// Obtain a CommandArgumentValue from rhs. If there is one,
// attempt to replace the modulo with faster operations.
if(rhsEvalTimes[EvaluationTime::Translate])
{
auto rhsVal = evaluate(rhs);
auto modByConst = ModuloByConstant(m_context);
auto rv = modByConst.call(lhs, rhsVal);
copyComment(rv, expr);
return rv;
}
auto rhsType = resultVariableType(rhs);
if(rhsEvalTimes[EvaluationTime::KernelLaunch])
{
auto div = magicNumberDivision(lhs, rhs, m_context);
if(div)
{
auto rv = lhs - (div * rhs);
copyComment(rv, expr);
return rv;
}
if(expr.comment.find("magicNumberDivision") == std::string::npos)
{
extraComment = fmt::format(
" (modulo: magicNumberDivision returned nullptr)", toString(rhs));
}
}
return std::make_shared<Expression>(Modulo{lhs, rhs, expr.comment + extraComment});
}
template <CValue Value>
ExpressionPtr operator()(Value const& expr) const
{
return std::make_shared<Expression>(expr);
}
ExpressionPtr call(ExpressionPtr expr) const
{
if(!expr)
return expr;
auto rv = std::visit(*this, *expr);
Log::trace("visitor:\n {}\n {}", toString(expr), toString(rv));
return rv;
}
private:
ContextPtr m_context;
};
/**
* Attempts to use fastDivision for all of the divisions within an Expression.
*/
ExpressionPtr fastDivision(ExpressionPtr expr, ContextPtr cxt)
{
auto visitor = FastDivisionExpressionVisitor(cxt);
auto rv = visitor.call(expr);
Log::trace("fastDivision:\n {}\n {}", toString(expr), toString(rv));
return rv;
}
}
}