Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mlir] [Dialect] Add LibC dialect and printf op for CPU #92369

Closed
wants to merge 3 commits into from

Conversation

Menooker
Copy link

@Menooker Menooker commented May 16, 2024

Add a new dialect LibC to expose some useful features from CPU's standard C library. Currently, only printf is added. printf is particularly useful in debugging and testing. There is a gpu.printf op, but it is missing on CPU.

RFC discussion at https://discourse.llvm.org/t/rfc-new-dialect-to-expose-handy-utilities/79041

@llvmbot llvmbot added the mlir label May 16, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented May 16, 2024

@llvm/pr-subscribers-mlir

Author: Menooker (Menooker)

Changes

Add a new dialect LibC to expose some useful features from CPU's standard C library. Currently, only printf is added. printf is particularly useful in debugging and testing. There is a gpu.printf op, but it is missing on CPU.


Full diff: https://github.com/llvm/llvm-project/pull/92369.diff

16 Files Affected:

  • (added) mlir/include/mlir/Conversion/LibCToLLVM/LibCToLLVM.h (+27)
  • (modified) mlir/include/mlir/Conversion/Passes.h (+1)
  • (modified) mlir/include/mlir/Conversion/Passes.td (+13)
  • (modified) mlir/include/mlir/Dialect/CMakeLists.txt (+1)
  • (added) mlir/include/mlir/Dialect/LibC/CMakeLists.txt (+2)
  • (added) mlir/include/mlir/Dialect/LibC/LibC.td (+55)
  • (added) mlir/include/mlir/Dialect/LibC/LibCDialect.h (+27)
  • (modified) mlir/include/mlir/InitAllDialects.h (+2)
  • (modified) mlir/lib/Conversion/CMakeLists.txt (+1)
  • (added) mlir/lib/Conversion/LibCToLLVM/CMakeLists.txt (+18)
  • (added) mlir/lib/Conversion/LibCToLLVM/LibCToLLVM.cpp (+155)
  • (modified) mlir/lib/Dialect/CMakeLists.txt (+1)
  • (added) mlir/lib/Dialect/LibC/CMakeLists.txt (+14)
  • (added) mlir/lib/Dialect/LibC/LibCDialect.cpp (+31)
  • (added) mlir/test/Conversion/LibCToLLVM/printf-run.mlir (+17)
  • (added) mlir/test/Conversion/LibCToLLVM/printf-to-mlir.mlir (+19)
diff --git a/mlir/include/mlir/Conversion/LibCToLLVM/LibCToLLVM.h b/mlir/include/mlir/Conversion/LibCToLLVM/LibCToLLVM.h
new file mode 100644
index 0000000000000..0776c2debe410
--- /dev/null
+++ b/mlir/include/mlir/Conversion/LibCToLLVM/LibCToLLVM.h
@@ -0,0 +1,27 @@
+//===- LibCToLLVM.h - Utils to convert from the libc dialect --------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+#ifndef MLIR_CONVERSION_LIBCTOLLVM_LIBCTOLLVM_H_
+#define MLIR_CONVERSION_LIBCTOLLVM_LIBCTOLLVM_H_
+
+#include "mlir/IR/PatternMatch.h"
+
+namespace mlir {
+class LLVMTypeConverter;
+class RewritePatternSet;
+class Pass;
+#define GEN_PASS_DECL_CONVERTLIBCTOLLVMPASS
+#include "mlir/Conversion/Passes.h.inc"
+
+/// Populate the given list with patterns that convert from LibC to Func.
+void populateLibCToLLVMConversionPatterns(LLVMTypeConverter &converter,
+                                                RewritePatternSet &patterns);
+
+void registerConvertLibCToLLVMInterface(DialectRegistry &registry);
+} // namespace mlir
+
+#endif // MLIR_CONVERSION_LIBCTOLLVM_LIBCTOLLVM_H_
diff --git a/mlir/include/mlir/Conversion/Passes.h b/mlir/include/mlir/Conversion/Passes.h
index 2179ae18ac074..b63be36f14abf 100644
--- a/mlir/include/mlir/Conversion/Passes.h
+++ b/mlir/include/mlir/Conversion/Passes.h
@@ -40,6 +40,7 @@
 #include "mlir/Conversion/GPUToVulkan/ConvertGPUToVulkanPass.h"
 #include "mlir/Conversion/IndexToLLVM/IndexToLLVM.h"
 #include "mlir/Conversion/IndexToSPIRV/IndexToSPIRV.h"
+#include "mlir/Conversion/LibCToLLVM/LibCToLLVM.h"
 #include "mlir/Conversion/LinalgToStandard/LinalgToStandard.h"
 #include "mlir/Conversion/MathToFuncs/MathToFuncs.h"
 #include "mlir/Conversion/MathToLLVM/MathToLLVM.h"
diff --git a/mlir/include/mlir/Conversion/Passes.td b/mlir/include/mlir/Conversion/Passes.td
index e6d678dc1b12b..5244ba415878e 100644
--- a/mlir/include/mlir/Conversion/Passes.td
+++ b/mlir/include/mlir/Conversion/Passes.td
@@ -1356,4 +1356,17 @@ def ConvertVectorToSPIRV : Pass<"convert-vector-to-spirv"> {
   let dependentDialects = ["spirv::SPIRVDialect"];
 }
 
+
+//===----------------------------------------------------------------------===//
+// LibCToLLVM
+//===----------------------------------------------------------------------===//
+
+def ConvertLibCToLLVMPass: Pass<"convert-libc-to-llvm"> {
+  let summary = "Convert libc to LLVM dialect";
+  let description = [{
+    This pass converts supported libc ops to LLVM dialect ops.
+  }];
+  let dependentDialects = ["LLVM::LLVMDialect"];
+}
+
 #endif // MLIR_CONVERSION_PASSES
diff --git a/mlir/include/mlir/Dialect/CMakeLists.txt b/mlir/include/mlir/Dialect/CMakeLists.txt
index 4bd7f12fabf7b..5589c47aa0809 100644
--- a/mlir/include/mlir/Dialect/CMakeLists.txt
+++ b/mlir/include/mlir/Dialect/CMakeLists.txt
@@ -15,6 +15,7 @@ add_subdirectory(Func)
 add_subdirectory(GPU)
 add_subdirectory(Index)
 add_subdirectory(IRDL)
+add_subdirectory(LibC)
 add_subdirectory(Linalg)
 add_subdirectory(LLVMIR)
 add_subdirectory(Math)
diff --git a/mlir/include/mlir/Dialect/LibC/CMakeLists.txt b/mlir/include/mlir/Dialect/LibC/CMakeLists.txt
new file mode 100644
index 0000000000000..80b2fea4aa50f
--- /dev/null
+++ b/mlir/include/mlir/Dialect/LibC/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_mlir_dialect(LibC libc)
+add_mlir_doc(LibC LibC Dialects/ -gen-dialect-doc -dialect=libc)
\ No newline at end of file
diff --git a/mlir/include/mlir/Dialect/LibC/LibC.td b/mlir/include/mlir/Dialect/LibC/LibC.td
new file mode 100644
index 0000000000000..e27e813e5d52c
--- /dev/null
+++ b/mlir/include/mlir/Dialect/LibC/LibC.td
@@ -0,0 +1,55 @@
+//===-- AMX.td - AMX dialect operation definitions *- tablegen -*----------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the basic operations for the libc dialect.
+//
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_LIBC_LIBC
+#define MLIR_DIALECT_LIBC_LIBC
+
+include "mlir/Dialect/LLVMIR/LLVMOpBase.td"
+include "mlir/Interfaces/SideEffectInterfaces.td"
+
+//===----------------------------------------------------------------------===//
+// LibC dialect definition.
+//===----------------------------------------------------------------------===//
+
+def LibC_Dialect : Dialect {
+  let name = "libc";
+  let cppNamespace = "::mlir::libc";
+  let description = [{
+ The dialect provides interfaces for the standard C library functions, to
+ enable users to call them in CPU environments if a valid standard C library
+ (like glibc) is linked. The dialect's ops are designed to be lowered to
+ function calls to the corresponding LibC functions.
+  }];
+}
+
+class LibC_Op<string mnemonic, list<Trait> traits = []> :
+  Op<LibC_Dialect, mnemonic, traits> {}
+
+def LibC_PrintfOp : LibC_Op<"printf", [MemoryEffects<[MemWrite]>]>,
+  Arguments<(ins StrAttr:$format,
+                Variadic<AnyTypeOf<[AnyInteger, Index, AnyFloat]>>:$args)> {
+  let summary = "C-style printf";
+  let description = [{
+    `cpuruntime.printf` takes a literal format string `format` and an arbitrary
+    number of scalar arguments that should be printed.
+
+    The format string is a C-style printf string, subject to any restrictions
+    imposed by the target platform.
+  }];
+  let assemblyFormat = [{
+    $format attr-dict ($args^ `:` type($args))?
+  }];
+}
+
+
+#endif // MLIR_DIALECT_LIBC_LIBC
diff --git a/mlir/include/mlir/Dialect/LibC/LibCDialect.h b/mlir/include/mlir/Dialect/LibC/LibCDialect.h
new file mode 100644
index 0000000000000..98baab3387dba
--- /dev/null
+++ b/mlir/include/mlir/Dialect/LibC/LibCDialect.h
@@ -0,0 +1,27 @@
+//===- LibCDialect.h - MLIR Dialect for LibC ---------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file declares the dialect for LibC in MLIR.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_LIBC_LIBCDIALECT_H_
+#define MLIR_DIALECT_LIBC_LIBCDIALECT_H_
+
+#include "mlir/Bytecode/BytecodeOpInterface.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Dialect.h"
+#include "mlir/IR/OpDefinition.h"
+#include "mlir/Interfaces/SideEffectInterfaces.h"
+
+#include "mlir/Dialect/LibC/LibCDialect.h.inc"
+
+#define GET_OP_CLASSES
+#include "mlir/Dialect/LibC/LibC.h.inc"
+
+#endif // MLIR_DIALECT_LIBC_LIBCDIALECT_H_
diff --git a/mlir/include/mlir/InitAllDialects.h b/mlir/include/mlir/InitAllDialects.h
index d9db21073e15c..da36d9f9aa057 100644
--- a/mlir/include/mlir/InitAllDialects.h
+++ b/mlir/include/mlir/InitAllDialects.h
@@ -43,6 +43,7 @@
 #include "mlir/Dialect/LLVMIR/LLVMDialect.h"
 #include "mlir/Dialect/LLVMIR/NVVMDialect.h"
 #include "mlir/Dialect/LLVMIR/ROCDLDialect.h"
+#include "mlir/Dialect/LibC/LibCDialect.h"
 #include "mlir/Dialect/Linalg/IR/Linalg.h"
 #include "mlir/Dialect/Linalg/Transforms/AllInterfaces.h"
 #include "mlir/Dialect/Linalg/Transforms/RuntimeOpVerification.h"
@@ -121,6 +122,7 @@ inline void registerAllDialects(DialectRegistry &registry) {
                   gpu::GPUDialect,
                   index::IndexDialect,
                   irdl::IRDLDialect,
+                  libc::LibCDialect,
                   linalg::LinalgDialect,
                   LLVM::LLVMDialect,
                   math::MathDialect,
diff --git a/mlir/lib/Conversion/CMakeLists.txt b/mlir/lib/Conversion/CMakeLists.txt
index 41ab7046b91ce..66178b122745e 100644
--- a/mlir/lib/Conversion/CMakeLists.txt
+++ b/mlir/lib/Conversion/CMakeLists.txt
@@ -29,6 +29,7 @@ add_subdirectory(GPUToSPIRV)
 add_subdirectory(GPUToVulkan)
 add_subdirectory(IndexToLLVM)
 add_subdirectory(IndexToSPIRV)
+add_subdirectory(LibCToLLVM)
 add_subdirectory(LinalgToStandard)
 add_subdirectory(LLVMCommon)
 add_subdirectory(MathToFuncs)
diff --git a/mlir/lib/Conversion/LibCToLLVM/CMakeLists.txt b/mlir/lib/Conversion/LibCToLLVM/CMakeLists.txt
new file mode 100644
index 0000000000000..f73506a22211a
--- /dev/null
+++ b/mlir/lib/Conversion/LibCToLLVM/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_mlir_conversion_library(MLIRLibCToLLVM
+  LibCToLLVM.cpp
+
+  ADDITIONAL_HEADER_DIRS
+  ${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/LibCToLLVM
+
+  DEPENDS
+  MLIRConversionPassIncGen
+
+  LINK_COMPONENTS
+  Core
+
+  LINK_LIBS PUBLIC
+  MLIRLLVMCommonConversion
+  MLIRLLVMDialect
+  MLIRLibCDialect
+  MLIRPass
+  )
diff --git a/mlir/lib/Conversion/LibCToLLVM/LibCToLLVM.cpp b/mlir/lib/Conversion/LibCToLLVM/LibCToLLVM.cpp
new file mode 100644
index 0000000000000..29fd57e1653cd
--- /dev/null
+++ b/mlir/lib/Conversion/LibCToLLVM/LibCToLLVM.cpp
@@ -0,0 +1,155 @@
+//===-- LibCToLLVM.cpp - conversion from LibC to Func calls ---------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Conversion/LibCToLLVM/LibCToLLVM.h"
+#include "mlir/Conversion/ConvertToLLVM/ToLLVMInterface.h"
+#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
+#include "mlir/Conversion/LLVMCommon/Pattern.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/Dialect/LibC/LibCDialect.h"
+#include "mlir/IR/PatternMatch.h"
+#include "mlir/IR/TypeUtilities.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Rewrite/FrozenRewritePatternSet.h"
+#include "mlir/Support/LogicalResult.h"
+#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
+
+namespace mlir {
+
+void populateLibCToLLVMConversionPatterns(LLVMTypeConverter &converter,
+                                          RewritePatternSet &patterns);
+
+#define GEN_PASS_DEF_CONVERTLIBCTOLLVMPASS
+#include "mlir/Conversion/Passes.h.inc"
+
+namespace {
+static const char formatStringPrefix[] = "cpuprintfFormat_";
+
+static LLVM::LLVMFuncOp getOrDefineFunction(ModuleOp &moduleOp,
+                                            const Location loc,
+                                            ConversionPatternRewriter &rewriter,
+                                            StringRef name,
+                                            LLVM::LLVMFunctionType type) {
+  LLVM::LLVMFuncOp ret;
+  if (!(ret = moduleOp.template lookupSymbol<LLVM::LLVMFuncOp>(name))) {
+    ConversionPatternRewriter::InsertionGuard guard(rewriter);
+    rewriter.setInsertionPointToStart(moduleOp.getBody());
+    ret = rewriter.create<LLVM::LLVMFuncOp>(loc, name, type,
+                                            LLVM::Linkage::External);
+  }
+  return ret;
+}
+
+class PrintfRewriter : public ConvertOpToLLVMPattern<libc::PrintfOp> {
+public:
+  using ConvertOpToLLVMPattern<libc::PrintfOp>::ConvertOpToLLVMPattern;
+  LogicalResult
+  matchAndRewrite(libc::PrintfOp op, libc::PrintfOpAdaptor adaptor,
+                  ConversionPatternRewriter &rewriter) const final {
+    auto moduleOp = op->getParentOfType<ModuleOp>();
+    auto loc = op->getLoc();
+    mlir::Type llvmI32 = typeConverter->convertType(rewriter.getI32Type());
+    mlir::Type llvmI64 = typeConverter->convertType(rewriter.getI64Type());
+    mlir::Type llvmI8 = typeConverter->convertType(rewriter.getI8Type());
+    mlir::Type i8Ptr = LLVM::LLVMPointerType::get(op.getContext());
+    auto printfFunc = getOrDefineFunction(
+        moduleOp, loc, rewriter, "printf",
+        LLVM::LLVMFunctionType::get(llvmI32, {i8Ptr}, /*isVarArg*/ true));
+
+    unsigned stringNumber = 0;
+    SmallString<16> stringConstName;
+    do {
+      stringConstName.clear();
+      (formatStringPrefix + Twine(stringNumber++)).toStringRef(stringConstName);
+    } while (moduleOp.lookupSymbol(stringConstName));
+
+    llvm::SmallString<20> formatString(adaptor.getFormat());
+    formatString.push_back('\0'); // Null terminate for C
+    size_t formatStringSize = formatString.size_in_bytes();
+
+    auto globalType = LLVM::LLVMArrayType::get(llvmI8, formatStringSize);
+    LLVM::GlobalOp global;
+    {
+      ConversionPatternRewriter::InsertionGuard guard(rewriter);
+      rewriter.setInsertionPointToStart(moduleOp.getBody());
+      global = rewriter.create<LLVM::GlobalOp>(
+          loc, globalType,
+          /*isConstant=*/true, LLVM::Linkage::Internal, stringConstName,
+          rewriter.getStringAttr(formatString));
+    }
+    Value globalPtr = rewriter.create<LLVM::AddressOfOp>(
+        loc,
+        LLVM::LLVMPointerType::get(rewriter.getContext(),
+                                   global.getAddrSpace()),
+        global.getSymNameAttr());
+    Value stringStart = rewriter.create<LLVM::GEPOp>(
+        loc, i8Ptr, globalType, globalPtr, ArrayRef<LLVM::GEPArg>{0, 0});
+    SmallVector<Value, 5> appendFormatArgs = {stringStart};
+    for (auto arg : adaptor.getArgs()) {
+      if (auto floatType = dyn_cast<FloatType>(arg.getType())) {
+        if (!floatType.isF64())
+          arg = rewriter.create<LLVM::FPExtOp>(
+              loc, typeConverter->convertType(rewriter.getF64Type()), arg);
+      }
+      if (arg.getType().getIntOrFloatBitWidth() != 64)
+        arg = rewriter.create<LLVM::ZExtOp>(loc, llvmI64, arg);
+      appendFormatArgs.push_back(arg);
+    }
+    rewriter.create<LLVM::CallOp>(loc, printfFunc, appendFormatArgs);
+    rewriter.eraseOp(op);
+    return success();
+  }
+};
+
+class ConvertLibCToLLVMPass
+    : public impl::ConvertLibCToLLVMPassBase<ConvertLibCToLLVMPass> {
+public:
+  using Base::Base;
+  void runOnOperation() final {
+    LLVMConversionTarget target(getContext());
+    RewritePatternSet patterns(&getContext());
+    LowerToLLVMOptions options(&getContext());
+    LLVMTypeConverter converter(&getContext(), options);
+    populateLibCToLLVMConversionPatterns(converter, patterns);
+
+    if (failed(applyPartialConversion(getOperation(), target,
+                                      std::move(patterns))))
+      signalPassFailure();
+  }
+};
+
+/// Implement the interface to convert MemRef to LLVM.
+struct ConvertLibCToDialectInterface : public ConvertToLLVMPatternInterface {
+  using ConvertToLLVMPatternInterface::ConvertToLLVMPatternInterface;
+  void loadDependentDialects(MLIRContext *context) const final {
+    context->loadDialect<LLVM::LLVMDialect>();
+  }
+
+  /// Hook for derived dialect interface to provide conversion patterns
+  /// and mark dialect legal for the conversion target.
+  void populateConvertToLLVMConversionPatterns(
+      ConversionTarget &target, LLVMTypeConverter &typeConverter,
+      RewritePatternSet &patterns) const final {
+    populateLibCToLLVMConversionPatterns(typeConverter, patterns);
+  }
+};
+
+} // namespace
+
+void populateLibCToLLVMConversionPatterns(LLVMTypeConverter &converter,
+                                          RewritePatternSet &patterns) {
+  patterns.add<PrintfRewriter>(converter);
+}
+
+void registerConvertLibCToLLVMInterface(DialectRegistry &registry) {
+  registry.addExtension(+[](MLIRContext *ctx, libc::LibCDialect *dialect) {
+    dialect->addInterfaces<ConvertLibCToDialectInterface>();
+  });
+}
+
+} // namespace mlir
diff --git a/mlir/lib/Dialect/CMakeLists.txt b/mlir/lib/Dialect/CMakeLists.txt
index a324ce7f9b19f..684bbb485f877 100644
--- a/mlir/lib/Dialect/CMakeLists.txt
+++ b/mlir/lib/Dialect/CMakeLists.txt
@@ -15,6 +15,7 @@ add_subdirectory(Func)
 add_subdirectory(GPU)
 add_subdirectory(Index)
 add_subdirectory(IRDL)
+add_subdirectory(LibC)
 add_subdirectory(Linalg)
 add_subdirectory(LLVMIR)
 add_subdirectory(Math)
diff --git a/mlir/lib/Dialect/LibC/CMakeLists.txt b/mlir/lib/Dialect/LibC/CMakeLists.txt
new file mode 100644
index 0000000000000..faefaba7e05cb
--- /dev/null
+++ b/mlir/lib/Dialect/LibC/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_mlir_dialect_library(MLIRLibCDialect
+  LibCDialect.cpp
+
+  ADDITIONAL_HEADER_DIRS
+  ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/LibC
+
+  DEPENDS
+  MLIRLibCIncGen
+
+  LINK_LIBS PUBLIC
+  MLIRIR
+  MLIRFuncDialect
+  MLIRSideEffectInterfaces
+  )
diff --git a/mlir/lib/Dialect/LibC/LibCDialect.cpp b/mlir/lib/Dialect/LibC/LibCDialect.cpp
new file mode 100644
index 0000000000000..7b6a710398879
--- /dev/null
+++ b/mlir/lib/Dialect/LibC/LibCDialect.cpp
@@ -0,0 +1,31 @@
+//===- LibCDialect.cpp - MLIR LibC ops implementation -----------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the LibC dialect and its operations.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/LibC/LibCDialect.h"
+#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/OpImplementation.h"
+#include "mlir/IR/TypeUtilities.h"
+
+using namespace mlir;
+
+#include "mlir/Dialect/LibC/LibCDialect.cpp.inc"
+
+void libc::LibCDialect::initialize() {
+  addOperations<
+#define GET_OP_LIST
+#include "mlir/Dialect/LibC/LibC.cpp.inc"
+      >();
+}
+
+#define GET_OP_CLASSES
+#include "mlir/Dialect/LibC/LibC.cpp.inc"
diff --git a/mlir/test/Conversion/LibCToLLVM/printf-run.mlir b/mlir/test/Conversion/LibCToLLVM/printf-run.mlir
new file mode 100644
index 0000000000000..0b93558f39bf0
--- /dev/null
+++ b/mlir/test/Conversion/LibCToLLVM/printf-run.mlir
@@ -0,0 +1,17 @@
+// RUN: mlir-opt %s --convert-libc-to-llvm --convert-func-to-llvm --convert-arith-to-llvm --convert-cf-to-llvm | mlir-cpu-runner -e main -entry-point-result=void -shared-libs=%mlir_runner_utils,%mlir_c_runner_utils | FileCheck %s
+
+module {
+  func.func @doprint(%t: f32, %t2: i32, %t3: i64) {
+    libc.printf "Hello world %f %d %lld\n" %t, %t2, %t3 : f32, i32, i64
+    return
+  }
+
+  func.func @main() {
+    %c2 = arith.constant 2.0 : f32
+    %c32i = arith.constant 2000000 : i32
+    %c64i = arith.constant 2000000 : i64
+    call @doprint(%c2, %c32i, %c64i) : (f32, i32, i64) -> ()
+    return
+  }
+  // CHECK: Hello world 2.000000 2000000 2000000
+}
\ No newline at end of file
diff --git a/mlir/test/Conversion/LibCToLLVM/printf-to-mlir.mlir b/mlir/test/Conversion/LibCToLLVM/printf-to-mlir.mlir
new file mode 100644
index 0000000000000..67cb2444849a0
--- /dev/null
+++ b/mlir/test/Conversion/LibCToLLVM/printf-to-mlir.mlir
@@ -0,0 +1,19 @@
+// RUN: mlir-opt %s --convert-libc-to-llvm | FileCheck %s
+
+module {
+  // CHECK: llvm.mlir.global internal constant @cpuprintfFormat_0("Hello world %f %d %lld\0A\00") {addr_space = 0 : i32}
+  // CHECK: llvm.func @printf(!llvm.ptr,
+  // CHECK-NEXT: func.func @doprint(%[[ARG0:.*]]: f32, %[[ARG1:.*]]: i32, %[[ARG2:.*]]: i64)
+  func.func @doprint(%t: f32, %t2: i32, %t3: i64) {
+    // CHECK-NEXT: llvm.mlir.addressof
+    // CHECK-DAG: %[[C1:.*]] = llvm.getelementptr
+    // CHECK-SAME: !llvm.ptr, !llvm.array<24 x i8>
+    // CHECK: %[[C2:.*]] = llvm.fpext %[[ARG0]] 
+    // CHECK: %[[C3:.*]] = llvm.zext %[[ARG1]] 
+    // CHECK-NOT: libc.printf
+    // CHECK-NEXT: llvm.call @printf(%[[C1]], %[[C2]], %[[C3]], %[[ARG2]])
+    libc.printf "Hello world %f %d %lld\n" %t, %t2, %t3 : f32, i32, i64
+    return
+  }
+
+}
\ No newline at end of file

@Menooker Menooker requested a review from ZhennanQin May 16, 2024 08:55
Copy link

github-actions bot commented May 16, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@Menooker
Copy link
Author

Maybe we can remove the some of the uses of CRunnerUtils in the future, when MLIR cpu-runner can access the libc functions?

@rengolin rengolin requested review from ftynse and joker-eph May 16, 2024 09:38
Copy link
Member

@rengolin rengolin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, we already have some printing primitives.

I don't mind adding printf, but adding a whole dialect just for this opens a dialect discussion that is far wider than this one function.

  • Can we add this to the existing primitives?
  • Do we need a whole libc dialect? Perhaps c++ too?
  • Will this help Clang with CIL? Do they already have something like this?

I'd suggest you try the easy route with the current primitives and see what that gets you.

Some nits on the code. Also, your tests are not enough. You need to cover the functionality you add, including the error messages that you expect to get on a negative test.

@@ -0,0 +1,55 @@
//===-- AMX.td - AMX dialect operation definitions *- tablegen -*----------===//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

Variadic<AnyTypeOf<[AnyInteger, Index, AnyFloat]>>:$args)> {
let summary = "C-style printf";
let description = [{
`cpuruntime.printf` takes a literal format string `format` and an arbitrary
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`cpuruntime.printf` takes a literal format string `format` and an arbitrary
`libc.printf` takes a literal format string `format` and an arbitrary

@Menooker
Copy link
Author

Thanks for @rengolin for the view!

Can we add this to the existing primitives?

What I would like to do is to add the functionality of printing values in MLIR. It would be very helpful when debugging and writing tests, since users can print arithmetic values any where in the MLIR code. Also developers can use filecheck on the outputs by printf. The printing primitives you mentioned does not support formatting and need to declare the functions before using, which is not easy to use in IR.

Do we need a whole libc dialect? Perhaps c++ too?

Other interesting features in libc I know will be the ability of assert and abort. We may introduce them as Ops in LibC dialect too. assert is a C-macro, but I think it is a good-to-have in MLIR, for example, to help debug the program in "-O0" mode, with printing the Source Location . Based on it, some other features can be implemened. Memref dialect can use libc.assert to make sure the user-passed memref in func args have the claimed rank and dimensions at the entry. Another useful feature based on libc.assert is the boundary checking on memory accesses on memref. I am not sure if these features are already in MLIR or not.

C++ has complex ABI issues and many of its useful features are based on templates. I don't think we need libcpp dialect for now.

adding a whole dialect just for this opens a dialect discussion that is far wider than this one function.

I totally understand that it is overkill to add a dialect for a single Op. My original proposal is to add an printf op for CPU. I have found an gpu.printf. There is no reason not having a CPU one, since it is easier to implement and equally useful. However, there is no such cpu dialect to hold the printf op, and there seems nowhere to place such op. That's why I added this dialect.

After more searching, I found that in vector dialect, there is vector.print which can print scalar values and etc. And this op lowers to printF32 or other functions in CRunnerUtils on CPU. So there may be 3 ways for me to introduce this printf for CPU:

  1. Add to vector dialect, as vector.printf. However, it seems a bit strange to put such op in vector dialect.
  2. Instead of introducing libc dialect, introduce a new print dialect. Besides print.printf, we can introduce print.printMemref (print.printTensor also?)
  3. Introduce libc dialect. Add more Ops into it, like abort and assert discussed above. Or we may call it debug dialect, for debugging purpose ops?

Some nits on the code. Also, your tests are not enough. You need to cover the functionality you add, including the error messages that you expect to get on a negative test.

Thanks for pointing out! I am an MLIR beginner here. Will improve them! :)

@rengolin
Copy link
Member

The printing primitives you mentioned does not support formatting and need to declare the functions before using, which is not easy to use in IR.

Indeed, we had the same problem in the past, but decided to print tensors/memrefs as vectors.

Other interesting features in libc I know will be the ability of assert and abort. We may introduce them as Ops in LibC dialect too. assert is a C-macro, but I think it is a good-to-have in MLIR, for example, to help debug the program in "-O0" mode, with printing the Source Location . Based on it, some other features can be implemened. Memref dialect can use libc.assert to make sure the user-passed memref in func args have the claimed rank and dimensions at the entry. Another useful feature based on libc.assert is the boundary checking on memory accesses on memref. I am not sure if these features are already in MLIR or not.

We have added "runner" functionality in local dialects (check, perf), but never quite found the place to put them.

I think there's a common trend between all these little functions and it's not libc. I'd call it runner or runtime or just run and de-compose the gpu.printf into gpu.get_tensor + run.printf, for example. I'd also add the benchmark loops, checking of values, etc.

C++ has complex ABI issues and many of its useful features are based on templates. I don't think we need libcpp dialect for now.

Right, I was using it to show how quickly this can escalate if we chose a tangent path. :)

I do like the idea of improving the ability to print stuff in MLIR, but just a printf wrapper is too much for a new dialect.

Right now, you can already lower your formatted printf into LLVM dialect call, so this should be enough for just basic printf. On the rest of the "runtime dialect", I think an RFC is in order to make sure we converge to a common design.

I'd love to find a home for our check and perf dialects.

@joker-eph
Copy link
Collaborator

Thanks for the contribution! Please have a look at https://mlir.llvm.org/getting_started/DeveloperGuide/#guidelines-on-contributing-a-new-dialect-or-important-components and conduct a discussion on Discourse for contributing a new dialect.

@Menooker
Copy link
Author

Thanks for the contribution! Please have a look at https://mlir.llvm.org/getting_started/DeveloperGuide/#guidelines-on-contributing-a-new-dialect-or-important-components and conduct a discussion on Discourse for contributing a new dialect.

Thanks for reminder. I have posted an RFC. We can move to that for discussion on the new dialect.

@Menooker Menooker closed this Aug 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants