/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "wasm/WasmMetadata.h"

#include "mozilla/BinarySearch.h"
#include "mozilla/CheckedInt.h"

#include "builtin/Number.h"  // Int32ToCStringBuf
#include "vm/Logging.h"

using mozilla::CheckedInt;

using namespace js;
using namespace js::wasm;

// CodeMetadata helpers -- computing the Instance layout.

bool CodeMetadata::allocateInstanceDataBytes(uint32_t bytes, uint32_t align,
                                             uint32_t* assignedOffset) {
  // Assert that this offset hasn't already been computed.
  MOZ_ASSERT(*assignedOffset == UINT32_MAX);

  CheckedInt<uint32_t> newInstanceDataLength(instanceDataLength);

  // Adjust the current global data length so that it's aligned to `align`
  newInstanceDataLength +=
      ComputeByteAlignment(newInstanceDataLength.value(), align);
  if (!newInstanceDataLength.isValid()) {
    return false;
  }

  // The allocated data is given by the aligned length
  *assignedOffset = newInstanceDataLength.value();

  // Advance the length for `bytes` being allocated
  newInstanceDataLength += bytes;
  if (!newInstanceDataLength.isValid()) {
    return false;
  }

  // This is the highest offset into Instance::globalArea that will not
  // overflow a signed 32-bit integer.
  const uint32_t maxInstanceDataOffset =
      uint32_t(INT32_MAX) - uint32_t(Instance::offsetOfData());

  // Check that the highest offset into this allocated space would not overflow
  // a signed 32-bit integer.
  if (newInstanceDataLength.value() > maxInstanceDataOffset + 1) {
    return false;
  }

  instanceDataLength = newInstanceDataLength.value();
  return true;
}

bool CodeMetadata::allocateInstanceDataBytesN(uint32_t bytes, uint32_t align,
                                              uint32_t count,
                                              uint32_t* assignedOffset) {
  // The size of each allocation should be a multiple of alignment so that a
  // contiguous array of allocations will be aligned
  MOZ_ASSERT(bytes % align == 0);

  // Compute the total bytes being allocated
  CheckedInt<uint32_t> totalBytes = bytes;
  totalBytes *= count;
  if (!totalBytes.isValid()) {
    return false;
  }

  // Allocate the bytes
  return allocateInstanceDataBytes(totalBytes.value(), align, assignedOffset);
}

bool CodeMetadata::prepareForCompile(CompileMode mode) {
  MOZ_ASSERT(!isPreparedForCompile());

  // Find every function that is exported from this module and give it an
  // implicit index
  uint32_t exportedFuncCount = 0;
  for (uint32_t funcIndex = 0; funcIndex < funcs.length(); funcIndex++) {
    const FuncDesc& func = funcs[funcIndex];
    if (func.isExported()) {
      exportedFuncCount++;
    }
  }

  if (!exportedFuncIndices.reserve(exportedFuncCount)) {
    return false;
  }
  for (uint32_t funcIndex = 0; funcIndex < funcs.length(); funcIndex++) {
    const FuncDesc& func = funcs[funcIndex];
    if (!func.isExported()) {
      continue;
    }
    exportedFuncIndices.infallibleEmplaceBack(funcIndex);
  }

  // Allocate the layout for instance data
  instanceDataLength = 0;

  // Allocate space for function counters, if we have them
  if (mode == CompileMode::LazyTiering) {
    if (!allocateInstanceDataBytesN(sizeof(FuncDefInstanceData),
                                    alignof(FuncDefInstanceData), numFuncDefs(),
                                    &funcDefsOffsetStart)) {
      return false;
    }
  }

  // Allocate space for type definitions
  if (!allocateInstanceDataBytesN(sizeof(TypeDefInstanceData),
                                  alignof(TypeDefInstanceData), types->length(),
                                  &typeDefsOffsetStart)) {
    return false;
  }

  // Allocate space for every function import
  if (!allocateInstanceDataBytesN(sizeof(FuncImportInstanceData),
                                  alignof(FuncImportInstanceData),
                                  numFuncImports, &funcImportsOffsetStart)) {
    return false;
  }

  // Allocate space for every function export
  if (!allocateInstanceDataBytesN(
          sizeof(FuncExportInstanceData), alignof(FuncExportInstanceData),
          numExportedFuncs(), &funcExportsOffsetStart)) {
    return false;
  }

  // Allocate space for every memory
  if (!allocateInstanceDataBytesN(sizeof(MemoryInstanceData),
                                  alignof(MemoryInstanceData),
                                  memories.length(), &memoriesOffsetStart)) {
    return false;
  }

  // Allocate space for every table
  if (!allocateInstanceDataBytesN(sizeof(TableInstanceData),
                                  alignof(TableInstanceData), tables.length(),
                                  &tablesOffsetStart)) {
    return false;
  }

  // Allocate space for every tag
  if (!allocateInstanceDataBytesN(sizeof(TagInstanceData),
                                  alignof(TagInstanceData), tags.length(),
                                  &tagsOffsetStart)) {
    return false;
  }

  // Allocate space for every global that requires it
  for (GlobalDesc& global : globals) {
    if (global.isConstant()) {
      continue;
    }

    uint32_t width = global.isIndirect() ? sizeof(void*) : global.type().size();

    uint32_t assignedOffset = UINT32_MAX;
    if (!allocateInstanceDataBytes(width, width, &assignedOffset)) {
      return false;
    }

    global.setOffset(assignedOffset);
  }

  return true;
}

uint32_t CodeMetadata::findFuncExportIndex(uint32_t funcIndex) const {
  MOZ_ASSERT(funcs[funcIndex].isExported());

  size_t match;
  if (!mozilla::BinarySearch(exportedFuncIndices, 0,
                             exportedFuncIndices.length(), funcIndex, &match)) {
    MOZ_CRASH("missing function export");
  }
  return (uint32_t)match;
}

uint32_t CodeTailMetadata::findFuncIndex(uint32_t bytecodeOffset) const {
  size_t funcDefIndex;
  if (!mozilla::BinarySearchIf(
          funcDefRanges, 0, funcDefRanges.length(),
          [bytecodeOffset](const BytecodeRange& range) {
            return range.compareOffset(bytecodeOffset);
          },
          &funcDefIndex)) {
    MOZ_CRASH("missing function definition");
  }
  return codeMeta->numFuncImports + funcDefIndex;
}

// CodeMetadata helpers -- getting function names.

static bool AppendName(const Bytes& namePayload, const Name& name,
                       UTF8Bytes* bytes) {
  MOZ_RELEASE_ASSERT(name.offsetInNamePayload <= namePayload.length());
  MOZ_RELEASE_ASSERT(name.length <=
                     namePayload.length() - name.offsetInNamePayload);
  return bytes->append(
      (const char*)namePayload.begin() + name.offsetInNamePayload, name.length);
}

static bool AppendFunctionIndexName(uint32_t funcIndex, UTF8Bytes* bytes) {
  const char beforeFuncIndex[] = "wasm-function[";
  const char afterFuncIndex[] = "]";

  Int32ToCStringBuf cbuf;
  size_t funcIndexStrLen;
  const char* funcIndexStr =
      Uint32ToCString(&cbuf, funcIndex, &funcIndexStrLen);
  MOZ_ASSERT(funcIndexStr);

  return bytes->append(beforeFuncIndex, strlen(beforeFuncIndex)) &&
         bytes->append(funcIndexStr, funcIndexStrLen) &&
         bytes->append(afterFuncIndex, strlen(afterFuncIndex));
}

bool CodeMetadata::getFuncName(NameContext ctx, uint32_t funcIndex,
                               const ShareableBytes* nameSectionPayload,
                               UTF8Bytes* name) const {
  if (nameSection && nameSection->moduleName.length != 0) {
    if (!AppendName(nameSectionPayload->vector, nameSection->moduleName,
                    name)) {
      return false;
    }
    if (!name->append('.')) {
      return false;
    }
  }

  if (nameSection && funcIndex < nameSection->funcNames.length() &&
      nameSection->funcNames[funcIndex].length != 0) {
    return AppendName(nameSectionPayload->vector,
                      nameSection->funcNames[funcIndex], name);
  }

  if (ctx == NameContext::BeforeLocation) {
    return true;
  }

  return AppendFunctionIndexName(funcIndex, name);
}

// CodeMetadata helpers -- memory accounting.

size_t CodeMetadata::sizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  return memories.sizeOfExcludingThis(mallocSizeOf) +
         types->sizeOfExcludingThis(mallocSizeOf) +
         globals.sizeOfExcludingThis(mallocSizeOf) +
         tags.sizeOfExcludingThis(mallocSizeOf) +
         tables.sizeOfExcludingThis(mallocSizeOf) +
         SizeOfMaybeExcludingThis(nameSection, mallocSizeOf) +
         funcs.sizeOfExcludingThis(mallocSizeOf) +
         elemSegmentTypes.sizeOfExcludingThis(mallocSizeOf) +
         customSectionRanges.sizeOfExcludingThis(mallocSizeOf);
}

CodeTailMetadata::CodeTailMetadata()
    : codeMeta(nullptr),
      debugEnabled(false),
      debugHash(),
      inliningBudget(mutexid::WasmInliningBudget, 0),
      callRefHints(nullptr),
      numCallRefMetrics(UINT32_MAX),
      numAllocSites(UINT32_MAX) {}

CodeTailMetadata::CodeTailMetadata(const CodeMetadata& codeMeta)
    : js::wasm::CodeTailMetadata() {
  this->codeMeta = &codeMeta;
  inliningBudget.lock().get() =
      InliningHeuristics::moduleInliningBudget(codeMeta.codeSectionSize());
}

// ModuleMetadata helpers -- memory accounting.

size_t ModuleMetadata::sizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  return imports.sizeOfExcludingThis(mallocSizeOf) +
         exports.sizeOfExcludingThis(mallocSizeOf) +
         elemSegments.sizeOfExcludingThis(mallocSizeOf) +
         dataSegmentRanges.sizeOfExcludingThis(mallocSizeOf) +
         dataSegments.sizeOfExcludingThis(mallocSizeOf) +
         customSections.sizeOfExcludingThis(mallocSizeOf);
}

bool ModuleMetadata::addDefinedFunc(
    ValTypeVector&& params, ValTypeVector&& results, bool declareForRef,
    mozilla::Maybe<CacheableName>&& optionalExportedName) {
  uint32_t typeIndex = codeMeta->types->length();
  FuncType funcType(std::move(params), std::move(results));
  if (!codeMeta->types->addType(std::move(funcType))) {
    return false;
  }
  return addDefinedFuncWithType(typeIndex, declareForRef,
                                std::move(optionalExportedName));
}

bool ModuleMetadata::addDefinedFuncWithType(
    uint32_t funcTypeIndex, bool declareForRef,
    mozilla::Maybe<CacheableName>&& optionalExportedName) {
  FuncDesc funcDesc = FuncDesc(funcTypeIndex);
  uint32_t funcIndex = codeMeta->funcs.length();
  if (!codeMeta->funcs.append(funcDesc)) {
    return false;
  }
  if (declareForRef) {
    codeMeta->funcs[funcIndex].declareFuncExported(true, true);
  }
  if (optionalExportedName.isSome()) {
    if (!exports.emplaceBack(std::move(optionalExportedName.ref()), funcIndex,
                             DefinitionKind::Function)) {
      return false;
    }
  }
  return true;
}

bool ModuleMetadata::addImportedFunc(ValTypeVector&& params,
                                     ValTypeVector&& results,
                                     CacheableName&& importModName,
                                     CacheableName&& importFieldName) {
  MOZ_ASSERT(codeMeta->numFuncImports == codeMeta->funcs.length());
  if (!addDefinedFunc(std::move(params), std::move(results), false,
                      mozilla::Nothing())) {
    return false;
  }
  codeMeta->numFuncImports++;
  return imports.emplaceBack(std::move(importModName),
                             std::move(importFieldName),
                             DefinitionKind::Function);
}
