/*
 * Copyright 2015, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "slang_rs_export_reduce.h"

#include <algorithm>
#include <sstream>
#include <string>

#include "clang/AST/ASTContext.h"

#include "slang_assert.h"
#include "slang_rs_context.h"
#include "slang_rs_export_type.h"
#include "slang_rs_object_ref_count.h"
#include "slang_rs_special_kernel_param.h"
#include "slang_version.h"

#include "bcinfo/MetadataExtractor.h"

namespace slang {

const char RSExportReduce::KeyReduce[] = "reduce";
const char RSExportReduce::KeyInitializer[] = "initializer";
const char RSExportReduce::KeyAccumulator[] = "accumulator";
const char RSExportReduce::KeyCombiner[] = "combiner";
const char RSExportReduce::KeyOutConverter[] = "outconverter";
const char RSExportReduce::KeyHalter[] = "halter";

bool RSExportReduce::matchName(const llvm::StringRef &Candidate) const {
  return
      Candidate.equals(mNameInitializer)  ||
      Candidate.equals(mNameAccumulator)  ||
      Candidate.equals(mNameCombiner)     ||
      Candidate.equals(mNameOutConverter) ||
      Candidate.equals(mNameHalter);
}

RSExportReduce *RSExportReduce::Create(RSContext *Context,
                                       const clang::SourceLocation Location,
                                       const llvm::StringRef &NameReduce,
                                       const llvm::StringRef &NameInitializer,
                                       const llvm::StringRef &NameAccumulator,
                                       const llvm::StringRef &NameCombiner,
                                       const llvm::StringRef &NameOutConverter,
                                       const llvm::StringRef &NameHalter) {
  slangAssert(Context);
  RSExportReduce *RNE = new RSExportReduce(Context,
                                           Location,
                                           NameReduce,
                                           NameInitializer,
                                           NameAccumulator,
                                           NameCombiner,
                                           NameOutConverter,
                                           NameHalter);

  return RNE;
}

const char *RSExportReduce::getKey(FnIdent Kind) {
  switch (Kind) {
    default:
      slangAssert(!"Unknown FnIdent");
      // and fall through
    case FN_IDENT_INITIALIZER:
      return KeyInitializer;
    case FN_IDENT_ACCUMULATOR:
      return KeyAccumulator;
    case FN_IDENT_COMBINER:
      return KeyCombiner;
    case FN_IDENT_OUT_CONVERTER:
      return KeyOutConverter;
    case FN_IDENT_HALTER:
      return KeyHalter;
  }
}

// This data is needed during analyzeTranslationUnit() but not afterwards.
// Breaking it out into a struct makes it easy for analyzeTranslationUnit()
// to call a number of helper functions that all need access to this data.
struct RSExportReduce::StateOfAnalyzeTranslationUnit {

  typedef std::function<std::string (const char *Key, const std::string &Name)> DiagnosticDescriptionType;

  StateOfAnalyzeTranslationUnit(
      RSContext &anRSContext,
      clang::Preprocessor &aPP,
      clang::ASTContext &anASTContext,
      const DiagnosticDescriptionType &aDiagnosticDescription) :

      RSC(anRSContext),
      PP(aPP),
      ASTC(anASTContext),
      DiagnosticDescription(aDiagnosticDescription),

      Ok(true),

      FnInitializer(nullptr),
      FnAccumulator(nullptr),
      FnCombiner(nullptr),
      FnOutConverter(nullptr),
      FnHalter(nullptr),

      FnInitializerParam(nullptr),
      FnInitializerParamTy(),

      FnAccumulatorOk(true),
      FnAccumulatorParamFirst(nullptr),
      FnAccumulatorParamFirstTy(),
      FnAccumulatorIndexOfFirstSpecialParameter(0),

      FnOutConverterOk(true),
      FnOutConverterParamFirst(nullptr),
      FnOutConverterParamFirstTy()
  { }

  /*-- Convenience ------------------------------------------*/

  RSContext                       &RSC;
  clang::Preprocessor             &PP;
  clang::ASTContext               &ASTC;
  const DiagnosticDescriptionType  DiagnosticDescription;

  /*-- Actual state -----------------------------------------*/

  bool Ok;

  clang::FunctionDecl *FnInitializer;
  clang::FunctionDecl *FnAccumulator;
  clang::FunctionDecl *FnCombiner;
  clang::FunctionDecl *FnOutConverter;
  clang::FunctionDecl *FnHalter;

  clang::ParmVarDecl  *FnInitializerParam;
  clang::QualType      FnInitializerParamTy;

  bool                 FnAccumulatorOk;
  clang::ParmVarDecl  *FnAccumulatorParamFirst;
  clang::QualType      FnAccumulatorParamFirstTy;
  size_t               FnAccumulatorIndexOfFirstSpecialParameter;

  bool                 FnOutConverterOk;  // also true if no outconverter
  clang::ParmVarDecl  *FnOutConverterParamFirst;
  clang::QualType      FnOutConverterParamFirstTy;
};

// does update S.Ok
clang::FunctionDecl *RSExportReduce::lookupFunction(StateOfAnalyzeTranslationUnit &S,
                                                    const char *Kind, const llvm::StringRef &Name) {
  if (Name.empty())
    return nullptr;

  clang::TranslationUnitDecl *TUDecl = getRSContext()->getASTContext().getTranslationUnitDecl();
  slangAssert(TUDecl);

  clang::FunctionDecl *Ret = nullptr;
  const clang::IdentifierInfo *II = S.PP.getIdentifierInfo(Name);
  if (II) {
    for (auto Decl : TUDecl->lookup(II)) {
      clang::FunctionDecl *FDecl = Decl->getAsFunction();
      if (!FDecl || !FDecl->isThisDeclarationADefinition())
        continue;
      if (Ret) {
        S.RSC.ReportError(mLocation,
                          "duplicate function definition for '%0(%1)' for '#pragma rs %2(%3)' (%4, %5)")
            << Kind << Name << KeyReduce << mNameReduce
            << Ret->getLocation().printToString(S.PP.getSourceManager())
            << FDecl->getLocation().printToString(S.PP.getSourceManager());
        S.Ok = false;
        return nullptr;
      }
      Ret = FDecl;
    }
  }
  if (!Ret) {
    // Either the identifier lookup failed, or we never found the function definition.
    S.RSC.ReportError(mLocation,
                      "could not find function definition for '%0(%1)' for '#pragma rs %2(%3)'")
        << Kind << Name << KeyReduce << mNameReduce;
    S.Ok = false;
    return nullptr;
  }
  if (Ret) {
    // Must have internal linkage
    if (Ret->getFormalLinkage() != clang::InternalLinkage) {
      S.RSC.ReportError(Ret->getLocation(), "%0 must be static")
          << S.DiagnosticDescription(Kind, Name);
      S.Ok = false;
    }
  }
  if (Ret == nullptr)
    S.Ok = false;
  return Ret;
}

// updates S.Ok; and, depending on Kind, possibly S.FnAccumulatorOk or S.FnOutConverterOk
void RSExportReduce::notOk(StateOfAnalyzeTranslationUnit &S, FnIdent Kind) {
    S.Ok = false;
    if (Kind == FN_IDENT_ACCUMULATOR) {
      S.FnAccumulatorOk = false;
    } else if (Kind == FN_IDENT_OUT_CONVERTER) {
      S.FnOutConverterOk = false;
    }
}

// updates S.Ok; and, depending on Kind, possibly S.FnAccumulatorOk or S.FnOutConverterOk
void RSExportReduce::checkVoidReturn(StateOfAnalyzeTranslationUnit &S,
                                     FnIdent Kind, clang::FunctionDecl *Fn) {
  slangAssert(Fn);
  const clang::QualType ReturnTy = Fn->getReturnType().getCanonicalType();
  if (!ReturnTy->isVoidType()) {
    S.RSC.ReportError(Fn->getLocation(),
                      "%0 must return void not '%1'")
        << S.DiagnosticDescription(getKey(Kind), Fn->getName()) << ReturnTy.getAsString();
    notOk(S, Kind);
  }
}

// updates S.Ok; and, depending on Kind, possibly S.FnAccumulatorOk or S.FnOutConverterOk
void RSExportReduce::checkPointeeConstQualified(StateOfAnalyzeTranslationUnit &S,
                                                FnIdent Kind, const llvm::StringRef &Name,
                                                const clang::ParmVarDecl *Param, bool ExpectedQualification) {
  const clang::QualType ParamQType = Param->getType();
  slangAssert(ParamQType->isPointerType());
  const clang::QualType PointeeQType = ParamQType->getPointeeType();
  if (PointeeQType.isConstQualified() != ExpectedQualification) {
    S.RSC.ReportError(Param->getLocation(),
                      "%0 parameter '%1' (type '%2') must%3 point to const-qualified type")
        << S.DiagnosticDescription(getKey(Kind), Name)
        << Param->getName() << ParamQType.getAsString()
        << (ExpectedQualification ? "" : " not");
    notOk(S, Kind);
  }
}

// Process "void mNameInitializer(compType *accum)"
void RSExportReduce::analyzeInitializer(StateOfAnalyzeTranslationUnit &S) {
  if (!S.FnInitializer) // initializer is always optional
    return;

  // Must return void
  checkVoidReturn(S, FN_IDENT_INITIALIZER, S.FnInitializer);

  // Must have exactly one parameter
  if (S.FnInitializer->getNumParams() != 1) {
    S.RSC.ReportError(S.FnInitializer->getLocation(),
                      "%0 must take exactly 1 parameter (found %1)")
        << S.DiagnosticDescription(KeyInitializer, mNameInitializer)
        << S.FnInitializer->getNumParams();
    S.Ok = false;
    return;
  }

  // Parameter must not be a special parameter
  S.FnInitializerParam = S.FnInitializer->getParamDecl(0);
  if (isSpecialKernelParameter(S.FnInitializerParam->getName())) {
    S.RSC.ReportError(S.FnInitializer->getLocation(),
                      "%0 cannot take special parameter '%1'")
        << S.DiagnosticDescription(KeyInitializer, mNameInitializer)
        << S.FnInitializerParam->getName();
    S.Ok = false;
    return;
  }

  // Parameter must be of pointer type
  S.FnInitializerParamTy = S.FnInitializerParam->getType().getCanonicalType();
  if (!S.FnInitializerParamTy->isPointerType()) {
    S.RSC.ReportError(S.FnInitializer->getLocation(),
                      "%0 parameter '%1' must be of pointer type not '%2'")
        << S.DiagnosticDescription(KeyInitializer, mNameInitializer)
        << S.FnInitializerParam->getName() << S.FnInitializerParamTy.getAsString();
    S.Ok = false;
    return;
  }

  // Parameter must not point to const-qualified
  checkPointeeConstQualified(S, FN_IDENT_INITIALIZER, mNameInitializer, S.FnInitializerParam, false);
}

// Process "void mNameAccumulator(compType *accum, in1Type in1, …, inNType inN[, specialarguments])"
void RSExportReduce::analyzeAccumulator(StateOfAnalyzeTranslationUnit &S) {
  slangAssert(S.FnAccumulator);

  // Must return void
  checkVoidReturn(S, FN_IDENT_ACCUMULATOR, S.FnAccumulator);

  // Must have initial parameter of same type as initializer parameter
  // (if there is an initializer), followed by at least 1 input

  if (S.FnAccumulator->getNumParams() < 2) {
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 must take at least 2 parameters")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator);
    S.Ok = S.FnAccumulatorOk = false;
    return;
  }

  S.FnAccumulatorParamFirst = S.FnAccumulator->getParamDecl(0);
  S.FnAccumulatorParamFirstTy = S.FnAccumulatorParamFirst->getType().getCanonicalType();

  // First parameter must be of pointer type
  if (!S.FnAccumulatorParamFirstTy->isPointerType()) {
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 parameter '%1' must be of pointer type not '%2'")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
        << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString();
    S.Ok = S.FnAccumulatorOk = false;
    return;
  }

  // If there is an initializer with a pointer-typed parameter (as
  // opposed to an initializer with a bad parameter list), then
  // accumulator first parameter must be of same type as initializer
  // parameter
  if (S.FnInitializer &&
      !S.FnInitializerParamTy.isNull() &&
      S.FnInitializerParamTy->isPointerType() &&
      !S.FnAccumulator->getASTContext().hasSameUnqualifiedType(
          S.FnInitializerParamTy->getPointeeType().getCanonicalType(),
          S.FnAccumulatorParamFirstTy->getPointeeType().getCanonicalType())) {
    // <accumulator> parameter '<baz>' (type '<tbaz>') and initializer <goo>() parameter '<gaz>' (type '<tgaz>')
    //   must be pointers to the same type
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 parameter '%1' (type '%2') and %3 %4() parameter '%5' (type '%6')"
                      " must be pointers to the same type")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
        << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString()
        << KeyInitializer << mNameInitializer
        << S.FnInitializerParam->getName() << S.FnInitializerParamTy.getAsString();
    S.Ok = S.FnAccumulatorOk = false;
  }

  if (S.FnAccumulatorOk && S.FnAccumulatorParamFirstTy->getPointeeType()->isFunctionType()) {
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 parameter '%1' (type '%2') must not be pointer to function type")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
        << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString();
    S.Ok = S.FnAccumulatorOk = false;
  }

  if (S.FnAccumulatorOk && S.FnAccumulatorParamFirstTy->getPointeeType()->isIncompleteType()) {
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 parameter '%1' (type '%2') must not be pointer to incomplete type")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
        << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString();
    S.Ok = S.FnAccumulatorOk = false;
  }

  if (S.FnAccumulatorOk &&
      HasRSObjectType(S.FnAccumulatorParamFirstTy->getPointeeType().getCanonicalType().getTypePtr())) {
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 parameter '%1' (type '%2') must not be pointer to data containing an object type")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
        << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString();
    S.Ok = S.FnAccumulatorOk = false;
  }

  // Parameter must not point to const-qualified
  checkPointeeConstQualified(S, FN_IDENT_ACCUMULATOR, mNameAccumulator, S.FnAccumulatorParamFirst, false);

  // Analyze special parameters
  S.Ok &= (S.FnAccumulatorOk &= processSpecialKernelParameters(
                                  &S.RSC,
                                  std::bind(S.DiagnosticDescription, KeyAccumulator, mNameAccumulator),
                                  S.FnAccumulator,
                                  &S.FnAccumulatorIndexOfFirstSpecialParameter,
                                  &mAccumulatorSignatureMetadata));

  // Must have at least an accumulator and an input.
  // If we get here we know there are at least 2 arguments; so the only problem case is
  // where we have an accumulator followed immediately by a special parameter.
  if (S.FnAccumulatorIndexOfFirstSpecialParameter < 2) {
    slangAssert(S.FnAccumulatorIndexOfFirstSpecialParameter < S.FnAccumulator->getNumParams());
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 must have at least 1 input ('%1' is a special parameter)")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
        << S.FnAccumulator->getParamDecl(S.FnAccumulatorIndexOfFirstSpecialParameter)->getName();
    S.Ok = S.FnAccumulatorOk = false;
    return;
  }

  if (S.FnAccumulatorOk) {
    mAccumulatorSignatureMetadata |= bcinfo::MD_SIG_In;
    mAccumulatorTypeSize = S.ASTC.getTypeSizeInChars(S.FnAccumulatorParamFirstTy->getPointeeType()).getQuantity();
    for (size_t ParamIdx = 1; ParamIdx < S.FnAccumulatorIndexOfFirstSpecialParameter; ++ParamIdx) {
      const clang::ParmVarDecl *const Param = S.FnAccumulator->getParamDecl(ParamIdx);
      mAccumulatorIns.push_back(Param);
      const clang::QualType ParamQType = Param->getType().getCanonicalType();
      const clang::Type *ParamType = ParamQType.getTypePtr();

      RSExportType *ParamEType = nullptr;
      if (ParamQType->isPointerType()) {
        S.RSC.ReportError(Param->getLocation(),
                          "%0 parameter '%1' (type '%2') must not be a pointer")
            << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
            << Param->getName() << ParamQType.getAsString();
        S.Ok = false;
      } else if (HasRSObjectType(ParamType)) {
        S.RSC.ReportError(Param->getLocation(),
                          "%0 parameter '%1' (type '%2') must not contain an object type")
            << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
            << Param->getName() << ParamQType.getAsString();
        S.Ok = false;
      } else if (RSExportType::ValidateType(&S.RSC, S.ASTC, ParamQType, Param, Param->getLocStart(),
                                            S.RSC.getTargetAPI(),
                                            false /* IsFilterscript */,
                                            true /* IsExtern */)) {
        // TODO: Better diagnostics on validation or creation failure?
        ParamEType = RSExportType::Create(&S.RSC, ParamType, NotLegacyKernelArgument);
        S.Ok &= (ParamEType != nullptr);
      } else {
        S.Ok = false;
      }
      mAccumulatorInTypes.push_back(ParamEType); // possibly nullptr
    }
  }
}

// Process "void combinename(compType *accum, const compType *val)"
void RSExportReduce::analyzeCombiner(StateOfAnalyzeTranslationUnit &S) {
  if (S.FnCombiner) {
    // Must return void
    checkVoidReturn(S, FN_IDENT_COMBINER, S.FnCombiner);

    // Must have exactly two parameters, of same type as first accumulator parameter

    if (S.FnCombiner->getNumParams() != 2) {
      S.RSC.ReportError(S.FnCombiner->getLocation(),
                        "%0 must take exactly 2 parameters (found %1)")
          << S.DiagnosticDescription(KeyCombiner, mNameCombiner)
          << S.FnCombiner->getNumParams();
      S.Ok = false;
      return;
    }

    if (S.FnAccumulatorParamFirstTy.isNull() || !S.FnAccumulatorParamFirstTy->isPointerType()) {
      // We're already in an error situation.  We could compare
      // against the initializer parameter type instead of the first
      // accumulator parameter type (we'd have to check for the
      // availability of a parameter type there, too), but it does not
      // seem worth the effort.
      //
      // Likewise, we could compare the two combiner parameter types
      // against each other.
      slangAssert(!S.Ok);
      return;
    }

    for (int ParamIdx = 0; ParamIdx < 2; ++ParamIdx) {
      const clang::ParmVarDecl *const FnCombinerParam = S.FnCombiner->getParamDecl(ParamIdx);
      const clang::QualType FnCombinerParamTy = FnCombinerParam->getType().getCanonicalType();
      if (!FnCombinerParamTy->isPointerType() ||
          !S.FnCombiner->getASTContext().hasSameUnqualifiedType(
              S.FnAccumulatorParamFirstTy->getPointeeType().getCanonicalType(),
              FnCombinerParamTy->getPointeeType().getCanonicalType())) {
        // <combiner> parameter '<baz>' (type '<tbaz>')
        //   and accumulator <goo>() parameter '<gaz>' (type '<tgaz>') must be pointers to the same type
        S.RSC.ReportError(S.FnCombiner->getLocation(),
                          "%0 parameter '%1' (type '%2') and %3 %4() parameter '%5' (type '%6')"
                          " must be pointers to the same type")
            << S.DiagnosticDescription(KeyCombiner, mNameCombiner)
            << FnCombinerParam->getName() << FnCombinerParamTy.getAsString()
            << KeyAccumulator << mNameAccumulator
            << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString();
        S.Ok = false;
      } else {
        // Check const-qualification
        checkPointeeConstQualified(S, FN_IDENT_COMBINER, mNameCombiner, FnCombinerParam, ParamIdx==1);
      }
    }

    return;
  }

  // Ensure accumulator properties permit omission of combiner.

  if (!S.FnAccumulatorOk) {
    // Couldn't fully analyze accumulator, so cannot see whether it permits omission of combiner.
    return;
  }

  if (mAccumulatorIns.size() != 1 ||
      S.FnAccumulatorIndexOfFirstSpecialParameter != S.FnAccumulator->getNumParams())
  {
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 must have exactly 1 input"
                      " and no special parameters in order for the %1 to be omitted")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
        << KeyCombiner;
    S.Ok = false;
    return;
  }

  const clang::ParmVarDecl *const FnAccumulatorParamInput = S.FnAccumulator->getParamDecl(1);
  const clang::QualType FnAccumulatorParamInputTy = FnAccumulatorParamInput->getType().getCanonicalType();
  if (!S.FnAccumulator->getASTContext().hasSameUnqualifiedType(
          S.FnAccumulatorParamFirstTy->getPointeeType().getCanonicalType(),
          FnAccumulatorParamInputTy.getCanonicalType())) {
    S.RSC.ReportError(S.FnAccumulator->getLocation(),
                      "%0 parameter '%1' (type '%2')"
                      " must be pointer to the type of parameter '%3' (type '%4')"
                      " in order for the %5 to be omitted")
        << S.DiagnosticDescription(KeyAccumulator, mNameAccumulator)
        << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString()
        << FnAccumulatorParamInput->getName() << FnAccumulatorParamInputTy.getAsString()
        << KeyCombiner;
    S.Ok = false;
  }
}

// Process "void outconvertname(resultType *result, const compType *accum)"
void RSExportReduce::analyzeOutConverter(StateOfAnalyzeTranslationUnit &S) {
  if (!S.FnOutConverter) // outconverter is always optional
    return;

  // Must return void
  checkVoidReturn(S, FN_IDENT_OUT_CONVERTER, S.FnOutConverter);

  // Must have exactly two parameters
  if (S.FnOutConverter->getNumParams() != 2) {
    S.RSC.ReportError(S.FnOutConverter->getLocation(),
                      "%0 must take exactly 2 parameters (found %1)")
        << S.DiagnosticDescription(KeyOutConverter, mNameOutConverter)
        << S.FnOutConverter->getNumParams();
    S.Ok = S.FnOutConverterOk = false;
    return;
  }

  // Parameters must not be special and must be of pointer type;
  // and second parameter must match first accumulator parameter
  for (int ParamIdx = 0; ParamIdx < 2; ++ParamIdx) {
    clang::ParmVarDecl *const FnOutConverterParam = S.FnOutConverter->getParamDecl(ParamIdx);

    if (isSpecialKernelParameter(FnOutConverterParam->getName())) {
      S.RSC.ReportError(S.FnOutConverter->getLocation(),
                        "%0 cannot take special parameter '%1'")
          << S.DiagnosticDescription(KeyOutConverter, mNameOutConverter)
          << FnOutConverterParam->getName();
      S.Ok = S.FnOutConverterOk = false;
      continue;
    }

    const clang::QualType FnOutConverterParamTy = FnOutConverterParam->getType().getCanonicalType();

    if (!FnOutConverterParamTy->isPointerType()) {
      S.RSC.ReportError(S.FnOutConverter->getLocation(),
                        "%0 parameter '%1' must be of pointer type not '%2'")
          << S.DiagnosticDescription(KeyOutConverter, mNameOutConverter)
          << FnOutConverterParam->getName() << FnOutConverterParamTy.getAsString();
      S.Ok = S.FnOutConverterOk = false;
      continue;
    }

    // Check const-qualification
    checkPointeeConstQualified(S, FN_IDENT_OUT_CONVERTER, mNameOutConverter, FnOutConverterParam, ParamIdx==1);

    if (ParamIdx == 0) {
      S.FnOutConverterParamFirst = FnOutConverterParam;
      S.FnOutConverterParamFirstTy = FnOutConverterParamTy;
      continue;
    }

    if (S.FnAccumulatorParamFirstTy.isNull() || !S.FnAccumulatorParamFirstTy->isPointerType()) {
      // We're already in an error situation.  We could compare
      // against the initializer parameter type instead of the first
      // accumulator parameter type (we'd have to check for the
      // availability of a parameter type there, too), but it does not
      // seem worth the effort.
      slangAssert(!S.Ok);
      continue;
    }

    if (!S.FnOutConverter->getASTContext().hasSameUnqualifiedType(
            S.FnAccumulatorParamFirstTy->getPointeeType().getCanonicalType(),
            FnOutConverterParamTy->getPointeeType().getCanonicalType())) {
      // <outconverter> parameter '<baz>' (type '<tbaz>')
      //   and accumulator <goo>() parameter '<gaz>' (type '<tgaz>') must be pointers to the same type
      S.RSC.ReportError(S.FnOutConverter->getLocation(),
                        "%0 parameter '%1' (type '%2') and %3 %4() parameter '%5' (type '%6')"
                        " must be pointers to the same type")
          << S.DiagnosticDescription(KeyOutConverter, mNameOutConverter)
          << FnOutConverterParam->getName() << FnOutConverterParamTy.getAsString()
          << KeyAccumulator << mNameAccumulator
          << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString();
      S.Ok = S.FnOutConverterOk = false;
    }
  }
}

void RSExportReduce::analyzeHalter(StateOfAnalyzeTranslationUnit &S) {
  if (!S.FnHalter) // halter is always optional
    return;

  // Must return bool
  const clang::QualType ReturnTy = S.FnHalter->getReturnType().getCanonicalType();
  if (!ReturnTy->isBooleanType()) {
    S.RSC.ReportError(S.FnHalter->getLocation(),
                    "%0 must return bool not '%1'")
        << S.DiagnosticDescription(KeyHalter, mNameHalter) << ReturnTy.getAsString();
    S.Ok = false;
  }

  // Must have exactly one parameter
  if (S.FnHalter->getNumParams() != 1) {
    S.RSC.ReportError(S.FnHalter->getLocation(),
                      "%0 must take exactly 1 parameter (found %1)")
        << S.DiagnosticDescription(KeyHalter, mNameHalter)
        << S.FnHalter->getNumParams();
    S.Ok = false;
    return;
  }

  // Parameter must not be a special parameter
  const clang::ParmVarDecl *const FnHalterParam = S.FnHalter->getParamDecl(0);
  if (isSpecialKernelParameter(FnHalterParam->getName())) {
    S.RSC.ReportError(S.FnHalter->getLocation(),
                      "%0 cannot take special parameter '%1'")
        << S.DiagnosticDescription(KeyHalter, mNameHalter)
        << FnHalterParam->getName();
    S.Ok = false;
    return;
  }

  // Parameter must be same type as first accumulator parameter

  if (S.FnAccumulatorParamFirstTy.isNull() || !S.FnAccumulatorParamFirstTy->isPointerType()) {
    // We're already in an error situation.  We could compare against
    // the initializer parameter type or the first combiner parameter
    // type instead of the first accumulator parameter type (we'd have
    // to check for the availability of a parameter type there, too),
    // but it does not seem worth the effort.
    slangAssert(!S.Ok);
    return;
  }

  const clang::QualType FnHalterParamTy = FnHalterParam->getType().getCanonicalType();
  if (!FnHalterParamTy->isPointerType() ||
      !S.FnHalter->getASTContext().hasSameUnqualifiedType(
          S.FnAccumulatorParamFirstTy->getPointeeType().getCanonicalType(),
          FnHalterParamTy->getPointeeType().getCanonicalType())) {
    // <halter> parameter '<baz>' (type '<tbaz>')
    //   and accumulator <goo>() parameter '<gaz>' (type '<tgaz>') must be pointers to the same type
    S.RSC.ReportError(S.FnHalter->getLocation(),
                      "%0 parameter '%1' (type '%2') and %3 %4() parameter '%5' (type '%6')"
                      " must be pointers to the same type")
        << S.DiagnosticDescription(KeyHalter, mNameHalter)
        << FnHalterParam->getName() << FnHalterParamTy.getAsString()
        << KeyAccumulator << mNameAccumulator
        << S.FnAccumulatorParamFirst->getName() << S.FnAccumulatorParamFirstTy.getAsString();
    S.Ok = false;
    return;
  }

  // Parameter must point to const-qualified
  checkPointeeConstQualified(S, FN_IDENT_HALTER, mNameHalter, FnHalterParam, true);
}

void RSExportReduce::analyzeResultType(StateOfAnalyzeTranslationUnit &S) {
  if (!(S.FnAccumulatorOk && S.FnOutConverterOk)) {
    // No idea what the result type is
    slangAssert(!S.Ok);
    return;
  }

  struct ResultInfoType {
    const clang::QualType QType;
    clang::VarDecl *const Decl;
    const char *FnKey;
    const std::string &FnName;
    std::function<std::string ()> UnlessOutConverter;
  } ResultInfo =
        S.FnOutConverter
        ? ResultInfoType({ S.FnOutConverterParamFirstTy, S.FnOutConverterParamFirst,
                           KeyOutConverter, mNameOutConverter,
                           []() { return std::string(""); }})
        : ResultInfoType({ S.FnAccumulatorParamFirstTy,  S.FnAccumulatorParamFirst,
                           KeyAccumulator,  mNameAccumulator,
                           []() { return std::string(" unless ") + KeyOutConverter + " is provided"; }});
  const clang::QualType PointeeQType = ResultInfo.QType->getPointeeType();

  if (PointeeQType->isPointerType()) {
    S.RSC.ReportError(ResultInfo.Decl->getLocation(),
                      "%0 parameter '%1' (type '%2') must not point to a pointer%3")
        << S.DiagnosticDescription(ResultInfo.FnKey, ResultInfo.FnName)
        << ResultInfo.Decl->getName() << ResultInfo.QType.getAsString()
        << ResultInfo.UnlessOutConverter();
  } else if (PointeeQType->isIncompleteType()) {
    S.RSC.ReportError(ResultInfo.Decl->getLocation(),
                      "%0 parameter '%1' (type '%2') must not point to an incomplete type%3")
        << S.DiagnosticDescription(ResultInfo.FnKey, ResultInfo.FnName)
        << ResultInfo.Decl->getName() << ResultInfo.QType.getAsString()
        << ResultInfo.UnlessOutConverter();
  } else if (HasRSObjectType(PointeeQType.getTypePtr())) {
    S.RSC.ReportError(ResultInfo.Decl->getLocation(),
                      "%0 parameter '%1' (type '%2') must not point to data containing an object type%3")
        << S.DiagnosticDescription(ResultInfo.FnKey, ResultInfo.FnName)
        << ResultInfo.Decl->getName() << ResultInfo.QType.getAsString()
        << ResultInfo.UnlessOutConverter();
  } else if (RSExportType::ValidateType(&S.RSC, S.ASTC, PointeeQType,
                                        ResultInfo.Decl, ResultInfo.Decl->getLocStart(),
                                        S.RSC.getTargetAPI(),
                                        false /* IsFilterscript */,
                                        true /* IsExtern */)) {
    // TODO: Better diagnostics on validation or creation failure?
    if ((mResultType = RSExportType::Create(&S.RSC, PointeeQType.getTypePtr(),
                                            NotLegacyKernelArgument, ResultInfo.Decl)) != nullptr) {
      const RSExportType *CheckType = mResultType;
      const char *ArrayErrorPhrase = "";
      if (mResultType->getClass() == RSExportType::ExportClassConstantArray) {
        CheckType = static_cast<const RSExportConstantArrayType *>(mResultType)->getElementType();
        ArrayErrorPhrase = "n array of";
      }
      switch (CheckType->getClass()) {
        case RSExportType::ExportClassMatrix:
          // Not supported for now -- what does a matrix result type mean?
          S.RSC.ReportError(ResultInfo.Decl->getLocation(),
                            "%0 parameter '%1' (type '%2') must not point to a%3 matrix type%4")
              << S.DiagnosticDescription(ResultInfo.FnKey, ResultInfo.FnName)
              << ResultInfo.Decl->getName() << ResultInfo.QType.getAsString()
              << ArrayErrorPhrase
              << ResultInfo.UnlessOutConverter();
          mResultType = nullptr;
          break;
        default:
          // All's well
          break;
      }
    }
  }

  if (mResultType)
    S.RSC.insertExportReduceResultType(mResultType);
  else
    S.Ok = false;
}

bool RSExportReduce::analyzeTranslationUnit() {

  RSContext &RSC = *getRSContext();
  clang::Preprocessor &PP = RSC.getPreprocessor();

  StateOfAnalyzeTranslationUnit S(
      RSC, PP, RSC.getASTContext(),
      [&RSC, &PP, this] (const char *Key, const std::string &Name) {
        std::ostringstream Description;
        Description
            << Key << " " << Name << "()"
            << " for '#pragma rs " << KeyReduce << "(" << mNameReduce << ")'"
            << " (" << mLocation.printToString(PP.getSourceManager()) << ")";
        return Description.str();
      });

  S.FnInitializer  = lookupFunction(S, KeyInitializer,  mNameInitializer);
  S.FnAccumulator  = lookupFunction(S, KeyAccumulator,  mNameAccumulator);
  S.FnCombiner     = lookupFunction(S, KeyCombiner,     mNameCombiner);
  S.FnOutConverter = lookupFunction(S, KeyOutConverter, mNameOutConverter);
  S.FnHalter       = lookupFunction(S, KeyHalter,       mNameHalter);

  if (!S.Ok)
    return false;

  analyzeInitializer(S);
  analyzeAccumulator(S);
  analyzeCombiner(S);
  analyzeOutConverter(S);
  analyzeHalter(S);
  analyzeResultType(S);

  return S.Ok;
}

}  // namespace slang