//===- MemRefUtils.h - Memref helpers to invoke MLIR JIT code ---*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Utils for MLIR ABI interfacing with frameworks. // // The templated free functions below make it possible to allocate dense // contiguous buffers with shapes that interoperate properly with the MLIR // codegen ABI. // //===----------------------------------------------------------------------===// #include "mlir/ExecutionEngine/CRunnerUtils.h" #include "mlir/Support/LLVM.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include #include #include #ifndef MLIR_EXECUTIONENGINE_MEMREFUTILS_H_ #define MLIR_EXECUTIONENGINE_MEMREFUTILS_H_ namespace mlir { using AllocFunType = llvm::function_ref; namespace detail { /// Given a shape with sizes greater than 0 along all dimensions, returns the /// distance, in number of elements, between a slice in a dimension and the next /// slice in the same dimension. /// e.g. shape[3, 4, 5] -> strides[20, 5, 1] template inline std::array makeStrides(ArrayRef shape) { assert(shape.size() == N && "expect shape specification to match rank"); std::array res; int64_t running = 1; for (int64_t idx = N - 1; idx >= 0; --idx) { assert(shape[idx] >= 0 && "size must be non-negative for all shape dimensions"); res[idx] = running; running *= shape[idx]; } return res; } /// Build a `StridedMemRefDescriptor` that matches the MLIR ABI. /// This is an implementation detail that is kept in sync with MLIR codegen /// conventions. Additionally takes a `shapeAlloc` array which /// is used instead of `shape` to allocate "more aligned" data and compute the /// corresponding strides. template typename std::enable_if<(N >= 1), StridedMemRefType>::type makeStridedMemRefDescriptor(T *ptr, T *alignedPtr, ArrayRef shape, ArrayRef shapeAlloc) { assert(shape.size() == N); assert(shapeAlloc.size() == N); StridedMemRefType descriptor; descriptor.basePtr = ptr; descriptor.data = alignedPtr; descriptor.offset = 0; std::copy(shape.begin(), shape.end(), descriptor.sizes); auto strides = makeStrides(shapeAlloc); std::copy(strides.begin(), strides.end(), descriptor.strides); return descriptor; } /// Build a `StridedMemRefDescriptor` that matches the MLIR ABI. /// This is an implementation detail that is kept in sync with MLIR codegen /// conventions. Additionally takes a `shapeAlloc` array which /// is used instead of `shape` to allocate "more aligned" data and compute the /// corresponding strides. template typename std::enable_if<(N == 0), StridedMemRefType>::type makeStridedMemRefDescriptor(T *ptr, T *alignedPtr, ArrayRef shape = {}, ArrayRef shapeAlloc = {}) { assert(shape.size() == N); assert(shapeAlloc.size() == N); StridedMemRefType descriptor; descriptor.basePtr = ptr; descriptor.data = alignedPtr; descriptor.offset = 0; return descriptor; } /// Align `nElements` of type T with an optional `alignment`. /// This replaces a portable `posix_memalign`. /// `alignment` must be a power of 2 and greater than the size of T. By default /// the alignment is sizeof(T). template std::pair allocAligned(size_t nElements, AllocFunType allocFun = &::malloc, std::optional alignment = std::optional()) { assert(sizeof(T) <= UINT_MAX && "Elemental type overflows"); auto size = nElements * sizeof(T); auto desiredAlignment = alignment.value_or(nextPowerOf2(sizeof(T))); assert((desiredAlignment & (desiredAlignment - 1)) == 0); assert(desiredAlignment >= sizeof(T)); T *data = reinterpret_cast(allocFun(size + desiredAlignment)); uintptr_t addr = reinterpret_cast(data); uintptr_t rem = addr % desiredAlignment; T *alignedData = (rem == 0) ? data : reinterpret_cast(addr + (desiredAlignment - rem)); assert(reinterpret_cast(alignedData) % desiredAlignment == 0); return std::make_pair(data, alignedData); } } // namespace detail //===----------------------------------------------------------------------===// // Public API //===----------------------------------------------------------------------===// /// Convenient callback to "visit" a memref element by element. /// This takes a reference to an individual element as well as the coordinates. /// It can be used in conjuction with a StridedMemrefIterator. template using ElementWiseVisitor = llvm::function_ref)>; /// Owning MemRef type that abstracts over the runtime type for ranked strided /// memref. template class OwningMemRef { public: using DescriptorType = StridedMemRefType; using FreeFunType = std::function; /// Allocate a new dense StridedMemrefRef with a given `shape`. An optional /// `shapeAlloc` array can be supplied to "pad" every dimension individually. /// If an ElementWiseVisitor is provided, it will be used to initialize the /// data, else the memory will be zero-initialized. The alloc and free method /// used to manage the data allocation can be optionally provided, and default /// to malloc/free. OwningMemRef( ArrayRef shape, ArrayRef shapeAlloc = {}, ElementWiseVisitor init = {}, std::optional alignment = std::optional(), AllocFunType allocFun = &::malloc, std::function)> freeFun = [](StridedMemRefType descriptor) { ::free(descriptor.basePtr); }) : freeFunc(freeFun) { if (shapeAlloc.empty()) shapeAlloc = shape; assert(shape.size() == Rank); assert(shapeAlloc.size() == Rank); for (unsigned i = 0; i < Rank; ++i) assert(shape[i] <= shapeAlloc[i] && "shapeAlloc must be greater than or equal to shape"); int64_t nElements = 1; for (int64_t s : shapeAlloc) nElements *= s; auto [allocatedPtr, alignedData] = detail::allocAligned(nElements, allocFun, alignment); descriptor = detail::makeStridedMemRefDescriptor( allocatedPtr, alignedData, shape, shapeAlloc); if (init) { for (StridedMemrefIterator it = descriptor.begin(), end = descriptor.end(); it != end; ++it) init(*it, it.getIndices()); } else { memset(alignedData, 0, nElements * sizeof(T)); } } /// Take ownership of an existing descriptor with a custom deleter. OwningMemRef(DescriptorType descriptor, FreeFunType freeFunc) : freeFunc(freeFunc), descriptor(descriptor) {} ~OwningMemRef() { if (freeFunc) freeFunc(descriptor); } OwningMemRef(const OwningMemRef &) = delete; OwningMemRef &operator=(const OwningMemRef &) = delete; OwningMemRef &operator=(const OwningMemRef &&other) { freeFunc = other.freeFunc; descriptor = other.descriptor; other.freeFunc = nullptr; memset(&other.descriptor, 0, sizeof(other.descriptor)); } OwningMemRef(OwningMemRef &&other) { *this = std::move(other); } DescriptorType &operator*() { return descriptor; } DescriptorType *operator->() { return &descriptor; } T &operator[](std::initializer_list indices) { return descriptor[indices]; } private: /// Custom deleter used to release the data buffer manager with the descriptor /// below. FreeFunType freeFunc; /// The descriptor is an instance of StridedMemRefType. DescriptorType descriptor; }; } // namespace mlir #endif // MLIR_EXECUTIONENGINE_MEMREFUTILS_H_