//===- quant.c - Test of Quant dialect C API ------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// RUN: mlir-capi-quant-test 2>&1 | FileCheck %s

#include "mlir-c/Dialect/Quant.h"
#include "mlir-c/BuiltinAttributes.h"
#include "mlir-c/BuiltinTypes.h"
#include "mlir-c/IR.h"

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

// CHECK-LABEL: testTypeHierarchy
static void testTypeHierarchy(MlirContext ctx) {
  fprintf(stderr, "testTypeHierarchy\n");

  MlirType i8 = mlirIntegerTypeGet(ctx, 8);
  MlirType any = mlirTypeParseGet(
      ctx, mlirStringRefCreateFromCString("!quant.any<i8<-8:7>:f32>"));
  MlirType uniform =
      mlirTypeParseGet(ctx, mlirStringRefCreateFromCString(
                                "!quant.uniform<i8<-8:7>:f32, 0.99872:127>"));
  MlirType perAxis = mlirTypeParseGet(
      ctx, mlirStringRefCreateFromCString(
               "!quant.uniform<i8:f32:1, {2.0e+2,0.99872:120}>"));
  MlirType calibrated = mlirTypeParseGet(
      ctx,
      mlirStringRefCreateFromCString("!quant.calibrated<f32<-0.998:1.2321>>"));

  // The parser itself is checked in C++ dialect tests.
  assert(!mlirTypeIsNull(any) && "couldn't parse AnyQuantizedType");
  assert(!mlirTypeIsNull(uniform) && "couldn't parse UniformQuantizedType");
  assert(!mlirTypeIsNull(perAxis) &&
         "couldn't parse UniformQuantizedPerAxisType");
  assert(!mlirTypeIsNull(calibrated) &&
         "couldn't parse CalibratedQuantizedType");

  // CHECK: i8 isa QuantizedType: 0
  fprintf(stderr, "i8 isa QuantizedType: %d\n", mlirTypeIsAQuantizedType(i8));
  // CHECK: any isa QuantizedType: 1
  fprintf(stderr, "any isa QuantizedType: %d\n", mlirTypeIsAQuantizedType(any));
  // CHECK: uniform isa QuantizedType: 1
  fprintf(stderr, "uniform isa QuantizedType: %d\n",
          mlirTypeIsAQuantizedType(uniform));
  // CHECK: perAxis isa QuantizedType: 1
  fprintf(stderr, "perAxis isa QuantizedType: %d\n",
          mlirTypeIsAQuantizedType(perAxis));
  // CHECK: calibrated isa QuantizedType: 1
  fprintf(stderr, "calibrated isa QuantizedType: %d\n",
          mlirTypeIsAQuantizedType(calibrated));

  // CHECK: any isa AnyQuantizedType: 1
  fprintf(stderr, "any isa AnyQuantizedType: %d\n",
          mlirTypeIsAAnyQuantizedType(any));
  // CHECK: uniform isa UniformQuantizedType: 1
  fprintf(stderr, "uniform isa UniformQuantizedType: %d\n",
          mlirTypeIsAUniformQuantizedType(uniform));
  // CHECK: perAxis isa UniformQuantizedPerAxisType: 1
  fprintf(stderr, "perAxis isa UniformQuantizedPerAxisType: %d\n",
          mlirTypeIsAUniformQuantizedPerAxisType(perAxis));
  // CHECK: calibrated isa CalibratedQuantizedType: 1
  fprintf(stderr, "calibrated isa CalibratedQuantizedType: %d\n",
          mlirTypeIsACalibratedQuantizedType(calibrated));

  // CHECK: perAxis isa UniformQuantizedType: 0
  fprintf(stderr, "perAxis isa UniformQuantizedType: %d\n",
          mlirTypeIsAUniformQuantizedType(perAxis));
  // CHECK: uniform isa CalibratedQuantizedType: 0
  fprintf(stderr, "uniform isa CalibratedQuantizedType: %d\n",
          mlirTypeIsACalibratedQuantizedType(uniform));
  fprintf(stderr, "\n");
}

// CHECK-LABEL: testAnyQuantizedType
void testAnyQuantizedType(MlirContext ctx) {
  fprintf(stderr, "testAnyQuantizedType\n");

  MlirType anyParsed = mlirTypeParseGet(
      ctx, mlirStringRefCreateFromCString("!quant.any<i8<-8:7>:f32>"));

  MlirType i8 = mlirIntegerTypeGet(ctx, 8);
  MlirType f32 = mlirF32TypeGet(ctx);
  MlirType any =
      mlirAnyQuantizedTypeGet(mlirQuantizedTypeGetSignedFlag(), i8, f32, -8, 7);

  // CHECK: flags: 1
  fprintf(stderr, "flags: %u\n", mlirQuantizedTypeGetFlags(any));
  // CHECK: signed: 1
  fprintf(stderr, "signed: %u\n", mlirQuantizedTypeIsSigned(any));
  // CHECK: storage type: i8
  fprintf(stderr, "storage type: ");
  mlirTypeDump(mlirQuantizedTypeGetStorageType(any));
  fprintf(stderr, "\n");
  // CHECK: expressed type: f32
  fprintf(stderr, "expressed type: ");
  mlirTypeDump(mlirQuantizedTypeGetExpressedType(any));
  fprintf(stderr, "\n");
  // CHECK: storage min: -8
  fprintf(stderr, "storage min: %" PRId64 "\n",
          mlirQuantizedTypeGetStorageTypeMin(any));
  // CHECK: storage max: 7
  fprintf(stderr, "storage max: %" PRId64 "\n",
          mlirQuantizedTypeGetStorageTypeMax(any));
  // CHECK: storage width: 8
  fprintf(stderr, "storage width: %u\n",
          mlirQuantizedTypeGetStorageTypeIntegralWidth(any));
  // CHECK: quantized element type: !quant.any<i8<-8:7>:f32>
  fprintf(stderr, "quantized element type: ");
  mlirTypeDump(mlirQuantizedTypeGetQuantizedElementType(any));
  fprintf(stderr, "\n");

  // CHECK: equal: 1
  fprintf(stderr, "equal: %d\n", mlirTypeEqual(anyParsed, any));
  // CHECK: !quant.any<i8<-8:7>:f32>
  mlirTypeDump(any);
  fprintf(stderr, "\n\n");
}

// CHECK-LABEL: testUniformType
void testUniformType(MlirContext ctx) {
  fprintf(stderr, "testUniformType\n");

  MlirType uniformParsed =
      mlirTypeParseGet(ctx, mlirStringRefCreateFromCString(
                                "!quant.uniform<i8<-8:7>:f32, 0.99872:127>"));

  MlirType i8 = mlirIntegerTypeGet(ctx, 8);
  MlirType f32 = mlirF32TypeGet(ctx);
  MlirType uniform = mlirUniformQuantizedTypeGet(
      mlirQuantizedTypeGetSignedFlag(), i8, f32, 0.99872, 127, -8, 7);

  // CHECK: scale: 0.998720
  fprintf(stderr, "scale: %lf\n", mlirUniformQuantizedTypeGetScale(uniform));
  // CHECK: zero point: 127
  fprintf(stderr, "zero point: %" PRId64 "\n",
          mlirUniformQuantizedTypeGetZeroPoint(uniform));
  // CHECK: fixed point: 0
  fprintf(stderr, "fixed point: %d\n",
          mlirUniformQuantizedTypeIsFixedPoint(uniform));

  // CHECK: equal: 1
  fprintf(stderr, "equal: %d\n", mlirTypeEqual(uniform, uniformParsed));
  // CHECK: !quant.uniform<i8<-8:7>:f32, 9.987200e-01:127>
  mlirTypeDump(uniform);
  fprintf(stderr, "\n\n");
}

// CHECK-LABEL: testUniformPerAxisType
void testUniformPerAxisType(MlirContext ctx) {
  fprintf(stderr, "testUniformPerAxisType\n");

  MlirType perAxisParsed = mlirTypeParseGet(
      ctx, mlirStringRefCreateFromCString(
               "!quant.uniform<i8:f32:1, {2.0e+2,0.99872:120}>"));

  MlirType i8 = mlirIntegerTypeGet(ctx, 8);
  MlirType f32 = mlirF32TypeGet(ctx);
  double scales[] = {200.0, 0.99872};
  int64_t zeroPoints[] = {0, 120};
  MlirType perAxis = mlirUniformQuantizedPerAxisTypeGet(
      mlirQuantizedTypeGetSignedFlag(), i8, f32,
      /*nDims=*/2, scales, zeroPoints,
      /*quantizedDimension=*/1,
      mlirQuantizedTypeGetDefaultMinimumForInteger(/*isSigned=*/true,
                                                   /*integralWidth=*/8),
      mlirQuantizedTypeGetDefaultMaximumForInteger(/*isSigned=*/true,
                                                   /*integralWidth=*/8));

  // CHECK: num dims: 2
  fprintf(stderr, "num dims: %" PRIdPTR "\n",
          mlirUniformQuantizedPerAxisTypeGetNumDims(perAxis));
  // CHECK: scale 0: 200.000000
  fprintf(stderr, "scale 0: %lf\n",
          mlirUniformQuantizedPerAxisTypeGetScale(perAxis, 0));
  // CHECK: scale 1: 0.998720
  fprintf(stderr, "scale 1: %lf\n",
          mlirUniformQuantizedPerAxisTypeGetScale(perAxis, 1));
  // CHECK: zero point 0: 0
  fprintf(stderr, "zero point 0: %" PRId64 "\n",
          mlirUniformQuantizedPerAxisTypeGetZeroPoint(perAxis, 0));
  // CHECK: zero point 1: 120
  fprintf(stderr, "zero point 1: %" PRId64 "\n",
          mlirUniformQuantizedPerAxisTypeGetZeroPoint(perAxis, 1));
  // CHECK: quantized dim: 1
  fprintf(stderr, "quantized dim: %" PRId32 "\n",
          mlirUniformQuantizedPerAxisTypeGetQuantizedDimension(perAxis));
  // CHECK: fixed point: 0
  fprintf(stderr, "fixed point: %d\n",
          mlirUniformQuantizedPerAxisTypeIsFixedPoint(perAxis));

  // CHECK: equal: 1
  fprintf(stderr, "equal: %d\n", mlirTypeEqual(perAxis, perAxisParsed));
  // CHECK: !quant.uniform<i8:f32:1, {2.000000e+02,9.987200e-01:120}>
  mlirTypeDump(perAxis);
  fprintf(stderr, "\n\n");
}

// CHECK-LABEL: testUniformSubChannelType
void testUniformSubChannelType(MlirContext ctx) {
  fprintf(stderr, "testUniformSubChannelType\n");

  MlirType subChannelParsed =
      mlirTypeParseGet(ctx, mlirStringRefCreateFromCString(
                                "!quant.uniform<i8:f32:{0:1, 1:2}, "
                                "{{2.0:10, 3.0:20}, {4.0:30, 5.0:40}}>"));

  MlirType i8 = mlirIntegerTypeGet(ctx, 8);
  MlirType f32 = mlirF32TypeGet(ctx);

  // block-size information
  int32_t quantizedDimensions[] = {0, 1};
  int64_t blockSizes[] = {1, 2};
  int64_t numBlockSizes = 2;

  // quantization parameters
  int64_t quantParamShape[] = {2, 2};
  int64_t quantParamRank = 2;
  int64_t numQuantizationParams = 4;
  MlirAttribute scales[] = {mlirFloatAttrDoubleGet(ctx, f32, 2.0),
                            mlirFloatAttrDoubleGet(ctx, f32, 3.0),
                            mlirFloatAttrDoubleGet(ctx, f32, 4.0),
                            mlirFloatAttrDoubleGet(ctx, f32, 5.0)};
  MlirAttribute zeroPoints[] = {
      mlirIntegerAttrGet(i8, 10), mlirIntegerAttrGet(i8, 20),
      mlirIntegerAttrGet(i8, 30), mlirIntegerAttrGet(i8, 40)};

  MlirType scalesType =
      mlirRankedTensorTypeGet(quantParamRank, quantParamShape, f32,
                              /*encoding=*/mlirAttributeGetNull());
  MlirType zeroPointsType = mlirRankedTensorTypeGet(
      quantParamRank, quantParamShape, i8, /*encoding=*/mlirAttributeGetNull());
  MlirAttribute denseScalesAttr =
      mlirDenseElementsAttrGet(scalesType, numQuantizationParams, scales);
  MlirAttribute denseZeroPointsAttr = mlirDenseElementsAttrGet(
      zeroPointsType, numQuantizationParams, zeroPoints);

  MlirType subChannel = mlirUniformQuantizedSubChannelTypeGet(
      mlirQuantizedTypeGetSignedFlag(), i8, f32, denseScalesAttr,
      denseZeroPointsAttr, numBlockSizes, quantizedDimensions, blockSizes,
      mlirQuantizedTypeGetDefaultMinimumForInteger(/*isSigned=*/true,
                                                   /*integralWidth=*/8),
      mlirQuantizedTypeGetDefaultMaximumForInteger(/*isSigned=*/true,
                                                   /*integralWidth=*/8));

  MlirAttribute arrayScalesAttr =
      mlirArrayAttrGet(ctx, numQuantizationParams, scales);
  MlirAttribute arrayZeroPointsAttr =
      mlirArrayAttrGet(ctx, numQuantizationParams, zeroPoints);
  MlirType illegalSubChannel = mlirUniformQuantizedSubChannelTypeGet(
      mlirQuantizedTypeGetSignedFlag(), i8, f32, arrayScalesAttr,
      arrayZeroPointsAttr, numBlockSizes, quantizedDimensions, blockSizes,
      mlirQuantizedTypeGetDefaultMinimumForInteger(/*isSigned=*/true,
                                                   /*integralWidth=*/8),
      mlirQuantizedTypeGetDefaultMaximumForInteger(/*isSigned=*/true,
                                                   /*integralWidth=*/8));

  // CHECK: is null sub-channel type: 1
  fprintf(stderr, "is null sub-channel type: %d\n",
          mlirTypeIsNull(illegalSubChannel));

  // CHECK: num dims: 2
  fprintf(stderr, "num dims: %" PRIdPTR "\n",
          mlirUniformQuantizedSubChannelTypeGetNumBlockSizes(subChannel));

  // CHECK: axis-block-size-pair[0]: 0:1
  fprintf(
      stderr, "axis-block-size-pair[0]: %" PRId32 ":%" PRId64 "\n",
      mlirUniformQuantizedSubChannelTypeGetQuantizedDimension(subChannel, 0),
      mlirUniformQuantizedSubChannelTypeGetBlockSize(subChannel, 0));

  // CHECK: axis-block-size-pair[1]: 1:2
  fprintf(
      stderr, "axis-block-size-pair[1]: %" PRId32 ":%" PRId64 "\n",
      mlirUniformQuantizedSubChannelTypeGetQuantizedDimension(subChannel, 1),
      mlirUniformQuantizedSubChannelTypeGetBlockSize(subChannel, 1));

  denseScalesAttr = mlirUniformQuantizedSubChannelTypeGetScales(subChannel);
  denseZeroPointsAttr =
      mlirUniformQuantizedSubChannelTypeGetZeroPoints(subChannel);
  scalesType = mlirAttributeGetType(denseScalesAttr);
  zeroPointsType = mlirAttributeGetType(denseZeroPointsAttr);

  // CHECK: tensor<2x2xf32>
  mlirTypeDump(scalesType);
  // CHECK: tensor<2x2xi8>
  mlirTypeDump(zeroPointsType);

  // CHECK: number of quantization parameters: 4
  fprintf(stderr, "number of quantization parameters: %" PRId64 "\n",
          mlirElementsAttrGetNumElements(denseScalesAttr));

  // CHECK: quantization-parameter[0]: 2.000000:10
  fprintf(stderr, "quantization-parameter[0]: %lf:%" PRId8 "\n",
          mlirDenseElementsAttrGetFloatValue(denseScalesAttr, 0),
          mlirDenseElementsAttrGetInt8Value(denseZeroPointsAttr, 0));

  // CHECK: quantization-parameter[1]: 3.000000:20
  fprintf(stderr, "quantization-parameter[1]: %lf:%" PRId8 "\n",
          mlirDenseElementsAttrGetFloatValue(denseScalesAttr, 1),
          mlirDenseElementsAttrGetInt8Value(denseZeroPointsAttr, 1));

  // CHECK: quantization-parameter[2]: 4.000000:30
  fprintf(stderr, "quantization-parameter[2]: %lf:%" PRId8 "\n",
          mlirDenseElementsAttrGetFloatValue(denseScalesAttr, 2),
          mlirDenseElementsAttrGetInt8Value(denseZeroPointsAttr, 2));

  // CHECK: quantization-parameter[3]: 5.000000:40
  fprintf(stderr, "quantization-parameter[3]: %lf:%" PRId8 "\n",
          mlirDenseElementsAttrGetFloatValue(denseScalesAttr, 3),
          mlirDenseElementsAttrGetInt8Value(denseZeroPointsAttr, 3));

  // CHECK: equal: 1
  fprintf(stderr, "equal: %d\n", mlirTypeEqual(subChannel, subChannelParsed));

  // CHECK: !quant.uniform<i8:f32:{0:1, 1:2},
  // {{.*}}2.000000e+00:10, 3.000000e+00:20},
  // {4.000000e+00:30, 5.000000e+00:40{{.*}}}}>
  mlirTypeDump(subChannel);
  fprintf(stderr, "\n\n");
}

// CHECK-LABEL: testCalibratedType
void testCalibratedType(MlirContext ctx) {
  fprintf(stderr, "testCalibratedType\n");

  MlirType calibratedParsed = mlirTypeParseGet(
      ctx,
      mlirStringRefCreateFromCString("!quant.calibrated<f32<-0.998:1.2321>>"));

  MlirType f32 = mlirF32TypeGet(ctx);
  MlirType calibrated = mlirCalibratedQuantizedTypeGet(f32, -0.998, 1.2321);

  // CHECK: min: -0.998000
  fprintf(stderr, "min: %lf\n", mlirCalibratedQuantizedTypeGetMin(calibrated));
  // CHECK: max: 1.232100
  fprintf(stderr, "max: %lf\n", mlirCalibratedQuantizedTypeGetMax(calibrated));

  // CHECK: equal: 1
  fprintf(stderr, "equal: %d\n", mlirTypeEqual(calibrated, calibratedParsed));
  // CHECK: !quant.calibrated<f32<-0.998:1.232100e+00>>
  mlirTypeDump(calibrated);
  fprintf(stderr, "\n\n");
}

int main(void) {
  MlirContext ctx = mlirContextCreate();
  mlirDialectHandleRegisterDialect(mlirGetDialectHandle__quant__(), ctx);
  testTypeHierarchy(ctx);
  testAnyQuantizedType(ctx);
  testUniformType(ctx);
  testUniformPerAxisType(ctx);
  testUniformSubChannelType(ctx);
  testCalibratedType(ctx);
  mlirContextDestroy(ctx);
  return EXIT_SUCCESS;
}
