text: /*
  Copyright 2013-present Barefoot Networks, Inc.
  
  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 "extern.h"
  #include "frontends/p4/fromv1.0/v1model.h"
  
  namespace BMV2 {
  
  std::map<cstring, ExternConverter *> *ExternConverter::cvtForType = nullptr;
  
  void ExternConverter::registerExternConverter(cstring name, ExternConverter* cvt) {
      static std::map<cstring, ExternConverter *> map;
      cvtForType = &map;
      LOG3("registered extern " << name);
      map[name] = cvt;
  }
  
  ExternConverter* ExternConverter::get(cstring type) {
      static ExternConverter defaultCvt;
      if (cvtForType && cvtForType->count(type))
          return cvtForType->at(type);
      return &defaultCvt;
  }
  
  Util::IJson*
  ExternConverter::cvtExternObject(ConversionContext* ctxt,
                                   const P4::ExternMethod* em,
                                   const IR::MethodCallExpression* mc,
                                   const IR::StatOrDecl* s,
                                   const bool& emitExterns) {
      return get(em)->convertExternObject(ctxt, em, mc, s, emitExterns);
  }
  
  void
  ExternConverter::cvtExternInstance(ConversionContext* ctxt,
                                     const IR::Declaration* c,
                                     const IR::ExternBlock* eb,
                                     const bool& emitExterns) {
      get(eb)->convertExternInstance(ctxt, c, eb, emitExterns);
  }
  
  Util::IJson*
  ExternConverter::cvtExternFunction(ConversionContext* ctxt,
                                     const P4::ExternFunction* ef,
                                     const IR::MethodCallExpression* mc,
                                     const IR::StatOrDecl* s,
                                     const bool emitExterns) {
      return get(ef)->convertExternFunction(ctxt, ef, mc, s, emitExterns);
  }
  
  Util::IJson*
  ExternConverter::convertExternObject(ConversionContext* ctxt,
                                       const P4::ExternMethod* em,
                                       const IR::MethodCallExpression* mc,
                                       const IR::StatOrDecl*,
                                       const bool& emitExterns) {
      if (emitExterns) {
          auto primitive = mkPrimitive("_" + em->originalExternType->name +
                                           "_" + em->method->name);
          auto parameters = mkParameters(primitive);
          primitive->emplace_non_null("source_info", mc->sourceInfoJsonObj());
          auto etr = new Util::JsonObject();
          etr->emplace("type", "extern");
          etr->emplace("value", em->object->controlPlaneName());
          parameters->append(etr);
          for (auto arg : *mc->arguments) {
              auto args = ctxt->conv->convert(arg->expression);
              parameters->append(args);
          }
          return primitive;
      } else {
          ::error(ErrorType::ERR_UNKNOWN, "Unknown extern method %1% from type %2%",
                  em->method->name, em->originalExternType->name);
          return nullptr;
      }
  }
  
  /// This method is invoked for all externs that do not have a registered
  /// conversion, i.e., unknown by the architecture.
  void
  ExternConverter::convertExternInstance(ConversionContext* ctxt,
                                         const IR::Declaration* decl,
                                         const IR::ExternBlock* eb,
                                         const bool& emitExterns) {
      if (!emitExterns) {
          ::error(ErrorType::ERR_UNKNOWN, "extern instance", eb->type->name);
          return;
      }
      auto attrs = new Util::JsonArray();
      auto params = eb->getConstructorParameters();
      for (auto param : params->parameters) {
          auto val = eb->getParameterValue(param->name);
          cstring type;
          cstring value;
          if (auto cst = val->to<IR::Constant>()) {
              type = "hexstr";
              value = Util::toString(&cst->value, 16);
          } else if (auto str = val->to<IR::StringLiteral>()) {
              type = "string";
              value = str->value;
          } else {
              modelError("%1%: parameter type not unsupported", param->type);
              continue;
          }
          ctxt->json->add_extern_attribute(param->name, type, value, attrs);
      }
      ctxt->json->add_extern(decl->controlPlaneName(), eb->type->getName(), attrs);
  }
  
  Util::IJson*
  ExternConverter::convertExternFunction(ConversionContext* ctxt,
                                         const P4::ExternFunction* ef,
                                         const IR::MethodCallExpression* mc,
                                         const IR::StatOrDecl* s,
                                         const bool emitExterns) {
      if (!emitExterns) {
          ::error(ErrorType::ERR_UNKNOWN, "extern function", ef->method->name);
          return nullptr;
      }
      auto primitive = mkPrimitive(ef->method->name);
      primitive->emplace_non_null("source_info", s->sourceInfoJsonObj());
      auto parameters = mkParameters(primitive);
      for (auto arg : *mc->arguments) {
          auto args = ctxt->conv->convert(arg->expression);
          parameters->append(args);
      }
      return primitive;
  }
  
  void
  ExternConverter::modelError(const char* format, const IR::Node* node) const {
      cstring errMsg = cstring(format) +
                       ". Are you using an up-to-date v1model.p4?";
      ::error(ErrorType::ERR_UNSUPPORTED_ON_TARGET, errMsg.c_str(), node);
  }
  
  void
  ExternConverter::addToFieldList(ConversionContext* ctxt,
                                  const IR::Expression* expr, Util::JsonArray* fl) {
      if (auto le = expr->to<IR::ListExpression>()) {
          for (auto e : le->components) {
              addToFieldList(ctxt, e, fl);
          }
          return;
      } else if (auto si = expr->to<IR::StructInitializerExpression>()) {
          for (auto e : si->components) {
              addToFieldList(ctxt, e->expression, fl);
          }
          return;
      }
  
      auto type = ctxt->typeMap->getType(expr, true);
      if (type->is<IR::Type_StructLike>()) {
          // recursively add all fields
          auto st = type->to<IR::Type_StructLike>();
          for (auto f : st->fields) {
              auto member = new IR::Member(expr, f->name);
              ctxt->typeMap->setType(member, ctxt->typeMap->getType(f, true));
              addToFieldList(ctxt, member, fl);
          }
          return;
      }
  
      bool simple = ctxt->conv->simpleExpressionsOnly;
      ctxt->conv->simpleExpressionsOnly = true;  // we do not want casts d2b in field_lists
      auto j = ctxt->conv->convert(expr);
      ctxt->conv->simpleExpressionsOnly = simple;  // restore state
      if (auto jo = j->to<Util::JsonObject>()) {
          if (auto t = jo->get("type")) {
              if (auto type = t->to<Util::JsonValue>()) {
                  if (*type == "runtime_data") {
                      // Can't have runtime_data in field lists -- need hexstr instead
                      auto val = jo->get("value")->to<Util::JsonValue>();
                      j = jo = new Util::JsonObject();
                      jo->emplace("type", "hexstr");
                      jo->emplace("value", stringRepr(val->getValue()));
                  }
              }
          }
      }
      fl->append(j);
  }
  
  int
  ExternConverter::createFieldList(ConversionContext* ctxt,
                                   const IR::Expression* expr, cstring group,
                                   cstring listName, Util::JsonArray* field_lists) {
      auto fl = new Util::JsonObject();
      field_lists->append(fl);
      int id = nextId(group);
      fl->emplace("id", id);
      fl->emplace("name", listName);
      fl->emplace_non_null("source_info", expr->sourceInfoJsonObj());
      auto elements = mkArrayField(fl, "elements");
      addToFieldList(ctxt, expr, elements);
      return id;
  }
  
  cstring
  ExternConverter::createCalculation(ConversionContext* ctxt,
                                     cstring algo, const IR::Expression* fields,
                                     Util::JsonArray* calculations, bool withPayload,
                                     const IR::Node* sourcePositionNode = nullptr) {
      cstring calcName = ctxt->refMap->newName("calc_");
      auto calc = new Util::JsonObject();
      calc->emplace("name", calcName);
      calc->emplace("id", nextId("calculations"));
      if (sourcePositionNode != nullptr)
          calc->emplace_non_null("source_info", sourcePositionNode->sourceInfoJsonObj());
      calc->emplace("algo", algo);
      if (!fields->is<IR::ListExpression>()) {
          // expand it into a list
          auto list = new IR::ListExpression({});
          auto type = ctxt->typeMap->getType(fields, true);
          if (!type->is<IR::Type_StructLike>()) {
              modelError("%1%: expected a struct", fields);
              return calcName;
          }
          for (auto f : type->to<IR::Type_StructLike>()->fields) {
              auto e = new IR::Member(fields, f->name);
              auto ftype = ctxt->typeMap->getType(f);
              ctxt->typeMap->setType(e, ftype);
              list->push_back(e);
          }
          fields = list;
          ctxt->typeMap->setType(fields, type);
      }
      auto jright = ctxt->conv->convertWithConstantWidths(fields);
      if (withPayload) {
          auto array = jright->to<Util::JsonArray>();
          BUG_CHECK(array, "expected a JSON array");
          auto payload = new Util::JsonObject();
          payload->emplace("type", "payload");
          payload->emplace("value", (Util::IJson*)nullptr);
          array->append(payload);
      }
      calc->emplace("input", jright);
      calculations->append(calc);
      return calcName;
  }
  
  cstring
  ExternConverter::convertHashAlgorithm(cstring algorithm) {
      cstring result;
      if (algorithm == P4V1::V1Model::instance.algorithm.crc32.name)
          result = "crc32";
      else if (algorithm == P4V1::V1Model::instance.algorithm.crc32_custom.name)
          result = "crc32_custom";
      else if (algorithm == P4V1::V1Model::instance.algorithm.crc16.name)
          result = "crc16";
      else if (algorithm == P4V1::V1Model::instance.algorithm.crc16_custom.name)
          result = "crc16_custom";
      else if (algorithm == P4V1::V1Model::instance.algorithm.random.name)
          result = "random";
      else if (algorithm == P4V1::V1Model::instance.algorithm.identity.name)
          result = "identity";
      else if (algorithm == P4V1::V1Model::instance.algorithm.csum16.name)
          result = "csum16";
      else if (algorithm == P4V1::V1Model::instance.algorithm.xor16.name)
          result = "xor16";
      else if (algorithm == P4V1::V1Model::instance.algorithm.h1.name)
          result = "h1";
      else if (algorithm == P4V1::V1Model::instance.algorithm.h2.name)
          result = "h2";
      else if (algorithm == P4V1::V1Model::instance.algorithm.h3.name)
          result = "h3";
      else if (algorithm == P4V1::V1Model::instance.algorithm.h4.name)
          result = "h4";
      else if (algorithm == P4V1::V1Model::instance.algorithm.g1.name)
          result = "g1";
      else if (algorithm == P4V1::V1Model::instance.algorithm.g2.name)
          result = "g2";
      else if (algorithm == P4V1::V1Model::instance.algorithm.g3.name)
          result = "g3";
      else if (algorithm == P4V1::V1Model::instance.algorithm.g4.name)
          result = "g4";
      else if (algorithm == P4V1::V1Model::instance.algorithm.d1.name)
          result = "d1";
      else if (algorithm == P4V1::V1Model::instance.algorithm.d2.name)
          result = "d2";
      else if (algorithm == P4V1::V1Model::instance.algorithm.d3.name)
          result = "d3";
      else if (algorithm == P4V1::V1Model::instance.algorithm.d4.name)
          result = "d4";
      else if (algorithm == P4V1::V1Model::instance.algorithm.d5.name)
          result = "d5";
      else if (algorithm == P4V1::V1Model::instance.algorithm.d6.name)
          result = "d6";
      else
          ::error("Unsupported algorithm %1%", algorithm);
      return result;
  }
  
  ExternConverter_assert ExternConverter_assert::singleton;
  ExternConverter_assume ExternConverter_assume::singleton;
  
  Util::IJson*
  ExternConverter::convertAssertAssume(ConversionContext* ctxt,
      const IR::MethodCallExpression* methodCall, const P4::ExternFunction* ef) {
       if (methodCall->arguments->size() != 1) {
          ::error("Expected 1 arguments for %1%", methodCall);
          return nullptr;
      }
      auto primitive = mkPrimitive(ef->method->name.name);
      auto parameters = mkParameters(primitive);
      auto cond = methodCall->arguments->at(0);
      // wrap expression in an additional JSON expression block
      // cast the result of expression to b2d
      auto jsonExpr = ctxt->conv->convert(cond->expression, true, true, true);
      parameters->append(jsonExpr);
      primitive->emplace_non_null("source_info", methodCall->sourceInfoJsonObj());
      return primitive;
  }
  
  Util::IJson* ExternConverter_assert::convertExternFunction(
      UNUSED ConversionContext* ctxt, UNUSED const P4::ExternFunction* ef,
      UNUSED const IR::MethodCallExpression* mc, UNUSED const IR::StatOrDecl* s,
      UNUSED const bool emitExterns) {
      return convertAssertAssume(ctxt, mc, ef);
  }
  
  Util::IJson* ExternConverter_assume::convertExternFunction(
      UNUSED ConversionContext* ctxt, UNUSED const P4::ExternFunction* ef,
      UNUSED const IR::MethodCallExpression* mc, UNUSED const IR::StatOrDecl* s,
      UNUSED const bool emitExterns) {
      return ExternConverter::convertAssertAssume(ctxt, mc, ef);
  }
  
  }  // namespace BMV2
  