/*
 * Copyright 2021 Mozilla Foundation
 *
 * 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.
 */

#ifndef wasm_builtin_module_h
#define wasm_builtin_module_h

#include "mozilla/EnumeratedArray.h"
#include "mozilla/Maybe.h"
#include "mozilla/Span.h"

#include "wasm/WasmBuiltins.h"
#include "wasm/WasmCompileArgs.h"
#include "wasm/WasmConstants.h"
#include "wasm/WasmModuleTypes.h"
#include "wasm/WasmSerialize.h"
#include "wasm/WasmTypeDecls.h"
#include "wasm/WasmTypeDef.h"

namespace js {
namespace wasm {

struct ImportValues;
struct Import;

struct MOZ_STACK_CLASS BuiltinModuleInstances {
  explicit BuiltinModuleInstances(JSContext* cx)
      : selfTest(cx), intGemm(cx), jsString(cx) {}

  Rooted<JSObject*> selfTest;
  Rooted<JSObject*> intGemm;
  Rooted<JSObject*> jsString;

  MutableHandle<JSObject*> operator[](BuiltinModuleId module) {
    switch (module) {
      case BuiltinModuleId::SelfTest: {
        return &selfTest;
      }
      case BuiltinModuleId::IntGemm: {
        return &intGemm;
      }
      case BuiltinModuleId::JSString: {
        return &jsString;
      }
      default: {
        MOZ_CRASH();
      }
    }
  }
};

// An builtin module func is a natively implemented function that may be
// compiled into a 'builtin module', which may be instantiated with a provided
// memory yielding an exported WebAssembly function wrapping the builtin module.
class BuiltinModuleFunc {
 private:
  SharedRecGroup recGroup_;
  const char* exportName_;
  const SymbolicAddressSignature* sig_;
  bool usesMemory_;
  BuiltinInlineOp inlineOp_;

 public:
  // Default constructor so this can be used in an EnumeratedArray.
  BuiltinModuleFunc() = default;

  // Initialize this builtin. Must only be called once.
  [[nodiscard]] bool init(const RefPtr<TypeContext>& types,
                          mozilla::Span<const ValType> params,
                          mozilla::Maybe<ValType> result, bool usesMemory,
                          const SymbolicAddressSignature* sig,
                          BuiltinInlineOp inlineOp, const char* exportName);

  // The rec group for the function type for this builtin.
  const RecGroup* recGroup() const { return recGroup_.get(); }
  // The type definition for the function type for this builtin.
  const TypeDef* typeDef() const { return &recGroup_->type(0); }
  // The function type for this builtin.
  const FuncType* funcType() const { return &typeDef()->funcType(); }

  // The name of the func as it is exported
  const char* exportName() const { return exportName_; }
  // The signature of the builtin that implements this function.
  const SymbolicAddressSignature* sig() const { return sig_; }
  // Whether this function takes a pointer to the memory base as a hidden final
  // parameter. This parameter will show up in the SymbolicAddressSignature,
  // but not the function type. Compilers must pass the memoryBase to the
  // function call as the last parameter.
  bool usesMemory() const { return usesMemory_; }
  // An optional inline operation that can be used for this function instead of
  // calling `sig`.
  BuiltinInlineOp inlineOp() const { return inlineOp_; }
};

// Static storage for all builtin module funcs in the system.
class BuiltinModuleFuncs {
  using Storage =
      mozilla::EnumeratedArray<BuiltinModuleFuncId, BuiltinModuleFunc,
                               size_t(BuiltinModuleFuncId::Limit)>;
  Storage funcs_;

  static BuiltinModuleFuncs* singleton_;

 public:
  [[nodiscard]] static bool init();
  static void destroy();

  // Get the BuiltinModuleFunc for an BuiltinModuleFuncId. BuiltinModuleFuncId
  // must be validated.
  static const BuiltinModuleFunc& getFromId(BuiltinModuleFuncId id) {
    return singleton_->funcs_[id];
  }
};

mozilla::Maybe<BuiltinModuleId> ImportMatchesBuiltinModule(
    mozilla::Span<const char> importName,
    const BuiltinModuleIds& enabledBuiltins);
mozilla::Maybe<BuiltinModuleId> ImportMatchesBuiltinModule(
    const Import& import, const BuiltinModuleIds& enabledBuiltins);

// Returns true if the import field matches a definition in the given builtin
// module for the given definition kind. The out-params matchedFunc and
// matchedFuncId are only used when kind == DefinitionKind::Function, and may
// be null if the caller doesn't need them.
bool ImportFieldMatchesBuiltinModuleDefinition(
    mozilla::Span<const char> importName, BuiltinModuleId module,
    DefinitionKind kind, const BuiltinModuleFunc** matchedFunc = nullptr,
    BuiltinModuleFuncId* matchedFuncId = nullptr);

// Compile and return the builtin module for a particular
// builtin module. The `moduleMemoryImport` can be used if the builtin module
// requires memory to set the import name that it will need when it is
// instantiated, otherwise ("" "memory") is used instead.
[[nodiscard]] bool CompileBuiltinModule(
    JSContext* cx, BuiltinModuleId module, const Import* moduleMemoryImport,
    MutableHandle<WasmModuleObject*> result);

// Compile, instantiate and return the builtin module instance for a particular
// builtin module.
[[nodiscard]] bool InstantiateBuiltinModule(JSContext* cx,
                                            BuiltinModuleId module,
                                            const Import* moduleMemoryImport,
                                            Handle<JSObject*> importObj,
                                            MutableHandle<JSObject*> result);

}  // namespace wasm
}  // namespace js

#endif  // wasm_builtin_module_h
