MLIR C interop with 2D memref

Hi there,

I tried to call a simple 2D memref load-store MLIR function from C, while the result looks weird -

The MLIR code:

// File name: main.mlir

func @load_store_2d(%A : memref<?x?xf32>, %B : memref<?x?xf32>) {
  %c0 = constant 0 : index
  %c1 = constant 1 : index

  %d0 = dim %A, %c0 : memref<?x?xf32>
  %d1 = dim %A, %c1 : memref<?x?xf32>

  affine.for %i = 0 to %d0 {
    affine.for %j = 0 to %d1 {
      %0 = affine.load %A[%i, %j] : memref<?x?xf32>
      affine.store %0, %B[%i, %j] : memref<?x?xf32>
    }
  }

  return 
}

The C code:

// File name: main.c

#include <stdio.h>

struct TwoDMemrefF32 {
  float *ptrToData;
  float *alignedPtrToData;
  long offset;
  long shape[2];
  long stride[2];
};

#define M 6
#define N 8

extern void _mlir_ciface_load_store_2d(struct TwoDMemrefF32 *,
                                       struct TwoDMemrefF32 *);

int main(int argc, char *argv[]) {
  int i, j;

  float A[M][N], B[M][N];

  for (i = 0; i < M; i++) {
    for (j = 0; j < N; j++) {
      A[i][j] = ((float)i + j) / (i + j + 1);
      B[i][j] = (float)0;
      printf("%8.6f ", A[i][j]);
    }
    printf("\n");
  }
  printf("\n");

  struct TwoDMemrefF32 A_mem = {&A[0][0], &A[0][0], 0, {M, N}, {1, 1}};
  struct TwoDMemrefF32 B_mem = {&B[0][0], &B[0][0], 0, {M, N}, {1, 1}};

  _mlir_ciface_load_store_2d(&A_mem, &B_mem);

  for (i = 0; i < M; i++) {
    for (j = 0; j < N; j++)
      printf("%8.6f ", B[i][j]);
    printf("\n");
  }

  return 0;
}

The script to compile and run:

#!/usr/bin/bash
# File name: run.sh

NAME="$1"
MAIN_FILE="${NAME}.c"
MAIN_LLVMIR_FILE="${NAME}.ll"
MLIR_FILE="${NAME}.mlir"
MLIR_LLVM_FILE="${NAME}_llvm.mlir"
MLIR_LLVMIR_FILE="${NAME}_mlir.ll"
MLIR_BC_FILE="${NAME}_mlir.bc"

RESULT_BC_FILE="result.bc"
RESULT_OBJ_FILE="result.o"

EXE=main

# NOTE: change this to your LLVM build bin directory.
LLVM_BINDIR="${PWD}/../../../../llvm/build/bin"

${LLVM_BINDIR}/mlir-opt \
  --lower-affine \
  --convert-scf-to-std \
  --convert-std-to-llvm='emit-c-wrappers=1' \
  "${MLIR_FILE}" \
  -o "${MLIR_LLVM_FILE}" 

${LLVM_BINDIR}/mlir-translate "${MLIR_LLVM_FILE}" --mlir-to-llvmir -o "${MLIR_LLVMIR_FILE}"

${LLVM_BINDIR}/llvm-as "${MLIR_LLVMIR_FILE}" -o "${MLIR_BC_FILE}"

${LLVM_BINDIR}/clang -emit-llvm "${MAIN_FILE}" -S -o "${MAIN_LLVMIR_FILE}"

${LLVM_BINDIR}/llvm-link "${MAIN_LLVMIR_FILE}" "${MLIR_LLVMIR_FILE}" -o "${RESULT_BC_FILE}"

${LLVM_BINDIR}/llc -filetype=obj "${RESULT_BC_FILE}"

${LLVM_BINDIR}/clang "${RESULT_OBJ_FILE}" -o "${EXE}"

"./${EXE}"

The result of compilation and running ./run.sh main is -

warning: Linking two modules of different data layouts: 'main_mlir.ll' is '' whereas 'llvm-link' is 'e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128'

0.000000 0.500000 0.666667 0.750000 0.800000 0.833333 0.857143 0.875000 
0.500000 0.666667 0.750000 0.800000 0.833333 0.857143 0.875000 0.888889 
0.666667 0.750000 0.800000 0.833333 0.857143 0.875000 0.888889 0.900000 
0.750000 0.800000 0.833333 0.857143 0.875000 0.888889 0.900000 0.909091 
0.800000 0.833333 0.857143 0.875000 0.888889 0.900000 0.909091 0.916667 
0.833333 0.857143 0.875000 0.888889 0.900000 0.909091 0.916667 0.923077 

0.000000 0.500000 0.666667 0.750000 0.800000 0.833333 0.857143 0.875000 
0.500000 0.666667 0.750000 0.800000 0.833333 0.000000 0.000000 0.000000 
0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 
0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 
0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 
0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 

The second block, which corresponds to the 2D memory that should load data from the first block, doesn’t exactly match its source.

I assume there might be some alignment issue, but I’m not entirely sure. Would anyone that has similar experience kindly point me why this may happen? Thanks in advance for any help :slight_smile:

cc: @ftynse

Best regards
Ruizhe

I think the strides are a problem. For the outer dimension, stride must be number of inner dim elements. Can you check if this works?

  struct TwoDMemrefF32 A_mem = {&A[0][0], &A[0][0], 0, {M, N}, {1, N}};
  struct TwoDMemrefF32 B_mem = {&B[0][0], &B[0][0], 0, {M, N}, {1, N}};

Actually, I. think it should be:

  struct TwoDMemrefF32 A_mem = {&A[0][0], &A[0][0], 0, {M, N}, {N, 1}};
  struct TwoDMemrefF32 B_mem = {&B[0][0], &B[0][0], 0, {M, N}, {N, 1}};
1 Like

Yes it works! Thank you very much :slight_smile: This issue has bothered me for a long time

As an additional clarification, strides indicate the number of elements one needs to “jump over” to get to the next element along the given dimension. There is no requirement about their specific values, both {N, 1} for row-major and {1, M} for column-major indexing would work. So would {N+10, 1}, for example. The assumption we make though is that there is no internal aliasing in a memref, e.g. two indices cannot point to the same element, so {1,1} or {N-1,1} would make many transformations invalid.