#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" #include "CheckedArithmetic.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp" using namespace mlir; namespace onnx_mlir::pim { namespace { static void emitCrashMessage(llvm::StringRef fieldName, llvm::StringRef message) { llvm::errs() << "PIM " << fieldName << " " << message << "\n"; } template static FailureOr checkedCastAtLocation(From value, Location loc, llvm::StringRef fieldName) { static_assert(std::is_integral_v && std::is_integral_v, "checkedCastAtLocation requires integral types"); using ToLimits = std::numeric_limits; if constexpr (std::is_signed_v == std::is_signed_v) { if (value < static_cast(ToLimits::min()) || value > static_cast(ToLimits::max())) { emitCheckedArithmeticError(loc, fieldName, "is outside representable range"); return failure(); } } else if constexpr (std::is_signed_v) { using UnsignedFrom = std::make_unsigned_t; using UnsignedTo = std::make_unsigned_t; if (value < 0 || static_cast(value) > static_cast(ToLimits::max())) { emitCheckedArithmeticError(loc, fieldName, "is outside representable range"); return failure(); } } else { using UnsignedFrom = std::make_unsigned_t; using UnsignedTo = std::conditional_t, std::make_unsigned_t, To>; if (static_cast(value) > static_cast(ToLimits::max())) { emitCheckedArithmeticError(loc, fieldName, "is outside representable range"); return failure(); } } return static_cast(value); } template FailureOr checkedMulAtLocation(UInt lhs, UInt rhs, Location loc, llvm::StringRef fieldName) { static_assert(std::is_integral_v && std::is_unsigned_v, "checkedMulAtLocation requires unsigned integral types"); if (lhs != 0 && rhs > std::numeric_limits::max() / lhs) { emitCheckedArithmeticError(loc, fieldName, "multiplication overflow"); return failure(); } return lhs * rhs; } } // namespace InFlightDiagnostic emitCheckedArithmeticError(Operation* anchor, llvm::StringRef fieldName, llvm::StringRef message) { assert(anchor && "expected arithmetic diagnostics to have an anchor op"); return anchor->emitOpError() << fieldName << " " << message; } InFlightDiagnostic emitCheckedArithmeticError(Location loc, llvm::StringRef fieldName, llvm::StringRef message) { return emitError(loc) << "PIM " << fieldName << " " << message; } FailureOr checkedI32(int64_t value, Operation* anchor, llvm::StringRef fieldName) { return checkedCast(value, anchor, fieldName); } FailureOr checkedI32(uint64_t value, Operation* anchor, llvm::StringRef fieldName) { return checkedCast(value, anchor, fieldName); } FailureOr checkedU8(uint64_t value, Operation* anchor, llvm::StringRef fieldName) { return checkedCast(value, anchor, fieldName); } FailureOr checkedSize(int64_t value, Operation* anchor, llvm::StringRef fieldName) { return checkedCast(value, anchor, fieldName); } FailureOr getCheckedI32Attr(Builder& builder, Operation* anchor, int64_t value, llvm::StringRef fieldName) { assert(anchor && "checked op-based attrs require a non-null diagnostic anchor"); auto checkedValue = checkedI32(value, anchor, fieldName); if (failed(checkedValue)) return failure(); return builder.getI32IntegerAttr(*checkedValue); } FailureOr getCheckedI32Attr(Builder& builder, Operation* anchor, uint64_t value, llvm::StringRef fieldName) { assert(anchor && "checked op-based attrs require a non-null diagnostic anchor"); auto checkedValue = checkedI32(value, anchor, fieldName); if (failed(checkedValue)) return failure(); return builder.getI32IntegerAttr(*checkedValue); } FailureOr getCheckedI32Attr(Builder& builder, Location loc, int64_t value, llvm::StringRef fieldName) { auto checkedValue = checkedCastAtLocation(value, loc, fieldName); if (failed(checkedValue)) return failure(); return builder.getI32IntegerAttr(*checkedValue); } FailureOr getCheckedI32Attr(Builder& builder, Location loc, uint64_t value, llvm::StringRef fieldName) { auto checkedValue = checkedCastAtLocation(value, loc, fieldName); if (failed(checkedValue)) return failure(); return builder.getI32IntegerAttr(*checkedValue); } FailureOr getCheckedShapedTypeSizeInBytes(ShapedType type, Operation* anchor, llvm::StringRef fieldName) { assert(anchor && "checked op-based size helpers require a non-null diagnostic anchor"); if (!type.hasStaticShape()) { emitCheckedArithmeticError(anchor, fieldName, "requires static shaped type"); return failure(); } if (!hasByteSizedElementType(type.getElementType())) { emitCheckedArithmeticError(anchor, fieldName, "requires byte-sized element type"); return failure(); } uint64_t elements = 1; for (int64_t dim : type.getShape()) { if (dim < 0) { emitCheckedArithmeticError(anchor, fieldName, "requires nonnegative dimensions"); return failure(); } auto nextElements = checkedMul(elements, static_cast(dim), anchor, fieldName); if (failed(nextElements)) return failure(); elements = *nextElements; } return checkedMul( elements, static_cast(getElementTypeSizeInBytes(type.getElementType())), anchor, fieldName); } FailureOr getCheckedShapedTypeSizeInBytes(ShapedType type, Location loc, llvm::StringRef fieldName) { if (!type.hasStaticShape()) { emitCheckedArithmeticError(loc, fieldName, "requires static shaped type"); return failure(); } if (!hasByteSizedElementType(type.getElementType())) { emitCheckedArithmeticError(loc, fieldName, "requires byte-sized element type"); return failure(); } uint64_t elements = 1; for (int64_t dim : type.getShape()) { if (dim < 0) { emitCheckedArithmeticError(loc, fieldName, "requires nonnegative dimensions"); return failure(); } auto nextElements = checkedMulAtLocation(elements, static_cast(dim), loc, fieldName); if (failed(nextElements)) return failure(); elements = *nextElements; } return checkedMulAtLocation( elements, static_cast(getElementTypeSizeInBytes(type.getElementType())), loc, fieldName); } int32_t checkedI32OrCrash(int64_t value, llvm::StringRef fieldName) { if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { emitCrashMessage(fieldName, "is outside representable range"); llvm_unreachable("PIM checked arithmetic failure"); } return static_cast(value); } int32_t checkedI32OrCrash(uint64_t value, llvm::StringRef fieldName) { if (value > static_cast(std::numeric_limits::max())) { emitCrashMessage(fieldName, "is outside representable range"); llvm_unreachable("PIM checked arithmetic failure"); } return static_cast(value); } uint8_t checkedU8OrCrash(uint64_t value, llvm::StringRef fieldName) { if (value > static_cast(std::numeric_limits::max())) { emitCrashMessage(fieldName, "is outside representable range"); llvm_unreachable("PIM checked arithmetic failure"); } return static_cast(value); } size_t checkedSizeOrCrash(int64_t value, llvm::StringRef fieldName) { if (value < 0) { emitCrashMessage(fieldName, "is outside representable range"); llvm_unreachable("PIM checked arithmetic failure"); } return static_cast(value); } size_t checkedAddOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName) { if (rhs > std::numeric_limits::max() - lhs) { emitCrashMessage(fieldName, "addition overflow"); llvm_unreachable("PIM checked arithmetic failure"); } return lhs + rhs; } size_t checkedMulOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName) { if (lhs != 0 && rhs > std::numeric_limits::max() / lhs) { emitCrashMessage(fieldName, "multiplication overflow"); llvm_unreachable("PIM checked arithmetic failure"); } return lhs * rhs; } } // namespace onnx_mlir::pim