diff --git a/mlir/include/mlir/ExecutionEngine/RunnerUtils.h b/mlir/include/mlir/ExecutionEngine/RunnerUtils.h --- a/mlir/include/mlir/ExecutionEngine/RunnerUtils.h +++ b/mlir/include/mlir/ExecutionEngine/RunnerUtils.h @@ -33,7 +33,9 @@ #include #include +#include #include +#include #include "mlir/ExecutionEngine/CRunnerUtils.h" @@ -229,6 +231,80 @@ printMemRef(DynamicMemRefType(m)); } +/// MemRefDataScanner struct declares the scanning API template to read in +/// input from a filestream into the memref passed. +template +struct MemRefDataScanner { + static void scan(std::fstream &fs, T *base, int64_t rank, int64_t offset, + const int64_t *sizes, const int64_t *strides); + static void scanFirst(std::fstream &fs, T *base, int64_t rank, int64_t offset, + const int64_t *sizes, const int64_t *strides); + static void scanLast(std::fstream &fs, T *base, int64_t rank, int64_t offset, + const int64_t *sizes, const int64_t *strides); +}; + +/// Helper function to scan the input file in a recursive structure that takes +/// into account the shape of the memref. +template +void MemRefDataScanner::scanFirst(std::fstream &fs, T *base, int64_t rank, + int64_t offset, const int64_t *sizes, + const int64_t *strides) { + scan(fs, base, rank - 1, offset, sizes + 1, strides + 1); + // If single element, return early. + if (sizes[0] <= 1) { + return; + } +} + +/// Helper function to scan the input file in a recursive structure that takes +/// into account the shape of the memref. +template +void MemRefDataScanner::scan(std::fstream &fs, T *base, int64_t rank, + int64_t offset, const int64_t *sizes, + const int64_t *strides) { + if (rank == 0) { + fs >> base[offset]; + return; + } + scanFirst(fs, base, rank, offset, sizes, strides); + for (unsigned i = 1; i + 1 < sizes[0]; ++i) { + scan(fs, base, rank - 1, offset + i * strides[0], sizes + 1, strides + 1); + } + if (sizes[0] <= 1) + return; + scanLast(fs, base, rank, offset, sizes, strides); +} + +/// Helper function to scan the input file in a recursive structure that takes +/// into account the shape of the memref. +template +void MemRefDataScanner::scanLast(std::fstream &fs, T *base, int64_t rank, + int64_t offset, const int64_t *sizes, + const int64_t *strides) { + scan(fs, base, rank - 1, offset + (sizes[0] - 1) * (*strides), sizes + 1, + strides + 1); +} + +extern "C" void openInputFile(unsigned fileNum, std::fstream &fs); + +/// Opens the file in filestream and scans the file into the memref passed. +template +static void scanMemRef(const DynamicMemRefType &M, unsigned fileNum) { + std::fstream fs; + openInputFile(fileNum, fs); + MemRefDataScanner::scan(fs, M.data, M.rank, M.offset, M.sizes, M.strides); + fs.close(); + std::cerr << "\nMemref scanning complete.\n"; +} + +/// Casts the UnrankedMemRef into DynamicMemRef and calls the main scanMemRef +/// function. +template +static void scanMemRef(UnrankedMemRefType &M, unsigned fileNum) { + std::cerr << "Scanning unranked memref:\n"; + scanMemRef(DynamicMemRefType(M), fileNum); +} + /// Verify the result of two computations are equivalent up to a small /// numerical error and return the number of errors. template @@ -384,6 +460,24 @@ extern "C" MLIR_RUNNERUTILS_EXPORT void _mlir_ciface_printMemrefVector4x4xf32( StridedMemRefType, 2> *m); +extern "C" MLIR_RUNNERUTILS_EXPORT void +_mlir_ciface_scanMemrefI32(UnrankedMemRefType *M, unsigned fileNum); +extern "C" MLIR_RUNNERUTILS_EXPORT void +_mlir_ciface_scanMemrefI64(UnrankedMemRefType *M, unsigned fileNum); +extern "C" MLIR_RUNNERUTILS_EXPORT void +_mlir_ciface_scanMemrefF32(UnrankedMemRefType *M, unsigned fileNum); +extern "C" MLIR_RUNNERUTILS_EXPORT void +_mlir_ciface_scanMemrefF64(UnrankedMemRefType *M, unsigned fileNum); + +extern "C" MLIR_RUNNERUTILS_EXPORT void scanMemrefI32(int64_t rank, void *ptr, + unsigned fileNum); +extern "C" MLIR_RUNNERUTILS_EXPORT void scanMemrefI64(int64_t rank, void *ptr, + unsigned fileNum); +extern "C" MLIR_RUNNERUTILS_EXPORT void scanMemrefF32(int64_t rank, void *ptr, + unsigned fileNum); +extern "C" MLIR_RUNNERUTILS_EXPORT void scanMemrefF64(int64_t rank, void *ptr, + unsigned fileNum); + extern "C" MLIR_RUNNERUTILS_EXPORT int64_t _mlir_ciface_verifyMemRefI32( UnrankedMemRefType *actual, UnrankedMemRefType *expected); extern "C" MLIR_RUNNERUTILS_EXPORT int64_t _mlir_ciface_verifyMemRefF32( diff --git a/mlir/lib/ExecutionEngine/JitRunner.cpp b/mlir/lib/ExecutionEngine/JitRunner.cpp --- a/mlir/lib/ExecutionEngine/JitRunner.cpp +++ b/mlir/lib/ExecutionEngine/JitRunner.cpp @@ -35,12 +35,20 @@ #include "llvm/Support/StringSaver.h" #include "llvm/Support/ToolOutputFile.h" #include +#include +#include #include +#include #include using namespace mlir; using llvm::Error; +/// Since `scan_memref` API will require the absolute path to the input +/// directory to extract input files, we will be storing the path obtained from +/// command line argument into an environment variable `INPUT_DIRECTORY_PATH`. +constexpr char kInputDirectoryPathEnv[] = "INPUT_DIRECTORY_PATH="; + namespace { /// This options struct prevents the need for global static initializers, and /// is only initialized if the JITRunner is invoked. @@ -86,6 +94,9 @@ llvm::cl::opt objectFilename{ "object-filename", llvm::cl::desc("Dump JITted-compiled object to file .o")}; + + llvm::cl::opt inputDirectoryPath{ + "input-dir", llvm::cl::desc("Input directory path"), llvm::cl::init(".")}; }; struct CompileAndExecuteConfig { @@ -362,6 +373,13 @@ .Case("void", compileAndExecuteVoidFunction) .Default(nullptr); + // Create/update an environment variable `INPUT_DIRECTORY_PATH` by setting + // it's value as the input directory path obtained from the command line + // option `inputDirectoryPath`. + std::string envKeyValuePair = + kInputDirectoryPathEnv + options.inputDirectoryPath.getValue(); + putenv(const_cast(envKeyValuePair.c_str())); + Error error = compileAndExecuteFn ? compileAndExecuteFn(options, m.get(), options.mainFuncName.getValue(), diff --git a/mlir/lib/ExecutionEngine/RunnerUtils.cpp b/mlir/lib/ExecutionEngine/RunnerUtils.cpp --- a/mlir/lib/ExecutionEngine/RunnerUtils.cpp +++ b/mlir/lib/ExecutionEngine/RunnerUtils.cpp @@ -14,10 +14,15 @@ //===----------------------------------------------------------------------===// #include "mlir/ExecutionEngine/RunnerUtils.h" +#include "llvm/ADT/StringExtras.h" #include // NOLINTBEGIN(*-identifier-naming) +/// The environment variable which contains the absolute path to the input +/// directory from which scanMemref* API will extract each input file. +constexpr char kInputDirectoryPathEnv[] = "INPUT_DIRECTORY_PATH"; + extern "C" void _mlir_ciface_printMemrefShapeI8(UnrankedMemRefType *M) { std::cout << "Unranked Memref "; printMemRefMetaData(std::cout, DynamicMemRefType(*M)); @@ -122,6 +127,62 @@ impl::printMemRef(*M); } +/// scanMemref* APIs need an integer argument 'fileNum'. Scans a file +/// identified by 'input.dat' and populates the memref passed with the +/// file content. + +/// A utility function which opens a file 'input.dat' whose path is +/// extracted from the environment variable identified by +/// `kInputDirectoryPathEnv`. It then returns the file stream back to the +/// caller. +extern "C" void openInputFile(unsigned fileNum, std::fstream &fs) { + std::string fileName = "/input" + llvm::itostr(fileNum) + ".dat"; + std::fstream inputDirFs; + std::string filePath = getenv(kInputDirectoryPathEnv); + filePath = filePath + fileName; + fs.open(filePath, std::fstream::in); +} + +extern "C" void _mlir_ciface_scanMemrefI32(UnrankedMemRefType *M, + unsigned fileNum) { + impl::scanMemRef(*M, fileNum); +} + +extern "C" void _mlir_ciface_scanMemrefI64(UnrankedMemRefType *M, + unsigned fileNum) { + impl::scanMemRef(*M, fileNum); +} + +extern "C" void _mlir_ciface_scanMemrefF32(UnrankedMemRefType *M, + unsigned fileNum) { + impl::scanMemRef(*M, fileNum); +} + +extern "C" void _mlir_ciface_scanMemrefF64(UnrankedMemRefType *M, + unsigned fileNum) { + impl::scanMemRef(*M, fileNum); +} + +extern "C" void scanMemrefI32(int64_t rank, void *ptr, unsigned fileNum) { + UnrankedMemRefType descriptor = {rank, ptr}; + _mlir_ciface_scanMemrefI32(&descriptor, fileNum); +} + +extern "C" void scanMemrefI64(int64_t rank, void *ptr, unsigned fileNum) { + UnrankedMemRefType descriptor = {rank, ptr}; + _mlir_ciface_scanMemrefI64(&descriptor, fileNum); +} + +extern "C" void scanMemrefF32(int64_t rank, void *ptr, unsigned fileNum) { + UnrankedMemRefType descriptor = {rank, ptr}; + _mlir_ciface_scanMemrefF32(&descriptor, fileNum); +} + +extern "C" void scanMemrefF64(int64_t rank, void *ptr, unsigned fileNum) { + UnrankedMemRefType descriptor = {rank, ptr}; + _mlir_ciface_scanMemrefF64(&descriptor, fileNum); +} + extern "C" int64_t _mlir_ciface_verifyMemRefI32(UnrankedMemRefType *actual, UnrankedMemRefType *expected) { diff --git a/mlir/test/mlir-cpu-runner/ExecutionTest/execution-test.mlir b/mlir/test/mlir-cpu-runner/ExecutionTest/execution-test.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/mlir-cpu-runner/ExecutionTest/execution-test.mlir @@ -0,0 +1,52 @@ +// RUN: mlir-opt -convert-linalg-to-loops -lower-affine -arith-expand \ +// RUN: -canonicalize -convert-scf-to-openmp -convert-scf-to-cf \ +// RUN: -canonicalize -convert-math-to-llvm -convert-vector-to-llvm \ +// RUN: -convert-memref-to-llvm -convert-openmp-to-llvm -canonicalize \ +// RUN: -convert-cf-to-llvm -reconcile-unrealized-casts -canonicalize %s | \ +// RUN: mlir-cpu-runner -O3 -e execution_test_driver -entry-point-result=void \ +// RUN: -input-dir=%mlir_src_root/test/mlir-cpu-runner/ExecutionTest \ +// RUN: -shared-libs=%mlir_runner_utils_dir/libmlir_runner_utils%shlibext,%mlir_runner_utils_dir/libmlir_c_runner_utils%shlibext | FileCheck %s + +// This test case demonstrates working of scanMemref* API in tandem with printMemref*. +module { + func.func private @printMemrefF32(memref<*xf32>) + func.func private @scanMemrefF32(memref<*xf32>, i32) + func.func @execution_test_driver() { + // Input arg0. + %0 = memref.alloc() : memref<10xf32> + %c0_i32 = arith.constant 0 : i32 + %1 = memref.cast %0 : memref<10xf32> to memref<*xf32> + call @scanMemrefF32(%1, %c0_i32) : (memref<*xf32>, i32) -> () + + // Input arg1. + %2 = memref.alloc() : memref<10xf32> + %c1_i32 = arith.constant 1 : i32 + %3 = memref.cast %2 : memref<10xf32> to memref<*xf32> + call @scanMemrefF32(%3, %c1_i32) : (memref<*xf32>, i32) -> () + + // Output arg2. + %4 = memref.alloc() : memref<10xf32> + + // Call user-defined function `getSum`. + call @getElementWiseSum(%0, %2, %4) : (memref<10xf32>, memref<10xf32>, memref<10xf32>) -> () + + // Print Output arg2. + %5 = memref.cast %4 : memref<10xf32> to memref<*xf32> + call @printMemrefF32(%5) : (memref<*xf32>) -> () + + return + } + func.func @getElementWiseSum(%arg0: memref<10xf32>, %arg1: memref<10xf32>, %arg2: memref<10xf32>) { + affine.for %arg3 = 0 to 10 { + %0 = affine.load %arg0[%arg3] : memref<10xf32> + %1 = affine.load %arg1[%arg3] : memref<10xf32> + %2 = arith.addf %0, %1 : f32 + affine.store %2, %arg2[%arg3] : memref<10xf32> + } + return + } +} +// %arg0 is initialized with input0.dat : 1 2 3 4 5 6 7 8 9 10 +// %arg1 is initialized with input1.dat : 11 12 13 14 15 16 17 18 19 20 +// Result for element-wise addition : +// CHECK: [12, 14, 16, 18, 20, 22, 24, 26, 28, 30] diff --git a/mlir/test/mlir-cpu-runner/ExecutionTest/input0.dat b/mlir/test/mlir-cpu-runner/ExecutionTest/input0.dat new file mode 100644 --- /dev/null +++ b/mlir/test/mlir-cpu-runner/ExecutionTest/input0.dat @@ -0,0 +1 @@ +1 2 3 4 5 6 7 8 9 10 diff --git a/mlir/test/mlir-cpu-runner/ExecutionTest/input1.dat b/mlir/test/mlir-cpu-runner/ExecutionTest/input1.dat new file mode 100644 --- /dev/null +++ b/mlir/test/mlir-cpu-runner/ExecutionTest/input1.dat @@ -0,0 +1 @@ +11 12 13 14 15 16 17 18 19 20