.. _program_listing_file_lib_Target_EmitCToLaksaRef_EmitCToLaksaRef.cpp: Program Listing for File EmitCToLaksaRef.cpp ============================================ |exhale_lsh| :ref:`Return to documentation for file ` (``lib/Target/EmitCToLaksaRef/EmitCToLaksaRef.cpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp // Implementation of translation EmitCToLaksaRef // // @file // @author Jiahong Bi (jiahong.bi@tu-dresden.de) #include "laksa-mlir/Target/EmitCToLaksaRef/LaksaRefEmitter.h" #include "mlir/Dialect/EmitC/IR/EmitC.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/CommandLine.h" using namespace mlir; using namespace mlir::laksa; namespace { llvm::cl::opt clLaksaRefHeader( "laksa-ref-header", llvm::cl::desc( "Header the generated program takes the reference kernel from, as " "produced by the mlir-to-cpp translation"), llvm::cl::init("ref.h")); llvm::cl::opt clLaksaRefMaxReported( "laksa-ref-max-reported", llvm::cl::desc( "How many differing elements the generated program spells out before " "going quiet"), llvm::cl::init(16)); // One argument of the reference kernel. struct RefArg { emitc::ArrayType type; std::string elementType; std::string format; // printf conversion for one element std::string cast; // what to cast an element to before printing it bool isRead = false; bool isWritten = false; std::string name; std::string boardName; // holds output.bin, only when written std::string file; // input.bin, or output.bin when write-only std::string fileMacro; std::string boardFile; // output.bin when both read and written std::string boardFileMacro; }; // The C spelling the mlir-to-cpp translation gives an element type. FailureOr getCElementType(Type type) { if (auto intTy = dyn_cast(type)) { unsigned width = intTy.getWidth(); if (width == 1) return std::string("bool"); if (width == 8 || width == 16 || width == 32 || width == 64) return (intTy.isUnsigned() ? std::string("uint") : std::string("int")) + std::to_string(width) + "_t"; } if (isa(type)) return std::string("float"); if (isa(type)) return std::string("double"); return failure(); } // The only emitc.func with a body, which is the reference kernel. FailureOr findRefFunc(ModuleOp moduleOp) { emitc::FuncOp found; for (auto funcOp : moduleOp.getOps()) { if (funcOp.isExternal()) continue; if (found) return moduleOp.emitError( "expected exactly one emitc.func to check against"); found = funcOp; } if (!found) return moduleOp.emitError("expected an emitc.func to check against"); return found; } // Whether the kernel loads from arg, stores to it, or both. void classifyArg(BlockArgument arg, RefArg &refArg) { for (Operation* user : arg.getUsers()) { auto subscriptOp = dyn_cast(user); if (!subscriptOp || subscriptOp->getOperand(0) != arg) continue; Value element = subscriptOp->getResult(0); for (Operation* access : element.getUsers()) { if (isa(access)) refArg.isRead = true; if (auto assignOp = dyn_cast(access)) if (assignOp.getVar() == element) refArg.isWritten = true; } } } void emitArrayDecl(raw_ostream &os, const RefArg &arg, StringRef name) { os << "static " << arg.elementType << " " << name; for (int64_t extent : arg.type.getShape()) os << "[" << extent << "]"; os << ";\n"; } void emitReadAll(raw_ostream &os) { os << "static int read_all(const char* path, void* data, size_t size)\n" "{\n" " FILE* file = fopen(path, \"rb\");\n" " if (!file) {\n" " fprintf(stderr, \"cannot open %s for reading\\n\", path);\n" " return -1;\n" " }\n" " size_t got = fread(data, 1, size, file);\n" " int trailing = fgetc(file) != EOF;\n" " fclose(file);\n" " if (got != size) {\n" " fprintf(stderr, \"%s: expected %zu bytes, got %zu\\n\", " "path, size, got);\n" " return -1;\n" " }\n" " if (trailing)\n" " fprintf(stderr, \"%s: ignoring bytes past %zu\\n\", path, " "size);\n" " return 0;\n" "}\n"; } void emitElapsedMs(raw_ostream &os) { os << "static double elapsed_ms(struct timespec begin, struct timespec " "end)\n" "{\n" " return (end.tv_sec - begin.tv_sec) * 1e3\n" " + (end.tv_nsec - begin.tv_nsec) / 1e6;\n" "}\n"; } void emitCompare(raw_ostream &os, const RefArg &arg) { ArrayRef shape = arg.type.getShape(); int64_t numElements = 1; for (int64_t extent : shape) numElements *= extent; os << "static long compare_" << arg.name << "(void)\n{\n" << " long mismatches = 0;\n"; std::string indent = " "; for (auto [dim, extent] : llvm::enumerate(shape)) { os << indent << "for (size_t i" << dim << " = 0; i" << dim << " < " << extent << "; ++i" << dim << ") {\n"; indent += " "; } std::string subscript; for (size_t dim = 0; dim < shape.size(); ++dim) subscript += "[i" + std::to_string(dim) + "]"; os << indent << arg.cast << " want = " << arg.name << subscript << ";\n" << indent << arg.cast << " have = " << arg.boardName << subscript << ";\n" << indent << "if (want == have) continue;\n" << indent << "if (mismatches < MAX_REPORTED)\n" << indent << " printf(\"" << arg.name; for (size_t dim = 0; dim < shape.size(); ++dim) os << "[%zu]"; os << ": expected " << arg.format << ", got " << arg.format << "\\n\""; for (size_t dim = 0; dim < shape.size(); ++dim) os << ", i" << dim; os << ", want, have);\n" << indent << "else if (mismatches == MAX_REPORTED)\n" << indent << " printf(\"...\\n\");\n" << indent << "++mismatches;\n"; for (size_t dim = shape.size(); dim > 0; --dim) { indent.resize(indent.size() - 4); os << indent << "}\n"; } os << "\n" << " if (mismatches)\n" << " printf(" << arg.boardFileMacro << " \": %ld of " << numElements << " elements differ from the reference\\n\", " << "mismatches);\n" << " else\n" << " printf(" << arg.boardFileMacro << " \": all " << numElements << " elements match the reference\\n\");\n" << " return mismatches;\n" "}\n"; } } // namespace //===----------------------------------------------------------------------===// // Translation entry point //===----------------------------------------------------------------------===// LogicalResult laksa::translateEmitCToLaksaRef(ModuleOp op, raw_ostream &os) { FailureOr refFunc = findRefFunc(op); if (failed(refFunc)) return failure(); Block &body = refFunc->getFunctionBody().front(); SmallVector args(body.getNumArguments()); unsigned numInputs = 0; unsigned numOutputs = 0; for (auto [idx, blockArg] : llvm::enumerate(body.getArguments())) { RefArg &arg = args[idx]; arg.type = dyn_cast(blockArg.getType()); if (!arg.type) return refFunc->emitError("argument ") << idx << " is not an emitc.array"; FailureOr elementType = getCElementType(arg.type.getElementType()); if (failed(elementType)) return refFunc->emitError("argument ") << idx << " has an element type without a C spelling"; arg.elementType = *elementType; bool isFloat = isa(arg.type.getElementType()); arg.format = isFloat ? "%g" : "%ld"; arg.cast = isFloat ? "double" : "long"; classifyArg(blockArg, arg); if (!arg.isRead && !arg.isWritten) return refFunc->emitError("argument ") << idx << " is neither read nor written by the reference"; arg.name = "arg" + std::to_string(idx); std::string argMacro = "ARG" + std::to_string(idx); if (arg.isRead) { arg.file = "input" + std::to_string(numInputs++) + ".bin"; arg.fileMacro = argMacro + "_IN_FILE"; } if (arg.isWritten) { arg.boardName = arg.name + "_board"; arg.boardFile = "output" + std::to_string(numOutputs++) + ".bin"; arg.boardFileMacro = argMacro + "_OUT_FILE"; if (!arg.isRead) { arg.file = arg.boardFile; arg.fileMacro = arg.boardFileMacro; } } } StringRef kernelName = refFunc->getSymName(); bool renameKernel = kernelName == "main"; std::string kernelCall = renameKernel ? "ref" : kernelName.str(); os << "// Generated by ladle -t emitc-to-laksa-ref.\n" "// Checks what the design wrote on the board against the scalar " "reference in\n" "// \"" << clLaksaRefHeader << "\", generated by\n" "// ladle .mlir -t mlir-to-cpp -o " << clLaksaRefHeader << "\n" "//\n" "// Build, with \"" << clLaksaRefHeader << "\" next to this file:\n" "// gcc -O2 -I. -o ref ref.c\n" "//\n" "// Run it where app.c left its .bin files:\n" "// ./ref\n" "\n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "\n"; if (renameKernel) os << "// \"" << clLaksaRefHeader << "\" names the kernel 'main', the name it carries in the input\n" "// program, so rename it on the way in.\n" "#define main " << kernelCall << "\n"; os << "#include \"" << clLaksaRefHeader << "\"\n"; if (renameKernel) os << "#undef main\n"; os << "\n"; for (const RefArg &arg : args) { if (!arg.fileMacro.empty()) os << "#define " << arg.fileMacro << " \"" << arg.file << "\"\n"; if (arg.isRead && arg.isWritten) os << "#define " << arg.boardFileMacro << " \"" << arg.boardFile << "\"\n"; } os << "\n" "// How many differing elements to spell out before going quiet.\n" "#define MAX_REPORTED " << clLaksaRefMaxReported << "\n\n"; for (const RefArg &arg : args) { emitArrayDecl(os, arg, arg.name); if (arg.isWritten) emitArrayDecl(os, arg, arg.boardName); } os << "\n"; emitReadAll(os); os << "\n"; emitElapsedMs(os); for (const RefArg &arg : args) { if (!arg.isWritten) continue; os << "\n"; emitCompare(os, arg); } os << "\nint main(void)\n{\n"; for (const RefArg &arg : args) { if (arg.isRead) os << " if (read_all(" << arg.fileMacro << ", " << arg.name << ", sizeof(" << arg.name << ")) != 0) return EXIT_FAILURE;\n"; if (arg.isWritten) os << " if (read_all(" << arg.boardFileMacro << ", " << arg.boardName << ", sizeof(" << arg.boardName << ")) != 0) return EXIT_FAILURE;\n"; } os << "\n" " // Only the kernel is timed, reading the files and comparing " "is left out.\n" " struct timespec begin, end;\n" " clock_gettime(CLOCK_MONOTONIC_RAW, &begin);\n" " " << kernelCall << "("; llvm::interleaveComma(args, os, [&](const RefArg &arg) { os << arg.name; }); os << ");\n" " clock_gettime(CLOCK_MONOTONIC_RAW, &end);\n" " printf(\"reference: ran on the CPU in %.6f ms\\n\",\n" " elapsed_ms(begin, end));\n" "\n" " long mismatches = 0;\n"; for (const RefArg &arg : args) if (arg.isWritten) os << " mismatches += compare_" << arg.name << "();\n"; os << " return mismatches ? EXIT_FAILURE : EXIT_SUCCESS;\n" "}\n"; return success(); }