Skip to content

Conversation

@ZenithalHourlyRate
Copy link
Member

TargetLLVMIR documentation introduced the C-compatible wrapper function for a MLIR function and ways to generate it, but did not demonstrate the corresponding C function signature for them.

The C function signature is not obvious, in that

  • MemrefDescriptor should be passed as pointer.
    • For example, MLIR function could return a new Descriptor, so pointer is a must.
    • Surprisingly, directly pass the struct, by C convention, is also a pointer so some function will work, but that is implicit and error-prone.
  • for @foo() -> memref<>, the return type becomes the first argument in _mlir_ciface_foo(%arg0: !llvm.ptr).
    • This is described in
      /// Creates an auxiliary function with pointer-to-memref-descriptor-struct
      /// arguments instead of unpacked arguments. This function can be called from C
      /// by passing a pointer to a C struct corresponding to a memref descriptor.
      /// Similarly, returned memrefs are passed via pointers to a C struct that is
      /// passed as additional argument.
      /// Internally, the auxiliary function unpacks the descriptor into individual
      /// components and forwards them to `newFuncOp` and forwards the results to
      /// the extra arguments.
      static void wrapForExternalCallers(OpBuilder &rewriter, Location loc,
      const LLVMTypeConverter &typeConverter,
      FunctionOpInterface funcOp,
      LLVM::LLVMFuncOp newFuncOp) {
      auto type = cast<FunctionType>(funcOp.getFunctionType());
      auto [wrapperFuncType, resultStructType] =
      typeConverter.convertFunctionTypeCWrapper(type);
      SmallVector<NamedAttribute> attributes;
      filterFuncAttributes(funcOp, attributes);
      auto wrapperFuncOp = rewriter.create<LLVM::LLVMFuncOp>(
      loc, llvm::formatv("_mlir_ciface_{0}", funcOp.getName()).str(),
      wrapperFuncType, LLVM::Linkage::External, /*dsoLocal=*/false,
      /*cconv=*/LLVM::CConv::C, /*comdat=*/nullptr, attributes);
      propagateArgResAttrs(rewriter, !!resultStructType, funcOp, wrapperFuncOp);
      OpBuilder::InsertionGuard guard(rewriter);
      rewriter.setInsertionPointToStart(wrapperFuncOp.addEntryBlock(rewriter));
      SmallVector<Value, 8> args;
      size_t argOffset = resultStructType ? 1 : 0;
      for (auto [index, argType] : llvm::enumerate(type.getInputs())) {
      Value arg = wrapperFuncOp.getArgument(index + argOffset);
      if (auto memrefType = dyn_cast<MemRefType>(argType)) {
      Value loaded = rewriter.create<LLVM::LoadOp>(
      loc, typeConverter.convertType(memrefType), arg);
      MemRefDescriptor::unpack(rewriter, loc, loaded, memrefType, args);
      continue;
      }
      if (isa<UnrankedMemRefType>(argType)) {
      Value loaded = rewriter.create<LLVM::LoadOp>(
      loc, typeConverter.convertType(argType), arg);
      UnrankedMemRefDescriptor::unpack(rewriter, loc, loaded, args);
      continue;
      }
      args.push_back(arg);
      }
      auto call = rewriter.create<LLVM::CallOp>(loc, newFuncOp, args);
      if (resultStructType) {
      rewriter.create<LLVM::StoreOp>(loc, call.getResult(),
      wrapperFuncOp.getArgument(0));
      rewriter.create<LLVM::ReturnOp>(loc, ValueRange{});
      } else {
      rewriter.create<LLVM::ReturnOp>(loc, call.getResults());
      }
      }
      Especially by code size_t argOffset = resultStructType ? 1 : 0; saying the actual argument starts at 1 when result is a struct (memref)

Users using the wrong signature will get incorrect results. LLVM discourse has some example of it

Cc @ftynse for relevent commit history. Cc @charitha22 and @Wheest from discourse post.

@llvmbot llvmbot added the mlir label Dec 23, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 23, 2024

@llvm/pr-subscribers-mlir

Author: Hongren Zheng (ZenithalHourlyRate)

Changes

TargetLLVMIR documentation introduced the C-compatible wrapper function for a MLIR function and ways to generate it, but did not demonstrate the corresponding C function signature for them.

The C function signature is not obvious, in that

  • MemrefDescriptor should be passed as pointer.
    • For example, MLIR function could return a new Descriptor, so pointer is a must.
    • Surprisingly, directly pass the struct, by C convention, is also a pointer so some function will work, but that is implicit and error-prone.
  • for @<!-- -->foo() -&gt; memref&lt;&gt;, the return type becomes the first argument in _mlir_ciface_foo(%arg0: !llvm.ptr).
    • This is described in
      /// Creates an auxiliary function with pointer-to-memref-descriptor-struct
      /// arguments instead of unpacked arguments. This function can be called from C
      /// by passing a pointer to a C struct corresponding to a memref descriptor.
      /// Similarly, returned memrefs are passed via pointers to a C struct that is
      /// passed as additional argument.
      /// Internally, the auxiliary function unpacks the descriptor into individual
      /// components and forwards them to `newFuncOp` and forwards the results to
      /// the extra arguments.
      static void wrapForExternalCallers(OpBuilder &rewriter, Location loc,
      const LLVMTypeConverter &typeConverter,
      FunctionOpInterface funcOp,
      LLVM::LLVMFuncOp newFuncOp) {
      auto type = cast<FunctionType>(funcOp.getFunctionType());
      auto [wrapperFuncType, resultStructType] =
      typeConverter.convertFunctionTypeCWrapper(type);
      SmallVector<NamedAttribute> attributes;
      filterFuncAttributes(funcOp, attributes);
      auto wrapperFuncOp = rewriter.create<LLVM::LLVMFuncOp>(
      loc, llvm::formatv("_mlir_ciface_{0}", funcOp.getName()).str(),
      wrapperFuncType, LLVM::Linkage::External, /*dsoLocal=*/false,
      /*cconv=*/LLVM::CConv::C, /*comdat=*/nullptr, attributes);
      propagateArgResAttrs(rewriter, !!resultStructType, funcOp, wrapperFuncOp);
      OpBuilder::InsertionGuard guard(rewriter);
      rewriter.setInsertionPointToStart(wrapperFuncOp.addEntryBlock(rewriter));
      SmallVector<Value, 8> args;
      size_t argOffset = resultStructType ? 1 : 0;
      for (auto [index, argType] : llvm::enumerate(type.getInputs())) {
      Value arg = wrapperFuncOp.getArgument(index + argOffset);
      if (auto memrefType = dyn_cast<MemRefType>(argType)) {
      Value loaded = rewriter.create<LLVM::LoadOp>(
      loc, typeConverter.convertType(memrefType), arg);
      MemRefDescriptor::unpack(rewriter, loc, loaded, memrefType, args);
      continue;
      }
      if (isa<UnrankedMemRefType>(argType)) {
      Value loaded = rewriter.create<LLVM::LoadOp>(
      loc, typeConverter.convertType(argType), arg);
      UnrankedMemRefDescriptor::unpack(rewriter, loc, loaded, args);
      continue;
      }
      args.push_back(arg);
      }
      auto call = rewriter.create<LLVM::CallOp>(loc, newFuncOp, args);
      if (resultStructType) {
      rewriter.create<LLVM::StoreOp>(loc, call.getResult(),
      wrapperFuncOp.getArgument(0));
      rewriter.create<LLVM::ReturnOp>(loc, ValueRange{});
      } else {
      rewriter.create<LLVM::ReturnOp>(loc, call.getResults());
      }
      }
      Especially by code size_t argOffset = resultStructType ? 1 : 0; saying the actual argument starts at 1 when result is a struct (memref)

Users using the wrong signature will get incorrect results. LLVM discourse has some example of it

Cc @ftynse for relevent commit history. Cc @charitha22 and @Wheest from discourse post.


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

1 Files Affected:

  • (modified) mlir/docs/TargetLLVMIR.md (+29-3)
diff --git a/mlir/docs/TargetLLVMIR.md b/mlir/docs/TargetLLVMIR.md
index 96a4589eb80e75..ed58040e1abbb8 100644
--- a/mlir/docs/TargetLLVMIR.md
+++ b/mlir/docs/TargetLLVMIR.md
@@ -646,7 +646,7 @@ Examples:
 
 ```mlir
 
-func.func @qux(%arg0: memref<?x?xf32>)
+func.func @qux(%arg0: memref<?x?xf32>) attributes {llvm.emit_c_interface}
 
 // Gets converted into the following
 // (using type alias for brevity):
@@ -683,8 +683,18 @@ llvm.func @qux(%arg0: !llvm.ptr, %arg1: !llvm.ptr,
 llvm.func @_mlir_ciface_qux(!llvm.ptr)
 ```
 
+
+```cpp
+// The C function implementation for the interface function
+extern "C" {
+void _mlir_ciface_qux(MemRefDescriptor<float, 2> *input) {
+  // detailed impl
+}
+}
+```
+
 ```mlir
-func.func @foo(%arg0: memref<?x?xf32>) {
+func.func @foo(%arg0: memref<?x?xf32>) attributes {llvm.emit_c_interface} {
   return
 }
 
@@ -719,8 +729,15 @@ llvm.func @_mlir_ciface_foo(%arg0: !llvm.ptr) {
 }
 ```
 
+```cpp
+// The C function signature for the interface function
+extern "C" {
+void _mlir_ciface_foo(MemRefDescriptor<float, 2> *input);
+}
+```
+
 ```mlir
-func.func @foo(%arg0: memref<?x?xf32>) -> memref<?x?xf32> {
+func.func @foo(%arg0: memref<?x?xf32>) -> memref<?x?xf32> attributes {llvm.emit_c_interface} {
   return %arg0 : memref<?x?xf32>
 }
 
@@ -744,6 +761,7 @@ llvm.func @foo(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: i64,
 }
 
 // Interface function callable from C.
+// NOTE that the returned memref becomes the first argument
 llvm.func @_mlir_ciface_foo(%arg0: !llvm.ptr, %arg1: !llvm.ptr) {
   %0 = llvm.load %arg1 : !llvm.ptr
   %1 = llvm.extractvalue %0[0] : !llvm.memref_2d
@@ -760,6 +778,14 @@ llvm.func @_mlir_ciface_foo(%arg0: !llvm.ptr, %arg1: !llvm.ptr) {
 }
 ```
 
+```cpp
+// The C function signature for the interface function
+extern "C" {
+void _mlir_ciface_foo(MemRefDescriptor<float, 2> *output,
+                      MemRefDescriptor<float, 2> *input);
+}
+```
+
 Rationale: Introducing auxiliary functions for C-compatible interfaces is
 preferred to modifying the calling convention since it will minimize the effect
 of C compatibility on intra-module calls or calls between MLIR-generated

@ftynse
Copy link
Member

ftynse commented Dec 24, 2024

Thanks, documentation improvements are always appreciated! Let me know if you need help merging this.

@ZenithalHourlyRate
Copy link
Member Author

Addressed the review comment

Let me know if you need help merging this.

I do not have commit access for now. I'd appreciate it if you could do me the favor.

@ftynse ftynse merged commit 698bb5f into llvm:main Dec 26, 2024
8 checks passed
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.

3 participants