[mlir][LLVM] Finer-grained control for C interface emission

C interface emission is controlled by a flag and has coarse granularity.
With this coarse control, interfaces are emitted for all external functions.
This makes is easy to get undefined symbols.

This revision adds support for controlling per-function emission with an "emit_c_interface" attribute.
diff --git a/mlir/docs/ConversionToLLVMDialect.md b/mlir/docs/ConversionToLLVMDialect.md
index 92be9a5..15d09cb 100644
--- a/mlir/docs/ConversionToLLVMDialect.md
+++ b/mlir/docs/ConversionToLLVMDialect.md
@@ -374,12 +374,14 @@
 
 ### C-compatible wrapper emission
 
-In practical cases, it may be desirable to have externally-facing functions
-with a single attribute corresponding to a MemRef argument. When interfacing
-with LLVM IR produced from C, the code needs to respect the corresponding
-calling convention. The conversion to the LLVM dialect provides an option to
-generate wrapper functions that take memref descriptors as pointers-to-struct
-compatible with data types produced by Clang when compiling C sources.
+In practical cases, it may be desirable to have externally-facing functions with
+a single attribute corresponding to a MemRef argument. When interfacing with
+LLVM IR produced from C, the code needs to respect the corresponding calling
+convention. The conversion to the LLVM dialect provides an option to generate
+wrapper functions that take memref descriptors as pointers-to-struct compatible
+with data types produced by Clang when compiling C sources. The generation of
+such wrapper functions can additionally be controlled at a function granularity
+by setting the `llvm.emit_c_interface` unit attribute.
 
 More specifically, a memref argument is converted into a pointer-to-struct
 argument of type `{T*, T*, i64, i64[N], i64[N]}*` in the wrapper function, where
diff --git a/mlir/lib/Conversion/StandardToLLVM/StandardToLLVM.cpp b/mlir/lib/Conversion/StandardToLLVM/StandardToLLVM.cpp
index d840a87..12fc8b1 100644
--- a/mlir/lib/Conversion/StandardToLLVM/StandardToLLVM.cpp
+++ b/mlir/lib/Conversion/StandardToLLVM/StandardToLLVM.cpp
@@ -932,6 +932,7 @@
 /// FuncOp legalization pattern that converts MemRef arguments to pointers to
 /// MemRef descriptors (LLVM struct data types) containing all the MemRef type
 /// information.
+static constexpr StringRef kEmitIfaceAttrName = "llvm.emit_c_interface";
 struct FuncOpConversion : public FuncOpConversionBase {
   FuncOpConversion(LLVMTypeConverter &converter, bool emitCWrappers)
       : FuncOpConversionBase(converter), emitWrappers(emitCWrappers) {}
@@ -942,7 +943,7 @@
     auto funcOp = cast<FuncOp>(op);
 
     auto newFuncOp = convertFuncOpToLLVMFuncOp(funcOp, rewriter);
-    if (emitWrappers) {
+    if (emitWrappers || funcOp.getAttrOfType<UnitAttr>(kEmitIfaceAttrName)) {
       if (newFuncOp.isExternal())
         wrapExternalFunction(rewriter, op->getLoc(), typeConverter, funcOp,
                              newFuncOp);
@@ -2821,7 +2822,6 @@
     if (failed(applyPartialConversion(m, target, patterns, &typeConverter)))
       signalPassFailure();
   }
-
 };
 } // end namespace
 
diff --git a/mlir/test/Conversion/StandardToLLVM/calling-convention.mlir b/mlir/test/Conversion/StandardToLLVM/calling-convention.mlir
index 348afff..87bdab2 100644
--- a/mlir/test/Conversion/StandardToLLVM/calling-convention.mlir
+++ b/mlir/test/Conversion/StandardToLLVM/calling-convention.mlir
@@ -1,4 +1,5 @@
 // RUN: mlir-opt -convert-std-to-llvm='emit-c-wrappers=1' %s | FileCheck %s
+// RUN: mlir-opt -convert-std-to-llvm %s | FileCheck %s --check-prefix=EMIT_C_ATTRIBUTE
 
 // This tests the default memref calling convention and the emission of C
 // wrappers. We don't need to separate runs because the wrapper-emission
@@ -72,6 +73,7 @@
 }
 
 // CHECK-LABEL: @callee
+// EMIT_C_ATTRIBUTE-LABEL: @callee
 func @callee(%arg0: memref<?xf32>, %arg1: index) {
   %0 = load %arg0[%arg1] : memref<?xf32>
   return
@@ -93,3 +95,17 @@
   // Forward the descriptor components to the call.
   // CHECK: llvm.call @callee(%[[ALLOC]], %[[ALIGN]], %[[OFFSET]], %[[SIZE]], %[[STRIDE]], %{{.*}}) : (!llvm<"float*">, !llvm<"float*">, !llvm.i64, !llvm.i64, !llvm.i64, !llvm.i64) -> ()
 
+//   EMIT_C_ATTRIBUTE-NOT: @mlir_ciface_callee
+
+// CHECK-LABEL: @other_callee
+// EMIT_C_ATTRIBUTE-LABEL: @other_callee
+func @other_callee(%arg0: memref<?xf32>, %arg1: index) attributes { llvm.emit_c_interface } {
+  %0 = load %arg0[%arg1] : memref<?xf32>
+  return
+}
+
+// CHECK: @_mlir_ciface_other_callee
+// CHECK:   llvm.call @other_callee
+
+// EMIT_C_ATTRIBUTE: @_mlir_ciface_other_callee
+// EMIT_C_ATTRIBUTE:   llvm.call @other_callee