Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Impressum WasmJS.cpp

  Interaktion und
PortierbarkeitC
 

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 *
 * Copyright 2016 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.
 */


#include "wasm/WasmJS.h"

#include "mozilla/EndianUtils.h"
#include "mozilla/Maybe.h"

#include <algorithm>
#include <cstdint>

#include "jsapi.h"
#include "jsexn.h"

#include "ds/IdValuePair.h"            // js::IdValuePair
#include "frontend/FrontendContext.h"  // AutoReportFrontendContext
#include "gc/GCContext.h"
#include "jit/AtomicOperations.h"
#include "jit/FlushICache.h"
#include "jit/JitContext.h"
#include "jit/JitOptions.h"
#include "jit/Simulator.h"
#include "js/ColumnNumber.h"  // JS::ColumnNumberOneOrigin
#include "js/ForOfIterator.h"
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/Printf.h"
#include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_GetProperty
#include "js/PropertySpec.h"        // JS_{PS,FN}{,_END}
#include "js/Stack.h"               // BuildStackString
#include "js/StreamConsumer.h"
#include "util/StringBuilder.h"
#include "util/Text.h"
#include "vm/ErrorObject.h"
#include "vm/FunctionFlags.h"      // js::FunctionFlags
#include "vm/GlobalObject.h"       // js::GlobalObject
#include "vm/HelperThreadState.h"  // js::PromiseHelperTask
#include "vm/Interpreter.h"
#include "vm/JSFunction.h"
#include "vm/PlainObject.h"    // js::PlainObject
#include "vm/PromiseObject.h"  // js::PromiseObject
#include "vm/SharedArrayObject.h"
#include "vm/StringType.h"
#include "vm/Warnings.h"  // js::WarnNumberASCII
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmBuiltinModule.h"
#include "wasm/WasmBuiltins.h"
#include "wasm/WasmCompile.h"
#include "wasm/WasmDebug.h"
#include "wasm/WasmFeatures.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmMemory.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmPI.h"
#include "wasm/WasmProcess.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmStubs.h"
#include "wasm/WasmValidate.h"

#include "gc/GCContext-inl.h"
#include "gc/StableCellHasher-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "wasm/WasmInstance-inl.h"

/*
 * [SMDOC] WebAssembly code rules (evolving)
 *
 * TlsContext.get() is only to be invoked from functions that have been invoked
 *   _directly_ by generated code as cold(!) Builtin calls, from code that is
 *   only used by signal handlers, or from helper functions that have been
 *   called _directly_ from a simulator.  All other code shall pass in a
 *   JSContext* to functions that need it, or an Instance* or Instance* since
 * the context is available through them.
 *
 *   Code that uses TlsContext.get() shall annotate each such call with the
 *   reason why the call is OK.
 */


using namespace js;
using namespace js::jit;
using namespace js::wasm;

using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::Some;
using mozilla::Span;

static bool ThrowCompileOutOfMemory(JSContext* cx) {
  // Most OOMs during compilation are due to large contiguous allocations,
  // and future allocations are likely to succeed. Throwing a proper error
  // object is nicer for users in these circumstances.
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OUT_OF_MEMORY);
  return false;
}

// ============================================================================
// Imports

static bool ThrowBadImportArg(JSContext* cx) {
  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           JSMSG_WASM_BAD_IMPORT_ARG);
  return false;
}

static bool ThrowBadImportType(JSContext* cx, const CacheableName& field,
                               const char* str) {
  UniqueChars fieldQuoted = field.toQuotedString(cx);
  if (!fieldQuoted) {
    ReportOutOfMemory(cx);
    return false;
  }
  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           JSMSG_WASM_BAD_IMPORT_TYPE, fieldQuoted.get(), str);
  return false;
}

// For now reject cross-compartment wrappers. These have more complicated realm
// semantics (we use nonCCWRealm in a few places) and may require unwrapping to
// test for specific function types.
static bool IsCallableNonCCW(const Value& v) {
  return IsCallable(v) && !IsCrossCompartmentWrapper(&v.toObject());
}

static bool IsWasmSuspendingWrapper(const Value& v) {
  return v.isObject() && js::IsWasmSuspendingObject(&v.toObject());
}

bool js::wasm::GetImports(JSContext* cx, const Module& module,
                          HandleObject importObj, ImportValues* imports) {
  const ModuleMetadata& moduleMeta = module.moduleMeta();
  const CodeMetadata& codeMeta = module.codeMeta();
  const BuiltinModuleIds& builtinModules = codeMeta.features().builtinModules;

  if (!moduleMeta.imports.empty() && !importObj) {
    return ThrowBadImportArg(cx);
  }

  BuiltinModuleInstances builtinInstances(cx);
  RootedValue importModuleValue(cx);
  RootedObject importModuleObject(cx);
  bool isImportedStringModule = false;
  RootedValue importFieldValue(cx);

  uint32_t tagIndex = 0;
  const TagDescVector& tags = codeMeta.tags;
  uint32_t globalIndex = 0;
  const GlobalDescVector& globals = codeMeta.globals;
  uint32_t tableIndex = 0;
  const TableDescVector& tables = codeMeta.tables;
  for (const Import& import : moduleMeta.imports) {
    Maybe<BuiltinModuleId> builtinModule =
        ImportMatchesBuiltinModule(import.module.utf8Bytes(), builtinModules);
    if (builtinModule) {
      if (*builtinModule == BuiltinModuleId::JSStringConstants) {
        isImportedStringModule = true;
        importModuleObject = nullptr;
      } else {
        MutableHandle<JSObject*> builtinInstance =
            builtinInstances[*builtinModule];
        if (!builtinInstance && !wasm::InstantiateBuiltinModule(
                                    cx, *builtinModule, builtinInstance)) {
          return false;
        }
        isImportedStringModule = false;
        importModuleObject = builtinInstance;
      }
    } else {
      RootedId moduleName(cx);
      if (!import.module.toPropertyKey(cx, &moduleName)) {
        return false;
      }

      if (!GetProperty(cx, importObj, importObj, moduleName,
                       &importModuleValue)) {
        return false;
      }

      if (!importModuleValue.isObject()) {
        UniqueChars moduleQuoted = import.module.toQuotedString(cx);
        if (!moduleQuoted) {
          ReportOutOfMemory(cx);
          return false;
        }
        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                 JSMSG_WASM_BAD_IMPORT_FIELD,
                                 moduleQuoted.get());
        return false;
      }

      isImportedStringModule = false;
      importModuleObject = &importModuleValue.toObject();
    }
    MOZ_RELEASE_ASSERT(!isImportedStringModule ||
                       import.kind == DefinitionKind::Global);

    if (isImportedStringModule) {
      RootedString stringConstant(cx, import.field.toJSString(cx));
      if (!stringConstant) {
        ReportOutOfMemory(cx);
        return false;
      }
      importFieldValue = StringValue(stringConstant);
    } else {
      RootedId fieldName(cx);
      if (!import.field.toPropertyKey(cx, &fieldName)) {
        return false;
      }
      if (!GetProperty(cx, importModuleObject, importModuleObject, fieldName,
                       &importFieldValue)) {
        return false;
      }
    }

    switch (import.kind) {
      case DefinitionKind::Function: {
        if (!IsCallableNonCCW(importFieldValue) &&
            !IsWasmSuspendingWrapper(importFieldValue)) {
          return ThrowBadImportType(cx, import.field, "Function");
        }

        if (!imports->funcs.append(&importFieldValue.toObject())) {
          ReportOutOfMemory(cx);
          return false;
        }

        break;
      }
      case DefinitionKind::Table: {
        const uint32_t index = tableIndex++;
        if (!importFieldValue.isObject() ||
            !importFieldValue.toObject().is<WasmTableObject>()) {
          return ThrowBadImportType(cx, import.field, "Table");
        }

        Rooted<WasmTableObject*> obj(
            cx, &importFieldValue.toObject().as<WasmTableObject>());
        if (obj->table().elemType() != tables[index].elemType) {
          JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                   JSMSG_WASM_BAD_TBL_TYPE_LINK);
          return false;
        }

        if (!imports->tables.append(obj)) {
          ReportOutOfMemory(cx);
          return false;
        }
        break;
      }
      case DefinitionKind::Memory: {
        if (!importFieldValue.isObject() ||
            !importFieldValue.toObject().is<WasmMemoryObject>()) {
          return ThrowBadImportType(cx, import.field, "Memory");
        }

        if (!imports->memories.append(
                &importFieldValue.toObject().as<WasmMemoryObject>())) {
          ReportOutOfMemory(cx);
          return false;
        }
        break;
      }
      case DefinitionKind::Tag: {
        const uint32_t index = tagIndex++;
        if (!importFieldValue.isObject() ||
            !importFieldValue.toObject().is<WasmTagObject>()) {
          return ThrowBadImportType(cx, import.field, "Tag");
        }

        Rooted<WasmTagObject*> obj(
            cx, &importFieldValue.toObject().as<WasmTagObject>());

        // Checks whether the signature of the imported exception object matches
        // the signature declared in the exception import's TagDesc.
        if (!TagType::matches(*obj->tagType(), *tags[index].type)) {
          UniqueChars fieldQuoted = import.field.toQuotedString(cx);
          UniqueChars moduleQuoted = import.module.toQuotedString(cx);
          if (!fieldQuoted || !moduleQuoted) {
            ReportOutOfMemory(cx);
            return false;
          }
          JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                   JSMSG_WASM_BAD_TAG_SIG, moduleQuoted.get(),
                                   fieldQuoted.get());
          return false;
        }

        if (!imports->tagObjs.append(obj)) {
          ReportOutOfMemory(cx);
          return false;
        }
        break;
      }
      case DefinitionKind::Global: {
        const uint32_t index = globalIndex++;
        const GlobalDesc& global = globals[index];
        MOZ_ASSERT(global.importIndex() == index);

        RootedVal val(cx);
        if (importFieldValue.isObject() &&
            importFieldValue.toObject().is<WasmGlobalObject>()) {
          Rooted<WasmGlobalObject*> obj(
              cx, &importFieldValue.toObject().as<WasmGlobalObject>());

          if (obj->isMutable() != global.isMutable()) {
            JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                     JSMSG_WASM_BAD_GLOB_MUT_LINK);
            return false;
          }

          bool matches = global.isMutable()
                             ? obj->type() == global.type()
                             : ValType::isSubTypeOf(obj->type(), global.type());
          if (!matches) {
            JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                     JSMSG_WASM_BAD_GLOB_TYPE_LINK);
            return false;
          }

          if (imports->globalObjs.length() <= index &&
              !imports->globalObjs.resize(index + 1)) {
            ReportOutOfMemory(cx);
            return false;
          }
          imports->globalObjs[index] = obj;
          val = obj->val();
        } else {
          if (!global.type().isRefType()) {
            if (global.type() == ValType::I64 && !importFieldValue.isBigInt()) {
              return ThrowBadImportType(cx, import.field, "BigInt");
            }
            if (global.type() != ValType::I64 && !importFieldValue.isNumber()) {
              return ThrowBadImportType(cx, import.field, "Number");
            }
          }

          if (global.isMutable()) {
            JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                     JSMSG_WASM_BAD_GLOB_MUT_LINK);
            return false;
          }

          if (!Val::fromJSValue(cx, global.type(), importFieldValue, &val)) {
            return false;
          }
        }

        if (!imports->globalValues.append(val)) {
          ReportOutOfMemory(cx);
          return false;
        }

        break;
      }
    }
  }

  MOZ_ASSERT(globalIndex == globals.length() ||
             !globals[globalIndex].isImport());

  return true;
}

static bool DescribeScriptedCaller(JSContext* cx, ScriptedCaller* caller,
                                   const char* introducer) {
  // Note: JS::DescribeScriptedCaller returns whether a scripted caller was
  // found, not whether an error was thrown. This wrapper function converts
  // back to the more ordinary false-if-error form.

  JS::AutoFilename af;
  if (JS::DescribeScriptedCaller(&af, cx, &caller->line)) {
    caller->filename =
        FormatIntroducedFilename(af.get(), caller->line, introducer);
    if (!caller->filename) {
      ReportOutOfMemory(cx);
      return false;
    }
  }

  return true;
}

static SharedCompileArgs InitCompileArgs(JSContext* cx, FeatureOptions options,
                                         const char* introducer) {
  ScriptedCaller scriptedCaller;
  if (!DescribeScriptedCaller(cx, &scriptedCaller, introducer)) {
    return nullptr;
  }

  return CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options);
}

// ============================================================================
// Testing / Fuzzing support

bool wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code,
                HandleObject importObj,
                MutableHandle<WasmInstanceObject*> instanceObj) {
  if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) {
    return false;
  }

  MutableBytes bytecode = cx->new_<ShareableBytes>();
  if (!bytecode) {
    return false;
  }

  if (!bytecode->append((uint8_t*)code->dataPointerEither().unwrap(),
                        code->byteLength().valueOr(0))) {
    ReportOutOfMemory(cx);
    return false;
  }

  FeatureOptions options;
  SharedCompileArgs compileArgs = InitCompileArgs(cx, options, "wasm_eval");
  if (!compileArgs) {
    return false;
  }

  UniqueChars error;
  UniqueCharsVector warnings;
  SharedModule module =
      CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr);
  if (!module) {
    if (error) {
      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               JSMSG_WASM_COMPILE_ERROR, error.get());
      return false;
    }
    return ThrowCompileOutOfMemory(cx);
  }

  Rooted<ImportValues> imports(cx);
  if (!GetImports(cx, *module, importObj, imports.address())) {
    return false;
  }

  return module->instantiate(cx, imports.get(), nullptr, instanceObj);
}

struct MOZ_STACK_CLASS SerializeListener : JS::OptimizedEncodingListener {
  // MOZ_STACK_CLASS means these can be nops.
  MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override { return 0; }
  MozExternalRefCountType MOZ_XPCOM_ABI Release() override { return 0; }

  mozilla::DebugOnly<bool> called = false;
  Bytes* serialized;
  explicit SerializeListener(Bytes* serialized) : serialized(serialized) {}

  void storeOptimizedEncoding(const uint8_t* bytes, size_t length) override {
    MOZ_ASSERT(!called);
    called = true;
    if (serialized->resizeUninitialized(length)) {
      memcpy(serialized->begin(), bytes, length);
    }
  }
};

bool wasm::CompileAndSerialize(JSContext* cx, const ShareableBytes& bytecode,
                               Bytes* serialized) {
  // The caller must check that code caching is available
  MOZ_ASSERT(CodeCachingAvailable(cx));

  // Create and manually fill in compile args for code caching
  MutableCompileArgs compileArgs = js_new<CompileArgs>();
  if (!compileArgs) {
    return false;
  }

  // The caller has ensured CodeCachingAvailable(). Moreover, we want to ensure
  // we go straight to tier-2 so that we synchronously call
  // JS::OptimizedEncodingListener::storeOptimizedEncoding().
  compileArgs->baselineEnabled = false;
  compileArgs->forceTiering = false;

  // We always pick Ion here, and we depend on CodeCachingAvailable() having
  // determined that Ion is available, see comments at CodeCachingAvailable().
  // To do better, we need to pass information about which compiler that should
  // be used into CompileAndSerialize().
  compileArgs->ionEnabled = true;

  // Select features that are enabled. This is guaranteed to be consistent with
  // our compiler selection, as code caching is only available if ion is
  // available, and ion is only available if it's not disabled by enabled
  // features.
  compileArgs->features = FeatureArgs::build(cx, FeatureOptions());

  SerializeListener listener(serialized);

  UniqueChars error;
  UniqueCharsVector warnings;
  SharedModule module =
      CompileBuffer(*compileArgs, bytecode, &error, &warnings, &listener);
  if (!module) {
    fprintf(stderr, "Compilation error: %s\n", error ? error.get() : "oom");
    return false;
  }

  MOZ_ASSERT(module->code().hasCompleteTier(Tier::Serialized));
  MOZ_ASSERT(listener.called);
  return !listener.serialized->empty();
}

bool wasm::DeserializeModule(JSContext* cx, const Bytes& serialized,
                             MutableHandleObject moduleObj) {
  MutableModule module =
      Module::deserialize(serialized.begin(), serialized.length());
  if (!module) {
    ReportOutOfMemory(cx);
    return false;
  }

  moduleObj.set(module->createObject(cx));
  return !!moduleObj;
}

// ============================================================================
// Common functions

// '[EnforceRange] unsigned long' types are coerced with
//    ConvertToInt(v, 32, 'unsigned')
// defined in Web IDL Section 3.2.4.9.
//
// This just generalizes that to an arbitrary limit that is representable as an
// integer in double form.

static bool EnforceRange(JSContext* cx, HandleValue v, const char* kind,
                         const char* noun, uint64_t max, uint64_t* val) {
  // Step 4.
  double x;
  if (!ToNumber(cx, v, &x)) {
    return false;
  }

  // Step 5.
  if (mozilla::IsNegativeZero(x)) {
    x = 0.0;
  }

  // Step 6.1.
  if (!std::isfinite(x)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_ENFORCE_RANGE, kind, noun);
    return false;
  }

  // Step 6.2.
  x = JS::ToInteger(x);

  // Step 6.3.
  if (x < 0 || x > double(max)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_ENFORCE_RANGE, kind, noun);
    return false;
  }

  *val = uint64_t(x);
  MOZ_ASSERT(double(*val) == x);
  return true;
}

static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind,
                            const char* noun, uint32_t* u32) {
  uint64_t u64 = 0;
  if (!EnforceRange(cx, v, kind, noun, uint64_t(UINT32_MAX), &u64)) {
    return false;
  }
  *u32 = uint32_t(u64);
  return true;
}

static bool EnforceRangeU64(JSContext* cx, HandleValue v, const char* kind,
                            const char* noun, uint64_t* u64) {
  // The max is Number.MAX_SAFE_INTEGER
  return EnforceRange(cx, v, kind, noun, (1LL << 53) - 1, u64);
}

static bool EnforceRangeBigInt64(JSContext* cx, HandleValue v, const char* kind,
                                 const char* noun, uint64_t* u64) {
  RootedBigInt bi(cx, ToBigInt(cx, v));
  if (!bi) {
    return false;
  }
  if (!BigInt::isUint64(bi, u64)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_ENFORCE_RANGE, kind, noun);
    return false;
  }
  return true;
}

static bool EnforceAddressValue(JSContext* cx, HandleValue v,
                                AddressType addressType, const char* kind,
                                const char* noun, uint64_t* result) {
  switch (addressType) {
    case AddressType::I32: {
      uint32_t result32;
      if (!EnforceRangeU32(cx, v, kind, noun, &result32)) {
        return false;
      }
      *result = uint64_t(result32);
      return true;
    }
    case AddressType::I64:
      return EnforceRangeBigInt64(cx, v, kind, noun, result);
    default:
      MOZ_CRASH("unknown address type");
  }
}

// The AddressValue typedef, a union of number and bigint, is used in the JS API
// spec for memory and table arguments, where number is used for memory32 and
// bigint is used for memory64.
[[nodiscard]] static bool CreateAddressValue(JSContext* cx, uint64_t value,
                                             AddressType addressType,
                                             MutableHandleValue addressValue) {
  switch (addressType) {
    case AddressType::I32:
      MOZ_ASSERT(value <= UINT32_MAX);
      addressValue.set(NumberValue(value));
      return true;
    case AddressType::I64: {
      BigInt* bi = BigInt::createFromUint64(cx, value);
      if (!bi) {
        return false;
      }
      addressValue.set(BigIntValue(bi));
      return true;
    }
    default:
      MOZ_CRASH("unknown address type");
  }
}

// Gets an AddressValue property ("initial" or "maximum") from a
// MemoryDescriptor or TableDescriptor. The values returned by this should be
// run through CheckLimits to enforce the validation limits prescribed by the
// spec.
static bool GetDescriptorAddressValue(JSContext* cx, HandleObject obj,
                                      const char* name, const char* noun,
                                      const char* msg, AddressType addressType,
                                      bool* found, uint64_t* value) {
  JSAtom* atom = Atomize(cx, name, strlen(name));
  if (!atom) {
    return false;
  }
  RootedId id(cx, AtomToId(atom));

  RootedValue val(cx);
  if (!GetProperty(cx, obj, obj, id, &val)) {
    return false;
  }

  if (val.isUndefined()) {
    *found = false;
    return true;
  }
  *found = true;

  return EnforceAddressValue(cx, val, addressType, noun, msg, value);
}

static bool GetLimits(JSContext* cx, HandleObject obj, LimitsKind kind,
                      Limits* limits) {
  limits->addressType = AddressType::I32;

  // Limits may specify an alternate address type, and we need this to check the
  // ranges for initial and maximum, so look for the address type first.
#ifdef ENABLE_WASM_MEMORY64
  // Get the address type field
  JSAtom* addressTypeAtom = Atomize(cx, "address", strlen("address"));
  if (!addressTypeAtom) {
    return false;
  }
  RootedId addressTypeId(cx, AtomToId(addressTypeAtom));
  RootedValue addressTypeVal(cx);
  if (!GetProperty(cx, obj, obj, addressTypeId, &addressTypeVal)) {
    return false;
  }

  // The address type has a default value
  if (!addressTypeVal.isUndefined()) {
    if (!ToAddressType(cx, addressTypeVal, &limits->addressType)) {
      return false;
    }

    if (limits->addressType == AddressType::I64 && !Memory64Available(cx)) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_WASM_NO_MEM64_LINK);
      return false;
    }
  }
#endif

  const char* noun = ToString(kind);
  uint64_t limit = 0;

  bool haveInitial = false;
  if (!GetDescriptorAddressValue(cx, obj, "initial", noun, "initial size",
                                 limits->addressType, &haveInitial, &limit)) {
    return false;
  }
  if (haveInitial) {
    limits->initial = limit;
  }

  bool haveMinimum = false;
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
  if (!GetDescriptorAddressValue(cx, obj, "minimum", noun, "initial size",
                                 limits->addressType, &haveMinimum, &limit)) {
    return false;
  }
  if (haveMinimum) {
    limits->initial = limit;
  }
#endif

  if (!(haveInitial || haveMinimum)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_MISSING_REQUIRED, "initial");
    return false;
  }
  if (haveInitial && haveMinimum) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_SUPPLY_ONLY_ONE, "minimum""initial");
    return false;
  }

  bool haveMaximum = false;
  if (!GetDescriptorAddressValue(cx, obj, "maximum", noun, "maximum size",
                                 limits->addressType, &haveMaximum, &limit)) {
    return false;
  }
  if (haveMaximum) {
    limits->maximum = Some(limit);
  }

  limits->shared = Shareable::False;

  // Memory limits may be shared.
  if (kind == LimitsKind::Memory) {
    // Get the shared field
    JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared"));
    if (!sharedAtom) {
      return false;
    }
    RootedId sharedId(cx, AtomToId(sharedAtom));

    RootedValue sharedVal(cx);
    if (!GetProperty(cx, obj, obj, sharedId, &sharedVal)) {
      return false;
    }

    // shared's default value is false, which is already the value set above.
    if (!sharedVal.isUndefined()) {
      limits->shared =
          ToBoolean(sharedVal) ? Shareable::True : Shareable::False;

      if (limits->shared == Shareable::True) {
        if (!haveMaximum) {
          JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                    JSMSG_WASM_MISSING_MAXIMUM, noun);
          return false;
        }

        if (!cx->realm()
                 ->creationOptions()
                 .getSharedMemoryAndAtomicsEnabled()) {
          JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                    JSMSG_WASM_NO_SHMEM_LINK);
          return false;
        }
      }
    }
  }

  return true;
}

static bool CheckLimits(JSContext* cx, uint64_t validationMax, LimitsKind kind,
                        Limits* limits) {
  const char* noun = ToString(kind);

  // There are several layers of validation and error-throwing here, including
  // one which is currently not defined by the JS API spec:
  //
  // - [EnforceRange] on parameters (must be TypeError)
  // - A check that initial <= maximum (must be RangeError)
  // - Either a mem_alloc or table_alloc operation, which has two components:
  //   - A pre-condition that the given memory or table type is valid
  //     (not specified, RangeError in practice)
  //   - The actual allocation (should report OOM if it fails)
  //
  // There are two questions currently left open by the spec: when is the memory
  // or table type validated, and if it is invalid, what type of exception does
  // it throw? In practice, all browsers throw RangeError, and by the time you
  // read this the spec will hopefully have been updated to reflect this. See
  // the following issue: https://github.com/WebAssembly/spec/issues/1792

  // Check that initial <= maximum
  if (limits->maximum.isSome() && *limits->maximum < limits->initial) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_MAX_LT_INITIAL, noun);
    return false;
  }

  // Check wasm validation limits
  if (limits->initial > validationMax) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE,
                             noun, "initial size");
    return false;
  }
  if (limits->maximum.isSome() && *limits->maximum > validationMax) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE,
                             noun, "maximum size");
    return false;
  }

  return true;
}

template <class Classconst char* name>
static JSObject* CreateWasmConstructor(JSContext* cx, JSProtoKey key) {
  Rooted<JSAtom*> className(cx, Atomize(cx, name, strlen(name)));
  if (!className) {
    return nullptr;
  }

  return NewNativeConstructor(cx, Class::construct, 1, className);
}

static JSObject* GetWasmConstructorPrototype(JSContext* cx,
                                             const CallArgs& callArgs,
                                             JSProtoKey key) {
  RootedObject proto(cx);
  if (!GetPrototypeFromBuiltinConstructor(cx, callArgs, key, &proto)) {
    return nullptr;
  }
  if (!proto) {
    proto = GlobalObject::getOrCreatePrototype(cx, key);
  }
  return proto;
}

[[nodiscard]] static bool ParseValTypes(JSContext* cx, HandleValue src,
                                        ValTypeVector& dest) {
  JS::ForOfIterator iterator(cx);

  if (!iterator.init(src, JS::ForOfIterator::ThrowOnNonIterable)) {
    return false;
  }

  RootedValue nextParam(cx);
  while (true) {
    bool done;
    if (!iterator.next(&nextParam, &done)) {
      return false;
    }
    if (done) {
      break;
    }

    ValType valType;
    if (!ToValType(cx, nextParam, &valType) || !dest.append(valType)) {
      return false;
    }
  }
  return true;
}

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
template <typename T>
static JSString* TypeToString(JSContext* cx, T type) {
  UniqueChars chars = ToString(type, nullptr);
  if (!chars) {
    return nullptr;
  }
  return NewStringCopyUTF8Z(
      cx, JS::ConstUTF8CharsZ(chars.get(), strlen(chars.get())));
}

#  ifdef ENABLE_WASM_MEMORY64
static JSString* AddressTypeToString(JSContext* cx, AddressType type) {
  return JS_NewStringCopyZ(cx, ToString(type));
}
#  endif

[[nodiscard]] static JSObject* ValTypesToArray(JSContext* cx,
                                               const ValTypeVector& valTypes) {
  Rooted<ArrayObject*> arrayObj(cx, NewDenseEmptyArray(cx));
  if (!arrayObj) {
    return nullptr;
  }
  for (ValType valType : valTypes) {
    RootedString type(cx, TypeToString(cx, valType));
    if (!type) {
      return nullptr;
    }
    if (!NewbornArrayPush(cx, arrayObj, StringValue(type))) {
      return nullptr;
    }
  }
  return arrayObj;
}

static JSObject* FuncTypeToObject(JSContext* cx, const FuncType& type) {
  Rooted<IdValueVector> props(cx, IdValueVector(cx));

  RootedObject parametersObj(cx, ValTypesToArray(cx, type.args()));
  if (!parametersObj ||
      !props.append(IdValuePair(NameToId(cx->names().parameters),
                                ObjectValue(*parametersObj)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  RootedObject resultsObj(cx, ValTypesToArray(cx, type.results()));
  if (!resultsObj || !props.append(IdValuePair(NameToId(cx->names().results),
                                               ObjectValue(*resultsObj)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return NewPlainObjectWithUniqueNames(cx, props);
}

static JSObject* TableTypeToObject(JSContext* cx, AddressType addressType,
                                   RefType type, uint64_t initial,
                                   Maybe<uint64_t> maximum) {
  Rooted<IdValueVector> props(cx, IdValueVector(cx));

  RootedString elementType(cx, TypeToString(cx, type));
  if (!elementType || !props.append(IdValuePair(NameToId(cx->names().element),
                                                StringValue(elementType)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  if (maximum.isSome()) {
    RootedId maximumId(cx, NameToId(cx->names().maximum));
    RootedValue maximumValue(cx);
    if (!CreateAddressValue(cx, maximum.value(), addressType, &maximumValue)) {
      ReportOutOfMemory(cx);
      return nullptr;
    }
    if (!props.append(IdValuePair(maximumId, maximumValue))) {
      ReportOutOfMemory(cx);
      return nullptr;
    }
  }

  RootedId minimumId(cx, NameToId(cx->names().minimum));
  RootedValue minimumValue(cx);
  if (!CreateAddressValue(cx, initial, addressType, &minimumValue)) {
    ReportOutOfMemory(cx);
    return nullptr;
  }
  if (!props.append(IdValuePair(minimumId, minimumValue))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

#  ifdef ENABLE_WASM_MEMORY64
  RootedString at(cx, AddressTypeToString(cx, addressType));
  if (!at) {
    return nullptr;
  }
  if (!props.append(
          IdValuePair(NameToId(cx->names().address), StringValue(at)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }
#  endif

  return NewPlainObjectWithUniqueNames(cx, props);
}

static JSObject* MemoryTypeToObject(JSContext* cx, bool shared,
                                    wasm::AddressType addressType,
                                    wasm::Pages minPages,
                                    Maybe<wasm::Pages> maxPages) {
  Rooted<IdValueVector> props(cx, IdValueVector(cx));
  if (maxPages) {
    RootedId maximumId(cx, NameToId(cx->names().maximum));
    RootedValue maximumValue(cx);
    if (!CreateAddressValue(cx, maxPages.value().value(), addressType,
                            &maximumValue)) {
      ReportOutOfMemory(cx);
      return nullptr;
    }
    if (!props.append(IdValuePair(maximumId, maximumValue))) {
      ReportOutOfMemory(cx);
      return nullptr;
    }
  }

  RootedId minimumId(cx, NameToId(cx->names().minimum));
  RootedValue minimumValue(cx);
  if (!CreateAddressValue(cx, minPages.value(), addressType, &minimumValue)) {
    ReportOutOfMemory(cx);
    return nullptr;
  }
  if (!props.append(IdValuePair(minimumId, minimumValue))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

#  ifdef ENABLE_WASM_MEMORY64
  RootedString at(cx, AddressTypeToString(cx, addressType));
  if (!at) {
    return nullptr;
  }
  if (!props.append(
          IdValuePair(NameToId(cx->names().address), StringValue(at)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }
#  endif

  if (!props.append(
          IdValuePair(NameToId(cx->names().shared), BooleanValue(shared)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return NewPlainObjectWithUniqueNames(cx, props);
}

static JSObject* GlobalTypeToObject(JSContext* cx, ValType type,
                                    bool isMutable) {
  Rooted<IdValueVector> props(cx, IdValueVector(cx));

  if (!props.append(IdValuePair(NameToId(cx->names().mutable_),
                                BooleanValue(isMutable)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  RootedString valueType(cx, TypeToString(cx, type));
  if (!valueType || !props.append(IdValuePair(NameToId(cx->names().value),
                                              StringValue(valueType)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return NewPlainObjectWithUniqueNames(cx, props);
}

static JSObject* TagTypeToObject(JSContext* cx,
                                 const wasm::ValTypeVector& params) {
  Rooted<IdValueVector> props(cx, IdValueVector(cx));

  RootedObject parametersObj(cx, ValTypesToArray(cx, params));
  if (!parametersObj ||
      !props.append(IdValuePair(NameToId(cx->names().parameters),
                                ObjectValue(*parametersObj)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return NewPlainObjectWithUniqueNames(cx, props);
}
#endif  // ENABLE_WASM_TYPE_REFLECTIONS

// ============================================================================
// WebAssembly.Module class and methods

const JSClassOps WasmModuleObject::classOps_ = {
    nullptr,                     // addProperty
    nullptr,                     // delProperty
    nullptr,                     // enumerate
    nullptr,                     // newEnumerate
    nullptr,                     // resolve
    nullptr,                     // mayResolve
    WasmModuleObject::finalize,  // finalize
    nullptr,                     // call
    nullptr,                     // construct
    nullptr,                     // trace
};

const JSClass WasmModuleObject::class_ = {
    "WebAssembly.Module",
    JSCLASS_DELAY_METADATA_BUILDER |
        JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS) |
        JSCLASS_FOREGROUND_FINALIZE,
    &WasmModuleObject::classOps_,
    &WasmModuleObject::classSpec_,
};

const JSClass& WasmModuleObject::protoClass_ = PlainObject::class_;

static constexpr char WasmModuleName[] = "Module";

const ClassSpec WasmModuleObject::classSpec_ = {
    CreateWasmConstructor<WasmModuleObject, WasmModuleName>,
    GenericCreatePrototype<WasmModuleObject>,
    WasmModuleObject::static_methods,
    nullptr,
    WasmModuleObject::methods,
    WasmModuleObject::properties,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

const JSPropertySpec WasmModuleObject::properties[] = {
    JS_STRING_SYM_PS(toStringTag, "WebAssembly.Module", JSPROP_READONLY),
    JS_PS_END,
};

const JSFunctionSpec WasmModuleObject::methods[] = {
    JS_FS_END,
};

const JSFunctionSpec WasmModuleObject::static_methods[] = {
    JS_FN("imports", WasmModuleObject::imports, 1, JSPROP_ENUMERATE),
    JS_FN("exports", WasmModuleObject::exports, 1, JSPROP_ENUMERATE),
    JS_FN("customSections", WasmModuleObject::customSections, 2,
          JSPROP_ENUMERATE),
    JS_FS_END,
};

/* static */
void WasmModuleObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  const Module& module = obj->as<WasmModuleObject>().module();
  size_t codeMemory = module.tier1CodeMemoryUsed();
  if (codeMemory) {
    obj->zone()->decJitMemory(codeMemory);
  }
  gcx->release(obj, &module, module.gcMallocBytesExcludingCode(),
               MemoryUse::WasmModule);
}

static bool IsModuleObject(JSObject* obj, const Module** module) {
  WasmModuleObject* mobj = obj->maybeUnwrapIf<WasmModuleObject>();
  if (!mobj) {
    return false;
  }

  *module = &mobj->module();
  return true;
}

static bool GetModuleArg(JSContext* cx, const CallArgs& args,
                         uint32_t numRequired, const char* name,
                         const Module** module) {
  if (!args.requireAtLeast(cx, name, numRequired)) {
    return false;
  }

  if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), module)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_MOD_ARG);
    return false;
  }

  return true;
}

struct KindNames {
  Rooted<PropertyName*> kind;
  Rooted<PropertyName*> table;
  Rooted<PropertyName*> memory;
  Rooted<PropertyName*> tag;
  Rooted<PropertyName*> type;

  explicit KindNames(JSContext* cx)
      : kind(cx), table(cx), memory(cx), tag(cx), type(cx) {}
};

static bool InitKindNames(JSContext* cx, KindNames* names) {
  JSAtom* kind = Atomize(cx, "kind", strlen("kind"));
  if (!kind) {
    return false;
  }
  names->kind = kind->asPropertyName();

  JSAtom* table = Atomize(cx, "table", strlen("table"));
  if (!table) {
    return false;
  }
  names->table = table->asPropertyName();

  JSAtom* memory = Atomize(cx, "memory", strlen("memory"));
  if (!memory) {
    return false;
  }
  names->memory = memory->asPropertyName();

  JSAtom* tag = Atomize(cx, "tag", strlen("tag"));
  if (!tag) {
    return false;
  }
  names->tag = tag->asPropertyName();

  JSAtom* type = Atomize(cx, "type", strlen("type"));
  if (!type) {
    return false;
  }
  names->type = type->asPropertyName();

  return true;
}

static JSString* KindToString(JSContext* cx, const KindNames& names,
                              DefinitionKind kind) {
  switch (kind) {
    case DefinitionKind::Function:
      return cx->names().function;
    case DefinitionKind::Table:
      return names.table;
    case DefinitionKind::Memory:
      return names.memory;
    case DefinitionKind::Global:
      return cx->names().global;
    case DefinitionKind::Tag:
      return names.tag;
  }

  MOZ_CRASH("invalid kind");
}

/* static */
bool WasmModuleObject::imports(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  const Module* module;
  if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.imports", &module)) {
    return false;
  }

  KindNames names(cx);
  if (!InitKindNames(cx, &names)) {
    return false;
  }

  const ModuleMetadata& moduleMeta = module->moduleMeta();

  RootedValueVector elems(cx);
  if (!elems.reserve(moduleMeta.imports.length())) {
    return false;
  }

#if defined(ENABLE_WASM_JS_STRING_BUILTINS) || \
    defined(ENABLE_WASM_TYPE_REFLECTIONS)
  const CodeMetadata& codeMeta = module->codeMeta();
#endif

#if defined(ENABLE_WASM_TYPE_REFLECTIONS)
  size_t numFuncImport = 0;
  size_t numMemoryImport = 0;
  size_t numGlobalImport = 0;
  size_t numTableImport = 0;
  size_t numTagImport = 0;
#endif  // ENABLE_WASM_TYPE_REFLECTIONS

  for (const Import& import : moduleMeta.imports) {
#ifdef ENABLE_WASM_JS_STRING_BUILTINS
    Maybe<BuiltinModuleId> builtinModule = ImportMatchesBuiltinModule(
        import.module.utf8Bytes(), codeMeta.features().builtinModules);
    if (builtinModule) {
      continue;
    }
#endif

    Rooted<IdValueVector> props(cx, IdValueVector(cx));
    if (!props.reserve(3)) {
      return false;
    }

    JSString* moduleStr = import.module.toAtom(cx);
    if (!moduleStr) {
      return false;
    }
    props.infallibleAppend(
        IdValuePair(NameToId(cx->names().module), StringValue(moduleStr)));

    JSString* nameStr = import.field.toAtom(cx);
    if (!nameStr) {
      return false;
    }
    props.infallibleAppend(
        IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));

    JSString* kindStr = KindToString(cx, names, import.kind);
    if (!kindStr) {
      return false;
    }
    props.infallibleAppend(
        IdValuePair(NameToId(names.kind), StringValue(kindStr)));

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
    RootedObject typeObj(cx);
    switch (import.kind) {
      case DefinitionKind::Function: {
        size_t funcIndex = numFuncImport++;
        const FuncType& funcType = codeMeta.getFuncType(funcIndex);
        typeObj = FuncTypeToObject(cx, funcType);
        break;
      }
      case DefinitionKind::Table: {
        size_t tableIndex = numTableImport++;
        const TableDesc& table = codeMeta.tables[tableIndex];
        typeObj =
            TableTypeToObject(cx, table.addressType(), table.elemType,
                              table.initialLength(), table.maximumLength());
        break;
      }
      case DefinitionKind::Memory: {
        size_t memoryIndex = numMemoryImport++;
        const MemoryDesc& memory = codeMeta.memories[memoryIndex];
        typeObj =
            MemoryTypeToObject(cx, memory.isShared(), memory.addressType(),
                               memory.initialPages(), memory.maximumPages());
        break;
      }
      case DefinitionKind::Global: {
        size_t globalIndex = numGlobalImport++;
        const GlobalDesc& global = codeMeta.globals[globalIndex];
        typeObj = GlobalTypeToObject(cx, global.type(), global.isMutable());
        break;
      }
      case DefinitionKind::Tag: {
        size_t tagIndex = numTagImport++;
        const TagDesc& tag = codeMeta.tags[tagIndex];
        typeObj = TagTypeToObject(cx, tag.type->argTypes());
        break;
      }
    }

    if (!typeObj || !props.append(IdValuePair(NameToId(names.type),
                                              ObjectValue(*typeObj)))) {
      ReportOutOfMemory(cx);
      return false;
    }
#endif  // ENABLE_WASM_TYPE_REFLECTIONS

    JSObject* obj = NewPlainObjectWithUniqueNames(cx, props);
    if (!obj) {
      return false;
    }

    elems.infallibleAppend(ObjectValue(*obj));
  }

  JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
  if (!arr) {
    return false;
  }

  args.rval().setObject(*arr);
  return true;
}

/* static */
bool WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  const Module* module;
  if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.exports", &module)) {
    return false;
  }

  KindNames names(cx);
  if (!InitKindNames(cx, &names)) {
    return false;
  }

  const ModuleMetadata& moduleMeta = module->moduleMeta();

  RootedValueVector elems(cx);
  if (!elems.reserve(moduleMeta.exports.length())) {
    return false;
  }

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
  const CodeMetadata& codeMeta = module->codeMeta();
#endif  // ENABLE_WASM_TYPE_REFLECTIONS

  for (const Export& exp : moduleMeta.exports) {
    Rooted<IdValueVector> props(cx, IdValueVector(cx));
    if (!props.reserve(2)) {
      return false;
    }

    JSString* nameStr = exp.fieldName().toAtom(cx);
    if (!nameStr) {
      return false;
    }
    props.infallibleAppend(
        IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));

    JSString* kindStr = KindToString(cx, names, exp.kind());
    if (!kindStr) {
      return false;
    }
    props.infallibleAppend(
        IdValuePair(NameToId(names.kind), StringValue(kindStr)));

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
    RootedObject typeObj(cx);
    switch (exp.kind()) {
      case DefinitionKind::Function: {
        const FuncType& funcType =
            module->codeMeta().getFuncType(exp.funcIndex());
        typeObj = FuncTypeToObject(cx, funcType);
        break;
      }
      case DefinitionKind::Table: {
        const TableDesc& table = codeMeta.tables[exp.tableIndex()];
        typeObj =
            TableTypeToObject(cx, table.addressType(), table.elemType,
                              table.initialLength(), table.maximumLength());
        break;
      }
      case DefinitionKind::Memory: {
        const MemoryDesc& memory = codeMeta.memories[exp.memoryIndex()];
        typeObj =
            MemoryTypeToObject(cx, memory.isShared(), memory.addressType(),
                               memory.initialPages(), memory.maximumPages());
        break;
      }
      case DefinitionKind::Global: {
        const GlobalDesc& global = codeMeta.globals[exp.globalIndex()];
        typeObj = GlobalTypeToObject(cx, global.type(), global.isMutable());
        break;
      }
      case DefinitionKind::Tag: {
        const TagDesc& tag = codeMeta.tags[exp.tagIndex()];
        typeObj = TagTypeToObject(cx, tag.type->argTypes());
        break;
      }
    }

    if (!typeObj || !props.append(IdValuePair(NameToId(names.type),
                                              ObjectValue(*typeObj)))) {
      ReportOutOfMemory(cx);
      return false;
    }
#endif  // ENABLE_WASM_TYPE_REFLECTIONS

    JSObject* obj = NewPlainObjectWithUniqueNames(cx, props);
    if (!obj) {
      return false;
    }

    elems.infallibleAppend(ObjectValue(*obj));
  }

  JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
  if (!arr) {
    return false;
  }

  args.rval().setObject(*arr);
  return true;
}

/* static */
bool WasmModuleObject::customSections(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  const Module* module;
  if (!GetModuleArg(cx, args, 2, "WebAssembly.Module.customSections",
                    &module)) {
    return false;
  }

  Vector<char, 8> name(cx);
  {
    RootedString str(cx, ToString(cx, args.get(1)));
    if (!str) {
      return false;
    }

    Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
    if (!linear) {
      return false;
    }

    if (!name.initLengthUninitialized(
            JS::GetDeflatedUTF8StringLength(linear))) {
      return false;
    }

    (void)JS::DeflateStringToUTF8Buffer(linear,
                                        Span(name.begin(), name.length()));
  }

  RootedValueVector elems(cx);
  Rooted<ArrayBufferObject*> buf(cx);
  for (const CustomSection& cs : module->moduleMeta().customSections) {
    if (name.length() != cs.name.length()) {
      continue;
    }
    if (memcmp(name.begin(), cs.name.begin(), name.length()) != 0) {
      continue;
    }

    buf = ArrayBufferObject::createZeroed(cx, cs.payload->length());
    if (!buf) {
      return false;
    }

    memcpy(buf->dataPointer(), cs.payload->begin(), cs.payload->length());
    if (!elems.append(ObjectValue(*buf))) {
      return false;
    }
  }

  JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
  if (!arr) {
    return false;
  }

  args.rval().setObject(*arr);
  return true;
}

/* static */
WasmModuleObject* WasmModuleObject::create(JSContext* cx, const Module& module,
                                           HandleObject proto) {
  AutoSetNewObjectMetadata metadata(cx);
  auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
  if (!obj) {
    return nullptr;
  }

  // The pipeline state on some architectures may retain stale instructions
  // even after we invalidate the instruction cache. There is no generally
  // available method to broadcast this pipeline flush to all threads after
  // we've compiled new code, so conservatively perform one here when we're
  // receiving a module that may have been compiled from another thread.
  //
  // The cost of this flush is expected to minimal enough to not be worth
  // optimizing away in the case the module was compiled on this thread.
  jit::FlushExecutionContext();

  // This accounts for module allocation size (excluding code which is handled
  // separately - see below). This assumes that the size of associated data
  // doesn't change for the life of the WasmModuleObject. The size is counted
  // once per WasmModuleObject referencing a Module.
  InitReservedSlot(obj, MODULE_SLOT, const_cast<Module*>(&module),
                   module.gcMallocBytesExcludingCode(), MemoryUse::WasmModule);
  module.AddRef();

  // Bug 1569888: We account for the first tier here; the second tier, if
  // different, also needs to be accounted for.
  size_t codeMemory = module.tier1CodeMemoryUsed();
  if (codeMemory) {
    cx->zone()->incJitMemory(codeMemory);
  }
  return obj;
}

static bool GetBufferSource(JSContext* cx, JSObject* obj, unsigned errorNumber,
                            MutableBytes* bytecode) {
  *bytecode = cx->new_<ShareableBytes>();
  if (!*bytecode) {
    return false;
  }

  JSObject* unwrapped = CheckedUnwrapStatic(obj);

  SharedMem<uint8_t*> dataPointer;
  size_t byteLength;
  if (!unwrapped || !IsBufferSource(unwrapped, &dataPointer, &byteLength)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
    return false;
  }

  if (!(*bytecode)->append(dataPointer.unwrap(), byteLength)) {
    ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

static bool ReportCompileWarnings(JSContext* cx,
                                  const UniqueCharsVector& warnings) {
  // Avoid spamming the console.
  size_t numWarnings = std::min<size_t>(warnings.length(), 3);

  for (size_t i = 0; i < numWarnings; i++) {
    if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING, warnings[i].get())) {
      return false;
    }
  }

  if (warnings.length() > numWarnings) {
    if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING,
                         "other warnings suppressed")) {
      return false;
    }
  }

  return true;
}

/* static */
bool WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs callArgs = CallArgsFromVp(argc, vp);

  Log(cx, "sync new Module() started");

  if (!ThrowIfNotConstructing(cx, callArgs, "Module")) {
    return false;
  }

  JS::RootedVector<JSString*> parameterStrings(cx);
  JS::RootedVector<Value> parameterArgs(cx);
  bool canCompileStrings = false;
  if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
                                   JS::CompilationType::Undefined,
                                   parameterStrings, nullptr, parameterArgs,
                                   NullHandleValue, &canCompileStrings)) {
    return false;
  }
  if (!canCompileStrings) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_CSP_BLOCKED_WASM, "WebAssembly.Module");
    return false;
  }

  if (!callArgs.requireAtLeast(cx, "WebAssembly.Module", 1)) {
    return false;
  }

  if (!callArgs[0].isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_BUF_ARG);
    return false;
  }

  MutableBytes bytecode;
  if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG,
                       &bytecode)) {
    return false;
  }

  FeatureOptions options;
  if (!options.init(cx, callArgs.get(1))) {
    return false;
  }

  SharedCompileArgs compileArgs =
      InitCompileArgs(cx, options, "WebAssembly.Module");
  if (!compileArgs) {
    return false;
  }

  UniqueChars error;
  UniqueCharsVector warnings;
  SharedModule module =
      CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr);

  if (!ReportCompileWarnings(cx, warnings)) {
    return false;
  }
  if (!module) {
    if (error) {
      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               JSMSG_WASM_COMPILE_ERROR, error.get());
      return false;
    }
    return ThrowCompileOutOfMemory(cx);
  }

  RootedObject proto(
      cx, GetWasmConstructorPrototype(cx, callArgs, JSProto_WasmModule));
  if (!proto) {
    ReportOutOfMemory(cx);
    return false;
  }

  RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto));
  if (!moduleObj) {
    return false;
  }

  Log(cx, "sync new Module() succeded");

  callArgs.rval().setObject(*moduleObj);
  return true;
}

const Module& WasmModuleObject::module() const {
  MOZ_ASSERT(is<WasmModuleObject>());
  return *(const Module*)getReservedSlot(MODULE_SLOT).toPrivate();
}

// ============================================================================
// WebAssembly.Instance class and methods

const JSClassOps WasmInstanceObject::classOps_ = {
    nullptr,                       // addProperty
    nullptr,                       // delProperty
    nullptr,                       // enumerate
    nullptr,                       // newEnumerate
    nullptr,                       // resolve
    nullptr,                       // mayResolve
    WasmInstanceObject::finalize,  // finalize
    nullptr,                       // call
    nullptr,                       // construct
    WasmInstanceObject::trace,     // trace
};

const JSClass WasmInstanceObject::class_ = {
    "WebAssembly.Instance",
    JSCLASS_DELAY_METADATA_BUILDER |
        JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS) |
        JSCLASS_FOREGROUND_FINALIZE,
    &WasmInstanceObject::classOps_,
    &WasmInstanceObject::classSpec_,
};

const JSClass& WasmInstanceObject::protoClass_ = PlainObject::class_;

static constexpr char WasmInstanceName[] = "Instance";

const ClassSpec WasmInstanceObject::classSpec_ = {
    CreateWasmConstructor<WasmInstanceObject, WasmInstanceName>,
    GenericCreatePrototype<WasmInstanceObject>,
    WasmInstanceObject::static_methods,
    nullptr,
    WasmInstanceObject::methods,
    WasmInstanceObject::properties,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

static bool IsInstance(HandleValue v) {
  return v.isObject() && v.toObject().is<WasmInstanceObject>();
}

/* static */
bool WasmInstanceObject::exportsGetterImpl(JSContext* cx,
                                           const CallArgs& args) {
  args.rval().setObject(
      args.thisv().toObject().as<WasmInstanceObject>().exportsObj());
  return true;
}

/* static */
bool WasmInstanceObject::exportsGetter(JSContext* cx, unsigned argc,
                                       Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsInstance, exportsGetterImpl>(cx, args);
}

const JSPropertySpec WasmInstanceObject::properties[] = {
    JS_PSG("exports", WasmInstanceObject::exportsGetter, JSPROP_ENUMERATE),
    JS_STRING_SYM_PS(toStringTag, "WebAssembly.Instance", JSPROP_READONLY),
    JS_PS_END,
};

const JSFunctionSpec WasmInstanceObject::methods[] = {
    JS_FS_END,
};

const JSFunctionSpec WasmInstanceObject::static_methods[] = {
    JS_FS_END,
};

bool WasmInstanceObject::isNewborn() const {
  MOZ_ASSERT(is<WasmInstanceObject>());
  return getReservedSlot(INSTANCE_SLOT).isUndefined();
}

// WeakScopeMap maps from function index to js::Scope. This maps is weak
// to avoid holding scope objects alive. The scopes are normally created
// during debugging.
//
// This is defined here in order to avoid recursive dependency between
// WasmJS.h and Scope.h.
using WasmFunctionScopeMap =
    JS::WeakCache<GCHashMap<uint32_t, WeakHeapPtr<WasmFunctionScope*>,
                            DefaultHasher<uint32_t>, CellAllocPolicy>>;
class WasmInstanceObject::UnspecifiedScopeMap {
 public:
  WasmFunctionScopeMap& asWasmFunctionScopeMap() {
    return *(WasmFunctionScopeMap*)this;
  }
};

/* static */
void WasmInstanceObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  WasmInstanceObject& instance = obj->as<WasmInstanceObject>();
  gcx->delete_(obj, &instance.scopes().asWasmFunctionScopeMap(),
               MemoryUse::WasmInstanceScopes);
  gcx->delete_(obj, &instance.indirectGlobals(),
               MemoryUse::WasmInstanceGlobals);
  if (!instance.isNewborn()) {
    if (instance.instance().debugEnabled()) {
      instance.instance().debug().finalize(gcx);
    }
    Instance::destroy(&instance.instance());
    gcx->removeCellMemory(obj, sizeof(Instance),
                          MemoryUse::WasmInstanceInstance);
  }
}

/* static */
void WasmInstanceObject::trace(JSTracer* trc, JSObject* obj) {
  WasmInstanceObject& instanceObj = obj->as<WasmInstanceObject>();
  instanceObj.indirectGlobals().trace(trc);
  if (!instanceObj.isNewborn()) {
    instanceObj.instance().tracePrivate(trc);
  }
}

/* static */
WasmInstanceObject* WasmInstanceObject::create(
    JSContext* cx, const SharedCode& code,
    const DataSegmentVector& dataSegments,
    const ModuleElemSegmentVector& elemSegments, uint32_t instanceDataLength,
    Handle<WasmMemoryObjectVector> memories, SharedTableVector&& tables,
    const JSObjectVector& funcImports, const GlobalDescVector& globals,
    const ValVector& globalImportValues,
    const WasmGlobalObjectVector& globalObjs,
    const WasmTagObjectVector& tagObjs, HandleObject proto,
    UniqueDebugState maybeDebug) {
  UniquePtr<WasmFunctionScopeMap> scopes =
      js::MakeUnique<WasmFunctionScopeMap>(cx->zone(), cx->zone());
  if (!scopes) {
    ReportOutOfMemory(cx);
    return nullptr;
  }
  // Note that `scopes` is a WeakCache, auto-linked into a sweep list on the
  // Zone, and so does not require rooting.

  uint32_t indirectGlobals = 0;

  for (uint32_t i = 0; i < globalObjs.length(); i++) {
    if (globalObjs[i] && globals[i].isIndirect()) {
      indirectGlobals++;
    }
  }

  Rooted<UniquePtr<GlobalObjectVector>> indirectGlobalObjs(
      cx, js::MakeUnique<GlobalObjectVector>(cx->zone()));
  if (!indirectGlobalObjs || !indirectGlobalObjs->resize(indirectGlobals)) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  {
    uint32_t next = 0;
    for (uint32_t i = 0; i < globalObjs.length(); i++) {
      if (globalObjs[i] && globals[i].isIndirect()) {
        (*indirectGlobalObjs)[next++] = globalObjs[i];
      }
    }
  }

  Instance* instance = nullptr;
  Rooted<WasmInstanceObject*> obj(cx);

  {
    // We must delay creating metadata for this object until after all its
    // slots have been initialized. We must also create the metadata before
    // calling Instance::init as that may allocate new objects.
    AutoSetNewObjectMetadata metadata(cx);
    obj = NewObjectWithGivenProto<WasmInstanceObject>(cx, proto);
    if (!obj) {
      return nullptr;
    }

    MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");

    InitReservedSlot(obj, SCOPES_SLOT, scopes.release(),
                     MemoryUse::WasmInstanceScopes);

    InitReservedSlot(obj, GLOBALS_SLOT, indirectGlobalObjs.release(),
                     MemoryUse::WasmInstanceGlobals);

    obj->initReservedSlot(INSTANCE_SCOPE_SLOT, UndefinedValue());

    // The INSTANCE_SLOT may not be initialized if Instance allocation fails,
    // leading to an observable "newborn" state in tracing/finalization.
    MOZ_ASSERT(obj->isNewborn());

    // Create this just before constructing Instance to avoid rooting hazards.
    instance = Instance::create(cx, obj, code, instanceDataLength,
                                std::move(tables), std::move(maybeDebug));
    if (!instance) {
      return nullptr;
    }

    InitReservedSlot(obj, INSTANCE_SLOT, instance,
                     MemoryUse::WasmInstanceInstance);
    MOZ_ASSERT(!obj->isNewborn());
  }

  if (!instance->init(cx, funcImports, globalImportValues, memories, globalObjs,
                      tagObjs, dataSegments, elemSegments)) {
    return nullptr;
  }

  return obj;
}

void WasmInstanceObject::initExportsObj(JSObject& exportsObj) {
  MOZ_ASSERT(getReservedSlot(EXPORTS_OBJ_SLOT).isUndefined());
  setReservedSlot(EXPORTS_OBJ_SLOT, ObjectValue(exportsObj));
}

static bool GetImportArg(JSContext* cx, HandleValue importArg,
                         MutableHandleObject importObj) {
  if (!importArg.isUndefined()) {
    if (!importArg.isObject()) {
      return ThrowBadImportArg(cx);
    }
    importObj.set(&importArg.toObject());
  }
  return true;
}

/* static */
bool WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  Log(cx, "sync new Instance() started");

  if (!ThrowIfNotConstructing(cx, args, "Instance")) {
    return false;
  }

  if (!args.requireAtLeast(cx, "WebAssembly.Instance", 1)) {
    return false;
  }

  const Module* module;
  if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), &module)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_MOD_ARG);
    return false;
  }

  RootedObject importObj(cx);
  if (!GetImportArg(cx, args.get(1), &importObj)) {
    return false;
  }

  RootedObject proto(
      cx, GetWasmConstructorPrototype(cx, args, JSProto_WasmInstance));
  if (!proto) {
    ReportOutOfMemory(cx);
    return false;
  }

  Rooted<ImportValues> imports(cx);
  if (!GetImports(cx, *module, importObj, imports.address())) {
    return false;
  }

  Rooted<WasmInstanceObject*> instanceObj(cx);
  if (!module->instantiate(cx, imports.get(), proto, &instanceObj)) {
    return false;
  }

  Log(cx, "sync new Instance() succeeded");

  args.rval().setObject(*instanceObj);
  return true;
}

Instance& WasmInstanceObject::instance() const {
  MOZ_ASSERT(!isNewborn());
  return *(Instance*)getReservedSlot(INSTANCE_SLOT).toPrivate();
}

JSObject& WasmInstanceObject::exportsObj() const {
  return getReservedSlot(EXPORTS_OBJ_SLOT).toObject();
}

WasmInstanceObject::UnspecifiedScopeMap& WasmInstanceObject::scopes() const {
  return *(UnspecifiedScopeMap*)(getReservedSlot(SCOPES_SLOT).toPrivate());
}

WasmInstanceObject::GlobalObjectVector& WasmInstanceObject::indirectGlobals()
    const {
  return *(GlobalObjectVector*)getReservedSlot(GLOBALS_SLOT).toPrivate();
}

/* static */
bool WasmInstanceObject::getExportedFunction(
    JSContext* cx, Handle<WasmInstanceObject*> instanceObj, uint32_t funcIndex,
    MutableHandleFunction fun) {
  Instance& instance = instanceObj->instance();
  return instance.getExportedFunction(cx, funcIndex, fun);
}

/* static */
WasmInstanceScope* WasmInstanceObject::getScope(
    JSContext* cx, Handle<WasmInstanceObject*> instanceObj) {
  if (!instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT).isUndefined()) {
    return (WasmInstanceScope*)instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT)
        .toGCThing();
  }

  Rooted<WasmInstanceScope*> instanceScope(
      cx, WasmInstanceScope::create(cx, instanceObj));
  if (!instanceScope) {
    return nullptr;
  }

  instanceObj->setReservedSlot(INSTANCE_SCOPE_SLOT,
                               PrivateGCThingValue(instanceScope));

  return instanceScope;
}

/* static */
WasmFunctionScope* WasmInstanceObject::getFunctionScope(
    JSContext* cx, Handle<WasmInstanceObject*> instanceObj,
    uint32_t funcIndex) {
  if (auto p =
          instanceObj->scopes().asWasmFunctionScopeMap().lookup(funcIndex)) {
    return p->value();
  }

  Rooted<WasmInstanceScope*> instanceScope(
      cx, WasmInstanceObject::getScope(cx, instanceObj));
  if (!instanceScope) {
    return nullptr;
  }

  Rooted<WasmFunctionScope*> funcScope(
      cx, WasmFunctionScope::create(cx, instanceScope, funcIndex));
  if (!funcScope) {
    return nullptr;
  }

  if (!instanceObj->scopes().asWasmFunctionScopeMap().putNew(funcIndex,
                                                             funcScope)) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return funcScope;
}

// ============================================================================
// WebAssembly.Memory class and methods

const JSClassOps WasmMemoryObject::classOps_ = {
    nullptr,                     // addProperty
    nullptr,                     // delProperty
    nullptr,                     // enumerate
    nullptr,                     // newEnumerate
    nullptr,                     // resolve
    nullptr,                     // mayResolve
    WasmMemoryObject::finalize,  // finalize
    nullptr,                     // call
    nullptr,                     // construct
    nullptr,                     // trace
};

const JSClass WasmMemoryObject::class_ = {
    "WebAssembly.Memory",
    JSCLASS_DELAY_METADATA_BUILDER |
        JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS) |
        JSCLASS_FOREGROUND_FINALIZE,
    &WasmMemoryObject::classOps_,
    &WasmMemoryObject::classSpec_,
};

const JSClass& WasmMemoryObject::protoClass_ = PlainObject::class_;

static constexpr char WasmMemoryName[] = "Memory";

static JSObject* CreateWasmMemoryPrototype(JSContext* cx, JSProtoKey key) {
  RootedObject proto(cx, GlobalObject::createBlankPrototype(
                             cx, cx->global(), &WasmMemoryObject::protoClass_));
  if (!proto) {
    return nullptr;
  }
  if (MemoryControlAvailable(cx)) {
    if (!JS_DefineFunctions(cx, proto,
                            WasmMemoryObject::memoryControlMethods)) {
      return nullptr;
    }
  }
  return proto;
}

const ClassSpec WasmMemoryObject::classSpec_ = {
    CreateWasmConstructor<WasmMemoryObject, WasmMemoryName>,
    CreateWasmMemoryPrototype,
    WasmMemoryObject::static_methods,
    nullptr,
    WasmMemoryObject::methods,
    WasmMemoryObject::properties,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

/* static */
void WasmMemoryObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  WasmMemoryObject& memory = obj->as<WasmMemoryObject>();
  if (memory.hasObservers()) {
    gcx->delete_(obj, &memory.observers(), MemoryUse::WasmMemoryObservers);
  }
}

/* static */
WasmMemoryObject* WasmMemoryObject::create(
    JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, bool isHuge,
    HandleObject proto) {
  AutoSetNewObjectMetadata metadata(cx);
  auto* obj = NewObjectWithGivenProto<WasmMemoryObject>(cx, proto);
  if (!obj) {
    return nullptr;
  }

  obj->initReservedSlot(BUFFER_SLOT, ObjectValue(*buffer));
  obj->initReservedSlot(ISHUGE_SLOT, BooleanValue(isHuge));
  MOZ_ASSERT(!obj->hasObservers());

  return obj;
}

/* static */
bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!ThrowIfNotConstructing(cx, args, "Memory")) {
    return false;
  }

  if (!args.requireAtLeast(cx, "WebAssembly.Memory", 1)) {
    return false;
  }

  if (!args.get(0).isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_DESC_ARG, "memory");
    return false;
  }

  RootedObject obj(cx, &args[0].toObject());
  Limits limits;
  if (!GetLimits(cx, obj, LimitsKind::Memory, &limits) ||
      !CheckLimits(cx, MaxMemoryPagesValidation(limits.addressType),
                   LimitsKind::Memory, &limits)) {
    return false;
  }

  if (Pages(limits.initial) > MaxMemoryPages(limits.addressType)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_MEM_IMP_LIMIT);
    return false;
  }
  MemoryDesc memory(limits);

  Rooted<ArrayBufferObjectMaybeShared*> buffer(cx,
                                               CreateWasmBuffer(cx, memory));
  if (!buffer) {
    return false;
  }

  RootedObject proto(cx,
                     GetWasmConstructorPrototype(cx, args, JSProto_WasmMemory));
  if (!proto) {
    ReportOutOfMemory(cx);
    return false;
  }

  Rooted<WasmMemoryObject*> memoryObj(
      cx, WasmMemoryObject::create(
              cx, buffer, IsHugeMemoryEnabled(limits.addressType), proto));
  if (!memoryObj) {
    return false;
  }

  args.rval().setObject(*memoryObj);
  return true;
}

static bool IsMemory(HandleValue v) {
  return v.isObject() && v.toObject().is<WasmMemoryObject>();
}

/* static */
bool WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmMemoryObject*> memoryObj(
      cx, &args.thisv().toObject().as<WasmMemoryObject>());
  Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &memoryObj->buffer());

  if (memoryObj->isShared()) {
    size_t memoryLength = memoryObj->volatileMemoryLength();
    MOZ_ASSERT(memoryLength >= buffer->byteLength());

    if (memoryLength > buffer->byteLength()) {
      Rooted<SharedArrayBufferObject*> newBuffer(
          cx, SharedArrayBufferObject::New(
                  cx, memoryObj->sharedArrayRawBuffer(), memoryLength));
      if (!newBuffer) {
        return false;
      }
      // OK to addReference after we try to allocate because the memoryObj
      // keeps the rawBuffer alive.
      if (!memoryObj->sharedArrayRawBuffer()->addReference()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_SC_SAB_REFCNT_OFLO);
        return false;
      }
      buffer = newBuffer;
      memoryObj->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuffer));
    }
  }

  args.rval().setObject(*buffer);
  return true;
}

/* static */
bool WasmMemoryObject::bufferGetter(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsMemory, bufferGetterImpl>(cx, args);
}

const JSPropertySpec WasmMemoryObject::properties[] = {
    JS_PSG("buffer", WasmMemoryObject::bufferGetter, JSPROP_ENUMERATE),
    JS_STRING_SYM_PS(toStringTag, "WebAssembly.Memory", JSPROP_READONLY),
    JS_PS_END,
};

/* static */
bool WasmMemoryObject::growImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmMemoryObject*> memory(
      cx, &args.thisv().toObject().as<WasmMemoryObject>());

  if (!args.requireAtLeast(cx, "WebAssembly.Memory.grow", 1)) {
    return false;
  }

  uint64_t delta;
  if (!EnforceAddressValue(cx, args.get(0), memory->addressType(), "Memory",
                           "grow delta", &delta)) {
    return false;
  }

  uint32_t ret = grow(memory, delta, cx);

  if (ret == uint32_t(-1)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW,
                             "memory");
    return false;
  }

  RootedValue result(cx);
  if (!CreateAddressValue(cx, ret, memory->addressType(), &result)) {
    ReportOutOfMemory(cx);
    return false;
  }
  args.rval().set(result);
  return true;
}

/* static */
bool WasmMemoryObject::grow(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsMemory, growImpl>(cx, args);
}

/* static */
bool WasmMemoryObject::discardImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmMemoryObject*> memory(
      cx, &args.thisv().toObject().as<WasmMemoryObject>());

  if (!args.requireAtLeast(cx, "WebAssembly.Memory.discard", 2)) {
    return false;
  }

  uint64_t byteOffset;
  if (!EnforceRangeU64(cx, args.get(0), "Memory""byte offset", &byteOffset)) {
    return false;
  }

  uint64_t byteLen;
  if (!EnforceRangeU64(cx, args.get(1), "Memory""length", &byteLen)) {
    return false;
  }

  if (byteOffset % wasm::PageSize != 0 || byteLen % wasm::PageSize != 0) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_UNALIGNED_ACCESS);
    return false;
  }

  if (!wasm::MemoryBoundsCheck(byteOffset, byteLen,
                               memory->volatileMemoryLength())) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_OUT_OF_BOUNDS);
    return false;
  }

  discard(memory, byteOffset, byteLen, cx);

  args.rval().setUndefined();
  return true;
}

/* static */
bool WasmMemoryObject::discard(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsMemory, discardImpl>(cx, args);
}

const JSFunctionSpec WasmMemoryObject::methods[] = {
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
    JS_FN("type", WasmMemoryObject::type, 0, JSPROP_ENUMERATE),
#endif
    JS_FN("grow", WasmMemoryObject::grow, 1, JSPROP_ENUMERATE),
    JS_FS_END,
};

const JSFunctionSpec WasmMemoryObject::memoryControlMethods[] = {
    JS_FN("discard", WasmMemoryObject::discard, 2, JSPROP_ENUMERATE),
    JS_FS_END,
};

const JSFunctionSpec WasmMemoryObject::static_methods[] = {
    JS_FS_END,
};

ArrayBufferObjectMaybeShared& WasmMemoryObject::buffer() const {
  return getReservedSlot(BUFFER_SLOT)
      .toObject()
      .as<ArrayBufferObjectMaybeShared>();
}

WasmSharedArrayRawBuffer* WasmMemoryObject::sharedArrayRawBuffer() const {
  MOZ_ASSERT(isShared());
  return buffer().as<SharedArrayBufferObject>().rawWasmBufferObject();
}

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
bool WasmMemoryObject::typeImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmMemoryObject*> memoryObj(
      cx, &args.thisv().toObject().as<WasmMemoryObject>());
  RootedObject typeObj(cx, MemoryTypeToObject(cx, memoryObj->isShared(),
                                              memoryObj->addressType(),
                                              memoryObj->volatilePages(),
                                              memoryObj->sourceMaxPages()));
  if (!typeObj) {
    return false;
  }
  args.rval().setObject(*typeObj);
  return true;
}

bool WasmMemoryObject::type(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsMemory, typeImpl>(cx, args);
}
#endif

size_t WasmMemoryObject::volatileMemoryLength() const {
  if (isShared()) {
    return sharedArrayRawBuffer()->volatileByteLength();
  }
  return buffer().byteLength();
}

wasm::Pages WasmMemoryObject::volatilePages() const {
  if (isShared()) {
    return sharedArrayRawBuffer()->volatileWasmPages();
  }
  return buffer().wasmPages();
}

wasm::Pages WasmMemoryObject::clampedMaxPages() const {
  if (isShared()) {
    return sharedArrayRawBuffer()->wasmClampedMaxPages();
  }
  return buffer().wasmClampedMaxPages();
}

Maybe<wasm::Pages> WasmMemoryObject::sourceMaxPages() const {
  if (isShared()) {
    return Some(sharedArrayRawBuffer()->wasmSourceMaxPages());
  }
  return buffer().wasmSourceMaxPages();
}

wasm::AddressType WasmMemoryObject::addressType() const {
  if (isShared()) {
    return sharedArrayRawBuffer()->wasmAddressType();
  }
  return buffer().wasmAddressType();
}

bool WasmMemoryObject::isShared() const {
  return buffer().is<SharedArrayBufferObject>();
}

bool WasmMemoryObject::hasObservers() const {
  return !getReservedSlot(OBSERVERS_SLOT).isUndefined();
}

WasmMemoryObject::InstanceSet& WasmMemoryObject::observers() const {
  MOZ_ASSERT(hasObservers());
  return *reinterpret_cast<InstanceSet*>(
      getReservedSlot(OBSERVERS_SLOT).toPrivate());
}

WasmMemoryObject::InstanceSet* WasmMemoryObject::getOrCreateObservers(
    JSContext* cx) {
  if (!hasObservers()) {
    auto observers = MakeUnique<InstanceSet>(cx->zone(), cx->zone());
    if (!observers) {
      ReportOutOfMemory(cx);
      return nullptr;
    }

    InitReservedSlot(this, OBSERVERS_SLOT, observers.release(),
                     MemoryUse::WasmMemoryObservers);
  }

  return &observers();
}

bool WasmMemoryObject::isHuge() const {
  return getReservedSlot(ISHUGE_SLOT).toBoolean();
}

bool WasmMemoryObject::movingGrowable() const {
  return !isHuge() && !buffer().wasmSourceMaxPages();
}

size_t WasmMemoryObject::boundsCheckLimit() const {
  if (!buffer().isWasm() || isHuge()) {
    return buffer().byteLength();
  }
  size_t mappedSize = buffer().wasmMappedSize();
#if !defined(JS_64BIT)
  // See clamping performed in CreateSpecificWasmBuffer().  On 32-bit systems
  // we do not want to overflow a uint32_t.  For the other 64-bit compilers,
  // all constraints are implied by the largest accepted value for a memory's
  // max field.
  MOZ_ASSERT(mappedSize < UINT32_MAX);
#endif
  MOZ_ASSERT(mappedSize % wasm::PageSize == 0);
  MOZ_ASSERT(mappedSize >= wasm::GuardSize);
  MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize));
  size_t limit = mappedSize - wasm::GuardSize;
  MOZ_ASSERT(limit <= MaxMemoryBoundsCheckLimit(addressType()));
  return limit;
}

bool WasmMemoryObject::addMovingGrowObserver(JSContext* cx,
                                             WasmInstanceObject* instance) {
  MOZ_ASSERT(movingGrowable());

  InstanceSet* observers = getOrCreateObservers(cx);
  if (!observers) {
    return false;
  }

  // A memory can be imported multiple times into an instance, but we only
  // register the instance as an observer once.
  if (!observers->put(instance)) {
    ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

/* static */
uint64_t WasmMemoryObject::growShared(Handle<WasmMemoryObject*> memory,
                                      uint64_t delta) {
  WasmSharedArrayRawBuffer* rawBuf = memory->sharedArrayRawBuffer();
  WasmSharedArrayRawBuffer::Lock lock(rawBuf);

  Pages oldNumPages = rawBuf->volatileWasmPages();
  Pages newPages = oldNumPages;
  if (!newPages.checkedIncrement(Pages(delta))) {
    return uint64_t(int64_t(-1));
  }

  if (!rawBuf->wasmGrowToPagesInPlace(lock, memory->addressType(), newPages)) {
    return uint64_t(int64_t(-1));
  }
  // New buffer objects will be created lazily in all agents (including in
  // this agent) by bufferGetterImpl, above, so no more work to do here.

  return oldNumPages.value();
}

/* static */
uint64_t WasmMemoryObject::grow(Handle<WasmMemoryObject*> memory,
                                uint64_t delta, JSContext* cx) {
  if (memory->isShared()) {
    return growShared(memory, delta);
  }

  Rooted<ArrayBufferObject*> oldBuf(cx,
                                    &memory->buffer().as<ArrayBufferObject>());

#if !defined(JS_64BIT)
  // TODO (large ArrayBuffer): See more information at the definition of
  // MaxMemoryBytes().
  MOZ_ASSERT(MaxMemoryBytes(memory->addressType()) <= UINT32_MAX,
             "Avoid 32-bit overflows");
#endif

  Pages oldNumPages = oldBuf->wasmPages();
  Pages newPages = oldNumPages;
  if (!newPages.checkedIncrement(Pages(delta))) {
    return uint64_t(int64_t(-1));
  }

  ArrayBufferObject* newBuf;
  if (memory->movingGrowable()) {
    MOZ_ASSERT(!memory->isHuge());
    newBuf = ArrayBufferObject::wasmMovingGrowToPages(memory->addressType(),
                                                      newPages, oldBuf, cx);
  } else {
    newBuf = ArrayBufferObject::wasmGrowToPagesInPlace(memory->addressType(),
                                                       newPages, oldBuf, cx);
  }
  if (!newBuf) {
    return uint64_t(int64_t(-1));
  }

  memory->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuf));

  // Only notify moving-grow-observers after the BUFFER_SLOT has been updated
  // since observers will call buffer().
  if (memory->hasObservers()) {
    for (InstanceSet::Range r = memory->observers().all(); !r.empty();
         r.popFront()) {
      r.front()->instance().onMovingGrowMemory(memory);
    }
  }

  return oldNumPages.value();
}

/* static */
void WasmMemoryObject::discard(Handle<WasmMemoryObject*> memory,
                               uint64_t byteOffset, uint64_t byteLen,
                               JSContext* cx) {
  if (memory->isShared()) {
    Rooted<SharedArrayBufferObject*> buf(
        cx, &memory->buffer().as<SharedArrayBufferObject>());
    SharedArrayBufferObject::wasmDiscard(buf, byteOffset, byteLen);
  } else {
    Rooted<ArrayBufferObject*> buf(cx,
                                   &memory->buffer().as<ArrayBufferObject>());
    ArrayBufferObject::wasmDiscard(buf, byteOffset, byteLen);
  }
}

bool js::wasm::IsSharedWasmMemoryObject(JSObject* obj) {
  WasmMemoryObject* mobj = obj->maybeUnwrapIf<WasmMemoryObject>();
  return mobj && mobj->isShared();
}

// ============================================================================
// WebAssembly.Table class and methods

const JSClassOps WasmTableObject::classOps_ = {
    nullptr,                    // addProperty
    nullptr,                    // delProperty
    nullptr,                    // enumerate
    nullptr,                    // newEnumerate
    nullptr,                    // resolve
    nullptr,                    // mayResolve
    WasmTableObject::finalize,  // finalize
    nullptr,                    // call
    nullptr,                    // construct
    WasmTableObject::trace,     // trace
};

const JSClass WasmTableObject::class_ = {
    "WebAssembly.Table",
    JSCLASS_DELAY_METADATA_BUILDER |
        JSCLASS_HAS_RESERVED_SLOTS(WasmTableObject::RESERVED_SLOTS) |
        JSCLASS_FOREGROUND_FINALIZE,
    &WasmTableObject::classOps_,
    &WasmTableObject::classSpec_,
};

const JSClass& WasmTableObject::protoClass_ = PlainObject::class_;

static constexpr char WasmTableName[] = "Table";

const ClassSpec WasmTableObject::classSpec_ = {
    CreateWasmConstructor<WasmTableObject, WasmTableName>,
    GenericCreatePrototype<WasmTableObject>,
    WasmTableObject::static_methods,
    nullptr,
    WasmTableObject::methods,
    WasmTableObject::properties,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

bool WasmTableObject::isNewborn() const {
  MOZ_ASSERT(is<WasmTableObject>());
  return getReservedSlot(TABLE_SLOT).isUndefined();
}

/* static */
void WasmTableObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  WasmTableObject& tableObj = obj->as<WasmTableObject>();
  if (!tableObj.isNewborn()) {
    auto& table = tableObj.table();
    gcx->release(obj, &table, table.gcMallocBytes(), MemoryUse::WasmTableTable);
  }
}

/* static */
void WasmTableObject::trace(JSTracer* trc, JSObject* obj) {
  WasmTableObject& tableObj = obj->as<WasmTableObject>();
  if (!tableObj.isNewborn()) {
    tableObj.table().tracePrivate(trc);
  }
}

// Return the JS value to use when a parameter to a function requiring a table
// value is omitted. An implementation of [1].
//
// [1]
// https://webassembly.github.io/spec/js-api/#defaultvalue
static Value RefTypeDefaultValue(wasm::RefType tableType) {
  return tableType.isExtern() ? UndefinedValue() : NullValue();
}

/* static */
WasmTableObject* WasmTableObject::create(JSContext* cx, Limits limits,
                                         wasm::RefType tableType,
                                         HandleObject proto) {
  AutoSetNewObjectMetadata metadata(cx);
  Rooted<WasmTableObject*> obj(
      cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto));
  if (!obj) {
    return nullptr;
  }

  MOZ_ASSERT(obj->isNewborn());

  TableDesc td(limits, tableType, Nothing(),
               /*isAsmJS*/ false,
               /*isImported=*/true, /*isExported=*/true);

  SharedTable table = Table::create(cx, td, obj);
  if (!table) {
    return nullptr;
  }

  size_t size = table->gcMallocBytes();
  InitReservedSlot(obj, TABLE_SLOT, table.forget().take(), size,
                   MemoryUse::WasmTableTable);

  MOZ_ASSERT(!obj->isNewborn());
  return obj;
}

/* static */
bool WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!ThrowIfNotConstructing(cx, args, "Table")) {
    return false;
  }

  if (!args.requireAtLeast(cx, "WebAssembly.Table", 1)) {
    return false;
  }

  if (!args.get(0).isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_DESC_ARG, "table");
    return false;
  }

  RootedObject obj(cx, &args[0].toObject());

  JSAtom* elementAtom = Atomize(cx, "element", strlen("element"));
  if (!elementAtom) {
    return false;
  }
  RootedId elementId(cx, AtomToId(elementAtom));

  RootedValue elementVal(cx);
  if (!GetProperty(cx, obj, obj, elementId, &elementVal)) {
    return false;
  }

  RefType tableType;
  if (!ToRefType(cx, elementVal, &tableType)) {
    return false;
  }

  Limits limits;
  if (!GetLimits(cx, obj, LimitsKind::Table, &limits) ||
      !CheckLimits(cx, MaxTableElemsValidation(limits.addressType),
                   LimitsKind::Table, &limits)) {
    return false;
  }

  if (limits.initial > MaxTableElemsRuntime) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_TABLE_IMP_LIMIT);
    return false;
  }

  RootedObject proto(cx,
                     GetWasmConstructorPrototype(cx, args, JSProto_WasmTable));
  if (!proto) {
    ReportOutOfMemory(cx);
    return false;
  }

  Rooted<WasmTableObject*> table(
      cx, WasmTableObject::create(cx, limits, tableType, proto));
  if (!table) {
    return false;
  }

  // Initialize the table to a default value
  RootedValue initValue(
      cx, args.length() < 2 ? RefTypeDefaultValue(tableType) : args[1]);
  if (!CheckRefType(cx, tableType, initValue)) {
    return false;
  }

  // Skip initializing the table if the fill value is null, as that is the
  // default value.
  if (!initValue.isNull() &&
      !table->fillRange(cx, 0, limits.initial, initValue)) {
    return false;
  }
#ifdef DEBUG
  // Assert that null is the default value of a new table.
  if (initValue.isNull()) {
    table->table().assertRangeNull(0, limits.initial);
  }
  if (!tableType.isNullable()) {
    table->table().assertRangeNotNull(0, limits.initial);
  }
#endif

  args.rval().setObject(*table);
  return true;
}

static bool IsTable(HandleValue v) {
  return v.isObject() && v.toObject().is<WasmTableObject>();
}

/* static */
bool WasmTableObject::lengthGetterImpl(JSContext* cx, const CallArgs& args) {
  const WasmTableObject& tableObj =
      args.thisv().toObject().as<WasmTableObject>();
  RootedValue length(cx);
  if (!CreateAddressValue(cx, tableObj.table().length(),
                          tableObj.table().addressType(), &length)) {
    ReportOutOfMemory(cx);
    return false;
  }
  args.rval().set(length);
  return true;
}

/* static */
bool WasmTableObject::lengthGetter(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsTable, lengthGetterImpl>(cx, args);
}

const JSPropertySpec WasmTableObject::properties[] = {
    JS_PSG("length", WasmTableObject::lengthGetter, JSPROP_ENUMERATE),
    JS_STRING_SYM_PS(toStringTag, "WebAssembly.Table", JSPROP_READONLY),
    JS_PS_END,
};

// Gets an AddressValue parameter for a table. This differs from our general
// EnforceAddressValue because our table implementation still uses 32-bit sizes
// internally, and this function therefore returns a uint32_t. Values outside
// the 32-bit range will be clamped to UINT32_MAX, which will always trigger
// bounds checks for all Table uses of AddressValue. See
// MacroAssembler::wasmClampTable64Address and its uses.
//
// isAddress should be true if the value is an actual address, and false if it
// is a different quantity (e.g. a grow delta).
static bool EnforceTableAddressValue(JSContext* cx, HandleValue v,
                                     const Table& table, const char* noun,
                                     uint32_t* result, bool isAddress) {
  uint64_t result64;
  if (!EnforceAddressValue(cx, v, table.addressType(), "Table", noun,
                           &result64)) {
    return false;
  }

  static_assert(MaxTableElemsRuntime < UINT32_MAX);
  *result = result64 > UINT32_MAX ? UINT32_MAX : uint32_t(result64);

  if (isAddress && *result >= table.length()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_WASM_BAD_RANGE, "Table", noun);
    return false;
  }

  return true;
}

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
/* static */
bool WasmTableObject::typeImpl(JSContext* cx, const CallArgs& args) {
  Table& table = args.thisv().toObject().as<WasmTableObject>().table();
  RootedObject typeObj(
      cx, TableTypeToObject(cx, table.addressType(), table.elemType(),
                            table.length(), table.maximum()));
  if (!typeObj) {
    return false;
  }
  args.rval().setObject(*typeObj);
  return true;
}

/* static */
bool WasmTableObject::type(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsTable, typeImpl>(cx, args);
}
#endif

/* static */
bool WasmTableObject::getImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmTableObject*> tableObj(
      cx, &args.thisv().toObject().as<WasmTableObject>());
  const Table& table = tableObj->table();

  if (!args.requireAtLeast(cx, "WebAssembly.Table.get", 1)) {
    return false;
  }

  uint32_t address;
  if (!EnforceTableAddressValue(cx, args.get(0), table, "get address", &address,
                                /*isAddress=*/true)) {
    return false;
  }

  return table.getValue(cx, address, args.rval());
}

/* static */
bool WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsTable, getImpl>(cx, args);
}

/* static */
bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmTableObject*> tableObj(
      cx, &args.thisv().toObject().as<WasmTableObject>());
  Table& table = tableObj->table();

  if (!args.requireAtLeast(cx, "WebAssembly.Table.set", 1)) {
    return false;
  }

  uint32_t address;
  if (!EnforceTableAddressValue(cx, args.get(0), table, "set address", &address,
                                /*isAddress=*/true)) {
    return false;
  }

  RootedValue fillValue(
      cx, args.length() < 2 ? RefTypeDefaultValue(table.elemType()) : args[1]);
  if (!tableObj->fillRange(cx, address, 1, fillValue)) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}

/* static */
bool WasmTableObject::set(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsTable, setImpl>(cx, args);
}

/* static */
bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmTableObject*> tableObj(
      cx, &args.thisv().toObject().as<WasmTableObject>());
  Table& table = tableObj->table();

  if (!args.requireAtLeast(cx, "WebAssembly.Table.grow", 1)) {
    return false;
  }

  uint32_t delta;
  if (!EnforceTableAddressValue(cx, args.get(0), table, "grow delta", &delta,
                                /*isAddress=*/false)) {
    return false;
  }

  RootedValue fillValue(
      cx, args.length() < 2 ? RefTypeDefaultValue(table.elemType()) : args[1]);
  if (!CheckRefType(cx, table.elemType(), fillValue)) {
    return false;
  }

  uint32_t oldLength = table.grow(delta);

  if (oldLength == uint32_t(-1)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW,
                             "table");
    return false;
  }

  // Skip filling the grown range of the table if the fill value is null, as
  // that is the default value.
  if (!fillValue.isNull() &&
      !tableObj->fillRange(cx, oldLength, delta, fillValue)) {
    return false;
  }
#ifdef DEBUG
  // Assert that null is the default value of the grown range.
  if (fillValue.isNull()) {
    table.assertRangeNull(oldLength, delta);
  }
  if (!table.elemType().isNullable()) {
    table.assertRangeNotNull(oldLength, delta);
  }
#endif

  RootedValue result(cx);
  if (!CreateAddressValue(cx, oldLength, table.addressType(), &result)) {
    ReportOutOfMemory(cx);
    return false;
  }
  args.rval().set(result);
  return true;
}

/* static */
bool WasmTableObject::grow(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsTable, growImpl>(cx, args);
}

const JSFunctionSpec WasmTableObject::methods[] = {
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
    JS_FN("type", WasmTableObject::type, 0, JSPROP_ENUMERATE),
#endif
    JS_FN("get", WasmTableObject::get, 1, JSPROP_ENUMERATE),
    JS_FN("set", WasmTableObject::set, 2, JSPROP_ENUMERATE),
    JS_FN("grow", WasmTableObject::grow, 1, JSPROP_ENUMERATE),
    JS_FS_END,
};

const JSFunctionSpec WasmTableObject::static_methods[] = {
    JS_FS_END,
};

Table& WasmTableObject::table() const {
  return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
}

bool WasmTableObject::fillRange(JSContext* cx, uint32_t index, uint32_t length,
                                HandleValue value) const {
  Table& tab = table();

  // All consumers are required to either bounds check or statically be in
  // bounds
  MOZ_ASSERT(uint64_t(index) + uint64_t(length) <= tab.length());

  RootedAnyRef any(cx, AnyRef::null());
  if (!wasm::CheckRefType(cx, tab.elemType(), value, &any)) {
    return false;
  }
  switch (tab.repr()) {
    case TableRepr::Func:
      MOZ_RELEASE_ASSERT(!tab.isAsmJS());
      tab.fillFuncRef(index, length, FuncRef::fromAnyRefUnchecked(any.get()),
                      cx);
      break;
    case TableRepr::Ref:
      tab.fillAnyRef(index, length, any);
      break;
  }
  return true;
}

// ============================================================================
// WebAssembly.global class and methods

const JSClassOps WasmGlobalObject::classOps_ = {
    nullptr,                     // addProperty
    nullptr,                     // delProperty
    nullptr,                     // enumerate
    nullptr,                     // newEnumerate
    nullptr,                     // resolve
    nullptr,                     // mayResolve
    WasmGlobalObject::finalize,  // finalize
    nullptr,                     // call
    nullptr,                     // construct
    WasmGlobalObject::trace,     // trace
};

const JSClass WasmGlobalObject::class_ = {
    "WebAssembly.Global",
    JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS) |
        JSCLASS_BACKGROUND_FINALIZE,
    &WasmGlobalObject::classOps_,
    &WasmGlobalObject::classSpec_,
};

const JSClass& WasmGlobalObject::protoClass_ = PlainObject::class_;

static constexpr char WasmGlobalName[] = "Global";

const ClassSpec WasmGlobalObject::classSpec_ = {
    CreateWasmConstructor<WasmGlobalObject, WasmGlobalName>,
    GenericCreatePrototype<WasmGlobalObject>,
    WasmGlobalObject::static_methods,
    nullptr,
    WasmGlobalObject::methods,
    WasmGlobalObject::properties,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

/* static */
void WasmGlobalObject::trace(JSTracer* trc, JSObject* obj) {
  WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
  if (global->isNewborn()) {
    // This can happen while we're allocating the object, in which case
    // every single slot of the object is not defined yet. In particular,
    // there's nothing to trace yet.
    return;
  }
  global->val().get().trace(trc);
}

/* static */
void WasmGlobalObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
  if (!global->isNewborn()) {
    // Release the strong reference to the type definitions this global could
    // be referencing.
    global->type().Release();
    gcx->delete_(obj, &global->mutableVal(), MemoryUse::WasmGlobalCell);
  }
}

/* static */
WasmGlobalObject* WasmGlobalObject::create(JSContext* cx, HandleVal value,
                                           bool isMutable, HandleObject proto) {
  Rooted<WasmGlobalObject*> obj(
      cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
  if (!obj) {
    return nullptr;
  }

  MOZ_ASSERT(obj->isNewborn());
  MOZ_ASSERT(obj->isTenured(), "assumed by global.set post barriers");

  GCPtrVal* val = js_new<GCPtrVal>(Val());
  if (!val) {
    ReportOutOfMemory(cx);
    return nullptr;
  }
  obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable));
  InitReservedSlot(obj, VAL_SLOT, val, MemoryUse::WasmGlobalCell);

  // It's simpler to initialize the cell after the object has been created,
  // to avoid needing to root the cell before the object creation.
  // We don't use `setVal` here because the assumes the cell has already
  // been initialized.
  obj->mutableVal() = value.get();
  // Acquire a strong reference to a type definition this global could
  // be referencing.
  obj->type().AddRef();

  MOZ_ASSERT(!obj->isNewborn());

  return obj;
}

/* static */
bool WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!ThrowIfNotConstructing(cx, args, "Global")) {
    return false;
  }

  if (!args.requireAtLeast(cx, "WebAssembly.Global", 1)) {
    return false;
  }

  if (!args.get(0).isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_DESC_ARG, "global");
    return false;
  }

  RootedObject obj(cx, &args[0].toObject());

  // Extract properties in lexicographic order per spec.

  RootedValue mutableVal(cx);
  if (!JS_GetProperty(cx, obj, "mutable", &mutableVal)) {
    return false;
  }

  RootedValue typeVal(cx);
  if (!JS_GetProperty(cx, obj, "value", &typeVal)) {
    return false;
  }

  ValType globalType;
  if (!ToValType(cx, typeVal, &globalType)) {
    return false;
  }

  if (!globalType.isExposable()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_VAL_TYPE);
    return false;
  }

  bool isMutable = ToBoolean(mutableVal);

  // Extract the initial value, or provide a suitable default.
  RootedVal globalVal(cx, globalType);

  // Override with non-undefined value, if provided.
  RootedValue valueVal(cx);
  if (globalType.isRefType()) {
    valueVal.set(args.length() < 2 ? RefTypeDefaultValue(globalType.refType())
                                   : args[1]);
    if (!Val::fromJSValue(cx, globalType, valueVal, &globalVal)) {
      return false;
    }
  } else {
    valueVal.set(args.get(1));
    if (!valueVal.isUndefined() &&
        !Val::fromJSValue(cx, globalType, valueVal, &globalVal)) {
      return false;
    }
  }

  RootedObject proto(cx,
                     GetWasmConstructorPrototype(cx, args, JSProto_WasmGlobal));
  if (!proto) {
    ReportOutOfMemory(cx);
    return false;
  }

  WasmGlobalObject* global =
      WasmGlobalObject::create(cx, globalVal, isMutable, proto);
  if (!global) {
    return false;
  }

  args.rval().setObject(*global);
  return true;
}

static bool IsGlobal(HandleValue v) {
  return v.isObject() && v.toObject().is<WasmGlobalObject>();
}

/* static */
bool WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args) {
  const WasmGlobalObject& globalObj =
      args.thisv().toObject().as<WasmGlobalObject>();
  if (!globalObj.type().isExposable()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_VAL_TYPE);
    return false;
  }
  return globalObj.val().get().toJSValue(cx, args.rval());
}

/* static */
bool WasmGlobalObject::valueGetter(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsGlobal, valueGetterImpl>(cx, args);
}

/* static */
bool WasmGlobalObject::valueSetterImpl(JSContext* cx, const CallArgs& args) {
  if (!args.requireAtLeast(cx, "WebAssembly.Global setter", 1)) {
    return false;
  }

  Rooted<WasmGlobalObject*> global(
      cx, &args.thisv().toObject().as<WasmGlobalObject>());
  if (!global->isMutable()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_GLOBAL_IMMUTABLE);
    return false;
  }

  RootedVal val(cx);
  if (!Val::fromJSValue(cx, global->type(), args.get(0), &val)) {
    return false;
  }
  global->setVal(val);

  args.rval().setUndefined();
  return true;
}

/* static */
bool WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsGlobal, valueSetterImpl>(cx, args);
}

const JSPropertySpec WasmGlobalObject::properties[] = {
    JS_PSGS("value", WasmGlobalObject::valueGetter,
            WasmGlobalObject::valueSetter, JSPROP_ENUMERATE),
    JS_STRING_SYM_PS(toStringTag, "WebAssembly.Global", JSPROP_READONLY),
    JS_PS_END,
};

const JSFunctionSpec WasmGlobalObject::methods[] = {
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
    JS_FN("type", WasmGlobalObject::type, 0, JSPROP_ENUMERATE),
#endif
    JS_FN("valueOf", WasmGlobalObject::valueGetter, 0, JSPROP_ENUMERATE),
    JS_FS_END,
};

const JSFunctionSpec WasmGlobalObject::static_methods[] = {
    JS_FS_END,
};

bool WasmGlobalObject::isMutable() const {
  return getReservedSlot(MUTABLE_SLOT).toBoolean();
}

ValType WasmGlobalObject::type() const { return val().get().type(); }

GCPtrVal& WasmGlobalObject::mutableVal() {
  return *reinterpret_cast<GCPtrVal*>(getReservedSlot(VAL_SLOT).toPrivate());
}

const GCPtrVal& WasmGlobalObject::val() const {
  return *reinterpret_cast<GCPtrVal*>(getReservedSlot(VAL_SLOT).toPrivate());
}

void WasmGlobalObject::setVal(wasm::HandleVal value) {
  MOZ_ASSERT(type() == value.get().type());
  mutableVal() = value;
}

void* WasmGlobalObject::addressOfCell() const {
  return (void*)&val().get().cell();
}

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
/* static */
bool WasmGlobalObject::typeImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmGlobalObject*> global(
      cx, &args.thisv().toObject().as<WasmGlobalObject>());
  RootedObject typeObj(
      cx, GlobalTypeToObject(cx, global->type(), global->isMutable()));
  if (!typeObj) {
    return false;
  }
  args.rval().setObject(*typeObj);
  return true;
}

/* static */
bool WasmGlobalObject::type(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsGlobal, typeImpl>(cx, args);
}
#endif

// ============================================================================
// WebAssembly.Tag class and methods

const JSClassOps WasmTagObject::classOps_ = {
    nullptr,                  // addProperty
    nullptr,                  // delProperty
    nullptr,                  // enumerate
    nullptr,                  // newEnumerate
    nullptr,                  // resolve
    nullptr,                  // mayResolve
    WasmTagObject::finalize,  // finalize
    nullptr,                  // call
    nullptr,                  // construct
    nullptr,                  // trace
};

const JSClass WasmTagObject::class_ = {
    "WebAssembly.Tag",
    JSCLASS_HAS_RESERVED_SLOTS(WasmTagObject::RESERVED_SLOTS) |
        JSCLASS_FOREGROUND_FINALIZE,
    &WasmTagObject::classOps_,
    &WasmTagObject::classSpec_,
};

const JSClass& WasmTagObject::protoClass_ = PlainObject::class_;

static constexpr char WasmTagName[] = "Tag";

const ClassSpec WasmTagObject::classSpec_ = {
    CreateWasmConstructor<WasmTagObject, WasmTagName>,
    GenericCreatePrototype<WasmTagObject>,
    WasmTagObject::static_methods,
    nullptr,
    WasmTagObject::methods,
    WasmTagObject::properties,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

/* static */
void WasmTagObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  WasmTagObject& tagObj = obj->as<WasmTagObject>();
  tagObj.tagType()->Release();
}

static bool IsTag(HandleValue v) {
  return v.isObject() && v.toObject().is<WasmTagObject>();
}

bool WasmTagObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!ThrowIfNotConstructing(cx, args, "Tag")) {
    return false;
  }

  if (!args.requireAtLeast(cx, "WebAssembly.Tag", 1)) {
    return false;
  }

  if (!args.get(0).isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_DESC_ARG, "tag");
    return false;
  }

  RootedObject obj(cx, &args[0].toObject());
  RootedValue paramsVal(cx);
  if (!JS_GetProperty(cx, obj, "parameters", ¶msVal)) {
    return false;
  }

  ValTypeVector params;
  if (!ParseValTypes(cx, paramsVal, params)) {
    return false;
  }

  RefPtr<TypeContext> types = js_new<TypeContext>();
  if (!types) {
    ReportOutOfMemory(cx);
    return false;
  }
  const TypeDef* tagTypeDef =
      types->addType(FuncType(std::move(params), ValTypeVector()));
  if (!tagTypeDef) {
    ReportOutOfMemory(cx);
    return false;
  }

  wasm::MutableTagType tagType = js_new<wasm::TagType>();
  if (!tagType || !tagType->initialize(tagTypeDef)) {
    ReportOutOfMemory(cx);
    return false;
  }

  RootedObject proto(cx,
                     GetWasmConstructorPrototype(cx, args, JSProto_WasmTag));
  if (!proto) {
    ReportOutOfMemory(cx);
    return false;
  }

  Rooted<WasmTagObject*> tagObj(cx, WasmTagObject::create(cx, tagType, proto));
  if (!tagObj) {
    return false;
  }

  args.rval().setObject(*tagObj);
  return true;
}

/* static */
WasmTagObject* WasmTagObject::create(JSContext* cx,
                                     const wasm::SharedTagType& tagType,
                                     HandleObject proto) {
  Rooted<WasmTagObject*> obj(cx,
                             NewObjectWithGivenProto<WasmTagObject>(cx, proto));
  if (!obj) {
    return nullptr;
  }

  tagType.get()->AddRef();
  obj->initReservedSlot(TYPE_SLOT, PrivateValue((void*)tagType.get()));

  return obj;
}

const JSPropertySpec WasmTagObject::properties[] = {
    JS_STRING_SYM_PS(toStringTag, "WebAssembly.Tag", JSPROP_READONLY),
    JS_PS_END,
};

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
/* static */
bool WasmTagObject::typeImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmTagObject*> tag(cx, &args.thisv().toObject().as<WasmTagObject>());
  RootedObject typeObj(cx, TagTypeToObject(cx, tag->valueTypes()));
  if (!typeObj) {
    return false;
  }
  args.rval().setObject(*typeObj);
  return true;
}

/* static  */
bool WasmTagObject::type(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsTag, typeImpl>(cx, args);
}
#endif

const JSFunctionSpec WasmTagObject::methods[] = {
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
    JS_FN("type", WasmTagObject::type, 0, JSPROP_ENUMERATE),
#endif
    JS_FS_END,
};

const JSFunctionSpec WasmTagObject::static_methods[] = {
    JS_FS_END,
};

const TagType* WasmTagObject::tagType() const {
  return (const TagType*)getFixedSlot(TYPE_SLOT).toPrivate();
};

const wasm::ValTypeVector& WasmTagObject::valueTypes() const {
  return tagType()->argTypes();
};

// ============================================================================
// WebAssembly.Exception class and methods

const JSClassOps WasmExceptionObject::classOps_ = {
    nullptr,                        // addProperty
    nullptr,                        // delProperty
    nullptr,                        // enumerate
    nullptr,                        // newEnumerate
    nullptr,                        // resolve
    nullptr,                        // mayResolve
    WasmExceptionObject::finalize,  // finalize
    nullptr,                        // call
    nullptr,                        // construct
    WasmExceptionObject::trace,     // trace
};

const JSClass WasmExceptionObject::class_ = {
    "WebAssembly.Exception",
    JSCLASS_HAS_RESERVED_SLOTS(WasmExceptionObject::RESERVED_SLOTS) |
        JSCLASS_FOREGROUND_FINALIZE,
    &WasmExceptionObject::classOps_,
    &WasmExceptionObject::classSpec_,
};

const JSClass& WasmExceptionObject::protoClass_ = PlainObject::class_;

static constexpr char WasmExceptionName[] = "Exception";

const ClassSpec WasmExceptionObject::classSpec_ = {
    CreateWasmConstructor<WasmExceptionObject, WasmExceptionName>,
    GenericCreatePrototype<WasmExceptionObject>,
    WasmExceptionObject::static_methods,
    nullptr,
    WasmExceptionObject::methods,
    WasmExceptionObject::properties,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

/* static */
void WasmExceptionObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  WasmExceptionObject& exnObj = obj->as<WasmExceptionObject>();
  if (exnObj.isNewborn()) {
    return;
  }
  gcx->free_(obj, exnObj.typedMem(), exnObj.tagType()->tagSize(),
             MemoryUse::WasmExceptionData);
  exnObj.tagType()->Release();
}

/* static */
void WasmExceptionObject::trace(JSTracer* trc, JSObject* obj) {
  WasmExceptionObject& exnObj = obj->as<WasmExceptionObject>();
  if (exnObj.isNewborn()) {
    return;
  }

  wasm::SharedTagType tag = exnObj.tagType();
  const wasm::ValTypeVector& params = tag->argTypes();
  const wasm::TagOffsetVector& offsets = tag->argOffsets();
  uint8_t* typedMem = exnObj.typedMem();
  for (size_t i = 0; i < params.length(); i++) {
    ValType paramType = params[i];
    if (paramType.isRefRepr()) {
      GCPtr<wasm::AnyRef>* paramPtr =
          reinterpret_cast<GCPtr<AnyRef>*>(typedMem + offsets[i]);
      TraceNullableEdge(trc, paramPtr, "wasm exception param");
    }
  }
}

static bool IsException(HandleValue v) {
  return v.isObject() && v.toObject().is<WasmExceptionObject>();
}

struct ExceptionOptions {
  bool traceStack;

  ExceptionOptions() : traceStack(false) {}

  [[nodiscard]] bool init(JSContext* cx, HandleValue val);
};

bool ExceptionOptions::init(JSContext* cx, HandleValue val) {
  if (val.isNullOrUndefined()) {
    return true;
  }

  if (!val.isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_EXN_OPTIONS);
    return false;
  }
  RootedObject obj(cx, &val.toObject());

  // Get `traceStack` and coerce to boolean
  RootedValue traceStackVal(cx);
  if (!JS_GetProperty(cx, obj, "traceStack", &traceStackVal)) {
    return false;
  }
  traceStack = ToBoolean(traceStackVal);

  return true;
}

bool WasmExceptionObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!ThrowIfNotConstructing(cx, args, "Exception")) {
    return false;
  }

  if (!args.requireAtLeast(cx, "WebAssembly.Exception", 2)) {
    return false;
  }

  if (!IsTag(args[0])) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_EXN_ARG);
    return false;
  }
  Rooted<WasmTagObject*> exnTag(cx, &args[0].toObject().as<WasmTagObject>());

  if (!args.get(1).isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_EXN_PAYLOAD);
    return false;
  }

  JS::ForOfIterator iterator(cx);
  if (!iterator.init(args.get(1), JS::ForOfIterator::ThrowOnNonIterable)) {
    return false;
  }

  // Get the optional 'options' parameter
  ExceptionOptions options;
  if (!options.init(cx, args.get(2))) {
    return false;
  }

  // Trace the stack if requested
  RootedObject stack(cx);
  if (options.traceStack && !CaptureStack(cx, &stack)) {
    return false;
  }

  RootedObject proto(
      cx, GetWasmConstructorPrototype(cx, args, JSProto_WasmException));
  if (!proto) {
    ReportOutOfMemory(cx);
    return false;
  }

  Rooted<WasmExceptionObject*> exnObj(
      cx, WasmExceptionObject::create(cx, exnTag, stack, proto));
  if (!exnObj) {
    return false;
  }

  wasm::SharedTagType tagType = exnObj->tagType();
  const wasm::ValTypeVector& params = tagType->argTypes();
  const wasm::TagOffsetVector& offsets = tagType->argOffsets();

  RootedValue nextArg(cx);
  for (size_t i = 0; i < params.length(); i++) {
    bool done;
    if (!iterator.next(&nextArg, &done)) {
      return false;
    }
    if (done) {
      UniqueChars expected(JS_smprintf("%zu", params.length()));
      UniqueChars got(JS_smprintf("%zu", i));
      if (!expected || !got) {
        ReportOutOfMemory(cx);
        return false;
      }

      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               JSMSG_WASM_BAD_EXN_PAYLOAD_LEN, expected.get(),
                               got.get());
      return false;
    }

    if (!exnObj->initArg(cx, offsets[i], params[i], nextArg)) {
      return false;
    }
  }

  args.rval().setObject(*exnObj);
  return true;
}

/* static */
WasmExceptionObject* WasmExceptionObject::create(JSContext* cx,
                                                 Handle<WasmTagObject*> tag,
                                                 HandleObject stack,
                                                 HandleObject proto) {
  Rooted<WasmExceptionObject*> obj(
      cx, NewObjectWithGivenProto<WasmExceptionObject>(cx, proto));
  if (!obj) {
    return nullptr;
  }
  const TagType* tagType = tag->tagType();

  // Allocate the data buffer before initializing the object so that an OOM
  // does not result in a partially constructed object.
  uint8_t* data = (uint8_t*)js_calloc(tagType->tagSize());
  if (!data) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  MOZ_ASSERT(obj->isNewborn());
  obj->initFixedSlot(TAG_SLOT, ObjectValue(*tag));
  tagType->AddRef();
  obj->initFixedSlot(TYPE_SLOT, PrivateValue((void*)tagType));
  InitReservedSlot(obj, DATA_SLOT, data, tagType->tagSize(),
                   MemoryUse::WasmExceptionData);
  obj->initFixedSlot(STACK_SLOT, ObjectOrNullValue(stack));

  MOZ_ASSERT(!obj->isNewborn());

  return obj;
}

WasmExceptionObject* WasmExceptionObject::wrapJSValue(JSContext* cx,
                                                      HandleValue value) {
  Rooted<WasmNamespaceObject*> wasm(cx, WasmNamespaceObject::getOrCreate(cx));
  if (!wasm) {
    return nullptr;
  }

  Rooted<AnyRef> valueAnyRef(cx);
  if (!AnyRef::fromJSValue(cx, value, &valueAnyRef)) {
    return nullptr;
  }

  Rooted<WasmTagObject*> wrappedJSValueTag(cx, wasm->wrappedJSValueTag());
  WasmExceptionObject* exn =
      WasmExceptionObject::create(cx, wrappedJSValueTag, nullptr, nullptr);
  if (!exn) {
    return nullptr;
  }
  MOZ_ASSERT(exn->isWrappedJSValue());

  exn->initRefArg(WrappedJSValueTagType_ValueOffset, valueAnyRef);
  return exn;
}

bool WasmExceptionObject::isNewborn() const {
  MOZ_ASSERT(is<WasmExceptionObject>());
  return getReservedSlot(DATA_SLOT).isUndefined();
}

bool WasmExceptionObject::isWrappedJSValue() const {
  return tagType() == sWrappedJSValueTagType;
}

Value WasmExceptionObject::wrappedJSValue() const {
  MOZ_ASSERT(isWrappedJSValue());
  return loadRefArg(WrappedJSValueTagType_ValueOffset).toJSValue();
}

const JSPropertySpec WasmExceptionObject::properties[] = {
    JS_PSG("stack", WasmExceptionObject::getStack, 0),
    JS_STRING_SYM_PS(toStringTag, "WebAssembly.Exception", JSPROP_READONLY),
    JS_PS_END,
};

/* static */
bool WasmExceptionObject::isImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmExceptionObject*> exnObj(
      cx, &args.thisv().toObject().as<WasmExceptionObject>());

  if (!args.requireAtLeast(cx, "WebAssembly.Exception.is", 1)) {
    return false;
  }

  if (!IsTag(args[0])) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_EXN_ARG);
    return false;
  }

  Rooted<WasmTagObject*> exnTag(cx,
                                &args.get(0).toObject().as<WasmTagObject>());
  args.rval().setBoolean(exnTag.get() == &exnObj->tag());

  return true;
}

/* static  */
bool WasmExceptionObject::isMethod(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsException, isImpl>(cx, args);
}

/* static */
bool WasmExceptionObject::getArgImpl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmExceptionObject*> exnObj(
      cx, &args.thisv().toObject().as<WasmExceptionObject>());

  if (!args.requireAtLeast(cx, "WebAssembly.Exception.getArg", 2)) {
    return false;
  }

  if (!IsTag(args[0])) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_EXN_ARG);
    return false;
  }

  Rooted<WasmTagObject*> exnTag(cx,
                                &args.get(0).toObject().as<WasmTagObject>());
  if (exnTag.get() != &exnObj->tag()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_EXN_TAG);
    return false;
  }

  uint32_t index;
  if (!EnforceRangeU32(cx, args.get(1), "Exception""getArg index", &index)) {
    return false;
  }

  const wasm::ValTypeVector& params = exnTag->valueTypes();
  if (index >= params.length()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE,
                             "Exception""getArg index");
    return false;
  }

  uint32_t offset = exnTag->tagType()->argOffsets()[index];
  RootedValue result(cx);
  if (!exnObj->loadArg(cx, offset, params[index], &result)) {
    return false;
  }
  args.rval().set(result);
  return true;
}

/* static  */
bool WasmExceptionObject::getArg(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsException, getArgImpl>(cx, args);
}

/* static */
bool WasmExceptionObject::getStack_impl(JSContext* cx, const CallArgs& args) {
  Rooted<WasmExceptionObject*> exnObj(
      cx, &args.thisv().toObject().as<WasmExceptionObject>());
  RootedObject savedFrameObj(cx, exnObj->stack());
  if (!savedFrameObj) {
    args.rval().setUndefined();
    return true;
  }
  JSPrincipals* principals = exnObj->realm()->principals();
  RootedString stackString(cx);
  if (!BuildStackString(cx, principals, savedFrameObj, &stackString)) {
    return false;
  }
  args.rval().setString(stackString);
  return true;
}

/* static */
bool WasmExceptionObject::getStack(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsException, getStack_impl>(cx, args);
}

JSObject* WasmExceptionObject::stack() const {
  return getReservedSlot(STACK_SLOT).toObjectOrNull();
}

uint8_t* WasmExceptionObject::typedMem() const {
  return (uint8_t*)getReservedSlot(DATA_SLOT).toPrivate();
}

bool WasmExceptionObject::loadArg(JSContext* cx, size_t offset,
                                  wasm::ValType type,
                                  MutableHandleValue vp) const {
  if (!type.isExposable()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_VAL_TYPE);
    return false;
  }
  return ToJSValue(cx, typedMem() + offset, type, vp);
}

bool WasmExceptionObject::initArg(JSContext* cx, size_t offset,
                                  wasm::ValType type, HandleValue value) {
  if (!type.isExposable()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_VAL_TYPE);
    return false;
  }

  // Avoid rooting hazard of `this` being live across `fromJSValue`
  // which may GC.
  uint8_t* dest = typedMem() + offset;
  RootedVal val(cx);
  if (!Val::fromJSValue(cx, type, value, &val)) {
    return false;
  }
  val.get().writeToHeapLocation(dest);
  return true;
}

void WasmExceptionObject::initRefArg(size_t offset, wasm::AnyRef ref) {
  uint8_t* dest = typedMem() + offset;
  *((GCPtr<AnyRef>*)dest) = ref;
}

wasm::AnyRef WasmExceptionObject::loadRefArg(size_t offset) const {
  uint8_t* src = typedMem() + offset;
  return *((GCPtr<AnyRef>*)src);
}

const JSFunctionSpec WasmExceptionObject::methods[] = {
    JS_FN("is", WasmExceptionObject::isMethod, 1, JSPROP_ENUMERATE),
    JS_FN("getArg", WasmExceptionObject::getArg, 2, JSPROP_ENUMERATE),
    JS_FS_END,
};

const JSFunctionSpec WasmExceptionObject::static_methods[] = {
    JS_FS_END,
};

const TagType* WasmExceptionObject::tagType() const {
  return (const TagType*)getReservedSlot(TYPE_SLOT).toPrivate();
}

WasmTagObject& WasmExceptionObject::tag() const {
  return getReservedSlot(TAG_SLOT).toObject().as<WasmTagObject>();
}

// ============================================================================
// WebAssembly.Function and methods
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
static JSObject* CreateWasmFunctionPrototype(JSContext* cx, JSProtoKey key) {
  // WasmFunction's prototype should inherit from JSFunction's prototype.
  RootedObject jsProto(cx, &cx->global()->getFunctionPrototype());
  return GlobalObject::createBlankPrototypeInheriting(cx, &PlainObject::class_,
                                                      jsProto);
}

[[nodiscard]] static bool IsWasmFunction(HandleValue v) {
  if (!v.isObject()) {
    return false;
  }
  if (!v.toObject().is<JSFunction>()) {
    return false;
  }
  return v.toObject().as<JSFunction>().isWasm();
}

bool WasmFunctionTypeImpl(JSContext* cx, const CallArgs& args) {
  RootedFunction function(cx, &args.thisv().toObject().as<JSFunction>());
  const FuncType& funcType = function->wasmTypeDef()->funcType();
  RootedObject typeObj(cx, FuncTypeToObject(cx, funcType));
  if (!typeObj) {
    return false;
  }
  args.rval().setObject(*typeObj);
  return true;
}

bool WasmFunctionType(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsWasmFunction, WasmFunctionTypeImpl>(cx, args);
}

static JSFunction* WasmFunctionCreate(JSContext* cx, HandleObject func,
                                      wasm::ValTypeVector&& params,
                                      wasm::ValTypeVector&& results,
                                      HandleObject proto) {
  MOZ_ASSERT(IsCallableNonCCW(ObjectValue(*func)));

  // We want to import the function to a wasm module and then export it again so
  // that it behaves exactly like a normal wasm function and can be used like
  // one in wasm tables. We synthesize such a module below, instantiate it, and
  // then return the exported function as the result.
  FeatureOptions options;
  ScriptedCaller scriptedCaller;
  SharedCompileArgs compileArgs =
      CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options);
  if (!compileArgs) {
    return nullptr;
  }

  MutableModuleMetadata moduleMeta = js_new<ModuleMetadata>();
  if (!moduleMeta || !moduleMeta->init(*compileArgs)) {
    return nullptr;
  }
  MutableCodeMetadata codeMeta = moduleMeta->codeMeta;
  CompilerEnvironment compilerEnv(CompileMode::Once, Tier::Optimized,
                                  DebugEnabled::False);
  compilerEnv.computeParameters();

  FuncType funcType = FuncType(std::move(params), std::move(results));
  if (!codeMeta->types->addType(std::move(funcType))) {
    return nullptr;
  }

  // Add an (import (func ...))
  FuncDesc funcDesc = FuncDesc(0);
  if (!codeMeta->funcs.append(funcDesc)) {
    return nullptr;
  }
  codeMeta->numFuncImports = 1;
  codeMeta->funcImportsAreJS = true;

  // Add an (export (func 0))
  codeMeta->funcs[0].declareFuncExported(/* eager */ true,
                                         /* canRefFunc */ true);

  // We will be looking up and using the function in the future by index so the
  // name doesn't matter.
  CacheableName fieldName;
  if (!moduleMeta->exports.emplaceBack(std::move(fieldName), 0,
                                       DefinitionKind::Function)) {
    return nullptr;
  }

  if (!moduleMeta->prepareForCompile(compilerEnv.mode())) {
    return nullptr;
  }

  ModuleGenerator mg(*codeMeta, compilerEnv, compilerEnv.initialState(),
                     nullptr, nullptr, nullptr);
  if (!mg.initializeCompleteTier()) {
    return nullptr;
  }
  // We're not compiling any function definitions.
  if (!mg.finishFuncDefs()) {
    return nullptr;
  }
  SharedBytes shareableBytes = js_new<ShareableBytes>();
  if (!shareableBytes) {
    return nullptr;
  }
  SharedModule module = mg.finishModule(*shareableBytes, moduleMeta,
                                        /*maybeCompleteTier2Listener=*/nullptr);
  if (!module) {
    return nullptr;
  }

  // Instantiate the module.
  Rooted<ImportValues> imports(cx);
  if (!imports.get().funcs.append(func)) {
    return nullptr;
  }
  Rooted<WasmInstanceObject*> instance(cx);
  if (!module->instantiate(cx, imports.get(), nullptr, &instance)) {
    MOZ_ASSERT(cx->isThrowingOutOfMemory());
    return nullptr;
  }

  // Get the exported function which wraps the JS function to return.
  RootedFunction wasmFunc(cx);
  if (!instance->getExportedFunction(cx, instance, 0, &wasmFunc)) {
    return nullptr;
  }
  return wasmFunc;
}

bool WasmFunctionConstruct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!ThrowIfNotConstructing(cx, args, "WebAssembly.Function")) {
    return false;
  }

  if (!args.requireAtLeast(cx, "WebAssembly.Function", 2)) {
    return false;
  }

  if (!args[0].isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_DESC_ARG, "function");
    return false;
  }
  RootedObject typeObj(cx, &args[0].toObject());

  // Extract properties in lexicographic order per spec.

  RootedValue parametersVal(cx);
  if (!JS_GetProperty(cx, typeObj, "parameters", ¶metersVal)) {
    return false;
  }

  ValTypeVector params;
  if (!ParseValTypes(cx, parametersVal, params)) {
    return false;
  }

  RootedValue resultsVal(cx);
  if (!JS_GetProperty(cx, typeObj, "results", &resultsVal)) {
    return false;
  }

  ValTypeVector results;
  if (!ParseValTypes(cx, resultsVal, results)) {
    return false;
  }

  // Get the target function

  if (!IsCallableNonCCW(args[1])) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_FUNCTION_VALUE);
    return false;
  }
  RootedObject func(cx, &args[1].toObject());

  RootedObject proto(
      cx, GetWasmConstructorPrototype(cx, args, JSProto_WasmFunction));
  if (!proto) {
    ReportOutOfMemory(cx);
    return false;
  }

  RootedFunction wasmFunc(cx, WasmFunctionCreate(cx, func, std::move(params),
                                                 std::move(results), proto));
  if (!wasmFunc) {
    ReportOutOfMemory(cx);
    return false;
  }
  args.rval().setObject(*wasmFunc);

  return true;
}

static constexpr char WasmFunctionName[] = "Function";

static JSObject* CreateWasmFunctionConstructor(JSContext* cx, JSProtoKey key) {
  RootedObject proto(cx, &cx->global()->getFunctionConstructor());

  Rooted<JSAtom*> className(
      cx, Atomize(cx, WasmFunctionName, strlen(WasmFunctionName)));
  if (!className) {
    return nullptr;
  }
  return NewFunctionWithProto(cx, WasmFunctionConstruct, 1,
                              FunctionFlags::NATIVE_CTOR, nullptr, className,
                              proto, gc::AllocKind::FUNCTION, TenuredObject);
}

const JSFunctionSpec WasmFunctionMethods[] = {
    JS_FN("type", WasmFunctionType, 0, 0),
    JS_FS_END,
};

const ClassSpec WasmFunctionClassSpec = {
    CreateWasmFunctionConstructor,
    CreateWasmFunctionPrototype,
    nullptr,
    nullptr,
    WasmFunctionMethods,
    nullptr,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

const JSClass js::WasmFunctionClass = {
    "WebAssembly.Function",
    0,
    JS_NULL_CLASS_OPS,
    &WasmFunctionClassSpec,
};

#endif

// ============================================================================
// WebAssembly class and static methods

static bool WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  args.rval().setString(cx->names().WebAssembly);
  return true;
}

static bool RejectWithPendingException(JSContext* cx,
                                       Handle<PromiseObject*> promise) {
  if (!cx->isExceptionPending()) {
    return false;
  }

  RootedValue rejectionValue(cx);
  if (!GetAndClearException(cx, &rejectionValue)) {
    return false;
  }

  return PromiseObject::reject(cx, promise, rejectionValue);
}

static bool Reject(JSContext* cx, const CompileArgs& args,
                   Handle<PromiseObject*> promise, const UniqueChars& error) {
  if (!error) {
    ThrowCompileOutOfMemory(cx);
    return RejectWithPendingException(cx, promise);
  }

  RootedObject stack(cx, promise->allocationSite());
  RootedString fileName(cx);
  if (const char* filename = args.scriptedCaller.filename.get()) {
    fileName =
        JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(filename, strlen(filename)));
  } else {
    fileName = JS_GetEmptyString(cx);
  }
  if (!fileName) {
    return false;
  }

  uint32_t line = args.scriptedCaller.line;

  // Ideally we'd report a JSMSG_WASM_COMPILE_ERROR here, but there's no easy
  // way to create an ErrorObject for an arbitrary error code with multiple
  // replacements.
  UniqueChars str(JS_smprintf("wasm validation error: %s", error.get()));
  if (!str) {
    return false;
  }

  size_t len = strlen(str.get());
  RootedString message(cx, NewStringCopyN<CanGC>(cx, str.get(), len));
  if (!message) {
    return false;
  }

  // There's no error |cause| available here.
  auto cause = JS::NothingHandleValue;

  RootedObject errorObj(
      cx, ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, fileName, 0,
                              line, JS::ColumnNumberOneOrigin(), nullptr,
                              message, cause));
  if (!errorObj) {
    return false;
  }

  RootedValue rejectionValue(cx, ObjectValue(*errorObj));
  return PromiseObject::reject(cx, promise, rejectionValue);
}

static void LogAsync(JSContext* cx, const char* funcName,
                     const Module& module) {
  Log(cx, "async %s succeeded%s", funcName,
      module.loggingDeserialized() ? " (loaded from cache)" : "");
}

enum class Ret { Pair, Instance };

class AsyncInstantiateTask : public OffThreadPromiseTask {
  SharedModule module_;
  PersistentRooted<ImportValues> imports_;
  Ret ret_;

 public:
  AsyncInstantiateTask(JSContext* cx, const Module& module, Ret ret,
                       Handle<PromiseObject*> promise)
      : OffThreadPromiseTask(cx, promise),
        module_(&module),
        imports_(cx),
        ret_(ret) {}

  ImportValues& imports() { return imports_.get(); }

  bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
    RootedObject instanceProto(
        cx, &cx->global()->getPrototype(JSProto_WasmInstance));

    Rooted<WasmInstanceObject*> instanceObj(cx);
    if (!module_->instantiate(cx, imports_.get(), instanceProto,
                              &instanceObj)) {
      return RejectWithPendingException(cx, promise);
    }

    RootedValue resolutionValue(cx);
    if (ret_ == Ret::Instance) {
      resolutionValue = ObjectValue(*instanceObj);
    } else {
      RootedObject resultObj(cx, JS_NewPlainObject(cx));
      if (!resultObj) {
        return RejectWithPendingException(cx, promise);
      }

      RootedObject moduleProto(cx,
                               &cx->global()->getPrototype(JSProto_WasmModule));
      RootedObject moduleObj(
          cx, WasmModuleObject::create(cx, *module_, moduleProto));
      if (!moduleObj) {
        return RejectWithPendingException(cx, promise);
      }

      RootedValue val(cx, ObjectValue(*moduleObj));
      if (!JS_DefineProperty(cx, resultObj, "module", val, JSPROP_ENUMERATE)) {
        return RejectWithPendingException(cx, promise);
      }

      val = ObjectValue(*instanceObj);
      if (!JS_DefineProperty(cx, resultObj, "instance", val,
                             JSPROP_ENUMERATE)) {
        return RejectWithPendingException(cx, promise);
      }

      resolutionValue = ObjectValue(*resultObj);
    }

    if (!PromiseObject::resolve(cx, promise, resolutionValue)) {
      return RejectWithPendingException(cx, promise);
    }

    LogAsync(cx, "instantiate", *module_);
    return true;
  }
};

static bool AsyncInstantiate(JSContext* cx, const Module& module,
                             HandleObject importObj, Ret ret,
                             Handle<PromiseObject*> promise) {
  auto task = js::MakeUnique<AsyncInstantiateTask>(cx, module, ret, promise);
  if (!task || !task->init(cx)) {
    return false;
  }

  if (!GetImports(cx, module, importObj, &task->imports())) {
    return RejectWithPendingException(cx, promise);
  }

  task.release()->dispatchResolveAndDestroy();
  return true;
}

static bool ResolveCompile(JSContext* cx, const Module& module,
                           Handle<PromiseObject*> promise) {
  RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule));
  RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
  if (!moduleObj) {
    return RejectWithPendingException(cx, promise);
  }

  RootedValue resolutionValue(cx, ObjectValue(*moduleObj));
  if (!PromiseObject::resolve(cx, promise, resolutionValue)) {
    return RejectWithPendingException(cx, promise);
  }

  LogAsync(cx, "compile", module);
  return true;
}

struct CompileBufferTask : PromiseHelperTask {
  MutableBytes bytecode;
  SharedCompileArgs compileArgs;
  UniqueChars error;
  UniqueCharsVector warnings;
  SharedModule module;
  bool instantiate;
  PersistentRootedObject importObj;

  CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise,
                    HandleObject importObj)
      : PromiseHelperTask(cx, promise),
        instantiate(true),
        importObj(cx, importObj) {}

  CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise)
      : PromiseHelperTask(cx, promise), instantiate(false) {}

  bool init(JSContext* cx, FeatureOptions options, const char* introducer) {
    compileArgs = InitCompileArgs(cx, options, introducer);
    if (!compileArgs) {
      return false;
    }
    return PromiseHelperTask::init(cx);
  }

  void execute() override {
    module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr);
  }

  bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
    if (!ReportCompileWarnings(cx, warnings)) {
      return false;
    }
    if (!module) {
      return Reject(cx, *compileArgs, promise, error);
    }
    if (instantiate) {
      return AsyncInstantiate(cx, *module, importObj, Ret::Pair, promise);
    }
    return ResolveCompile(cx, *module, promise);
  }
};

static bool RejectWithPendingException(JSContext* cx,
                                       Handle<PromiseObject*> promise,
                                       CallArgs& callArgs) {
  if (!RejectWithPendingException(cx, promise)) {
    return false;
  }

  callArgs.rval().setObject(*promise);
  return true;
}

static bool EnsurePromiseSupport(JSContext* cx) {
  if (!cx->runtime()->offThreadPromiseState.ref().initialized()) {
    JS_ReportErrorASCII(
        cx, "WebAssembly Promise APIs not supported in this runtime.");
    return false;
  }
  return true;
}

static bool GetBufferSource(JSContext* cx, const CallArgs& callArgs,
                            const char* name, MutableBytes* bytecode) {
  if (!callArgs.requireAtLeast(cx, name, 1)) {
    return false;
  }

  if (!callArgs[0].isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_BUF_ARG);
    return false;
  }

  return GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG,
                         bytecode);
}

static bool WebAssembly_compile(JSContext* cx, unsigned argc, Value* vp) {
  if (!EnsurePromiseSupport(cx)) {
    return false;
  }

  Log(cx, "async compile() started");

  Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
  if (!promise) {
    return false;
  }

  CallArgs callArgs = CallArgsFromVp(argc, vp);

  JS::RootedVector<JSString*> parameterStrings(cx);
  JS::RootedVector<Value> parameterArgs(cx);
  bool canCompileStrings = false;
  if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
                                   JS::CompilationType::Undefined,
                                   parameterStrings, nullptr, parameterArgs,
                                   NullHandleValue, &canCompileStrings)) {
    return RejectWithPendingException(cx, promise, callArgs);
  }
  if (!canCompileStrings) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_CSP_BLOCKED_WASM, "WebAssembly.compile");
    return RejectWithPendingException(cx, promise, callArgs);
  }

  auto task = cx->make_unique<CompileBufferTask>(cx, promise);
  if (!task) {
    return false;
  }

  if (!GetBufferSource(cx, callArgs, "WebAssembly.compile", &task->bytecode)) {
    return RejectWithPendingException(cx, promise, callArgs);
  }

  FeatureOptions options;
  if (!options.init(cx, callArgs.get(1))) {
    return false;
  }

  if (!task->init(cx, options, "WebAssembly.compile")) {
    return false;
  }

  if (!StartOffThreadPromiseHelperTask(cx, std::move(task))) {
    return false;
  }

  callArgs.rval().setObject(*promise);
  return true;
}

static bool GetInstantiateArgs(JSContext* cx, const CallArgs& callArgs,
                               MutableHandleObject firstArg,
                               MutableHandleObject importObj,
                               MutableHandleValue featureOptions) {
  if (!callArgs.requireAtLeast(cx, "WebAssembly.instantiate", 1)) {
    return false;
  }

  if (!callArgs[0].isObject()) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_BUF_MOD_ARG);
    return false;
  }

  firstArg.set(&callArgs[0].toObject());

  if (!GetImportArg(cx, callArgs.get(1), importObj)) {
    return false;
  }

  featureOptions.set(callArgs.get(2));
  return true;
}

static bool WebAssembly_instantiate(JSContext* cx, unsigned argc, Value* vp) {
  if (!EnsurePromiseSupport(cx)) {
    return false;
  }

  Log(cx, "async instantiate() started");

  Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
  if (!promise) {
    return false;
  }

  CallArgs callArgs = CallArgsFromVp(argc, vp);

  RootedObject firstArg(cx);
  RootedObject importObj(cx);
  RootedValue featureOptions(cx);
  if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj,
                          &featureOptions)) {
    return RejectWithPendingException(cx, promise, callArgs);
  }

  const Module* module;
  if (IsModuleObject(firstArg, &module)) {
    if (!AsyncInstantiate(cx, *module, importObj, Ret::Instance, promise)) {
      return false;
    }
  } else {
    JS::RootedVector<JSString*> parameterStrings(cx);
    JS::RootedVector<Value> parameterArgs(cx);
    bool canCompileStrings = false;
    if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
                                     JS::CompilationType::Undefined,
                                     parameterStrings, nullptr, parameterArgs,
                                     NullHandleValue, &canCompileStrings)) {
      return RejectWithPendingException(cx, promise, callArgs);
    }
    if (!canCompileStrings) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_CSP_BLOCKED_WASM,
                                "WebAssembly.instantiate");
      return RejectWithPendingException(cx, promise, callArgs);
    }

    FeatureOptions options;
    if (!options.init(cx, featureOptions)) {
      return false;
    }

    auto task = cx->make_unique<CompileBufferTask>(cx, promise, importObj);
    if (!task || !task->init(cx, options, "WebAssembly.instantiate")) {
      return false;
    }

    if (!GetBufferSource(cx, firstArg, JSMSG_WASM_BAD_BUF_MOD_ARG,
                         &task->bytecode)) {
      return RejectWithPendingException(cx, promise, callArgs);
    }

    if (!StartOffThreadPromiseHelperTask(cx, std::move(task))) {
      return false;
    }
  }

  callArgs.rval().setObject(*promise);
  return true;
}

static bool WebAssembly_validate(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs callArgs = CallArgsFromVp(argc, vp);

  MutableBytes bytecode;
  if (!GetBufferSource(cx, callArgs, "WebAssembly.validate", &bytecode)) {
    return false;
  }

  FeatureOptions options;
  if (!options.init(cx, callArgs.get(1))) {
    return false;
  }

  UniqueChars error;
  bool validated = Validate(cx, *bytecode, options, &error);

  // If the reason for validation failure was OOM (signalled by null error
  // message), report out-of-memory so that validate's return is always
  // correct.
  if (!validated && !error) {
    ReportOutOfMemory(cx);
    return false;
  }

  if (error) {
    MOZ_ASSERT(!validated);
    Log(cx, "validate() failed with: %s", error.get());
  }

  callArgs.rval().setBoolean(validated);
  return true;
}

static bool EnsureStreamSupport(JSContext* cx) {
  // This should match wasm::StreamingCompilationAvailable().

  if (!EnsurePromiseSupport(cx)) {
    return false;
  }

  if (!CanUseExtraThreads()) {
    JS_ReportErrorASCII(
        cx, "WebAssembly.compileStreaming not supported with --no-threads");
    return false;
  }

  if (!cx->runtime()->consumeStreamCallback) {
    JS_ReportErrorASCII(cx,
                        "WebAssembly streaming not supported in this runtime");
    return false;
  }

  return true;
}

// This value is chosen and asserted to be disjoint from any host error code.
static const size_t StreamOOMCode = 0;

static bool RejectWithStreamErrorNumber(JSContext* cx, size_t errorCode,
                                        Handle<PromiseObject*> promise) {
  if (errorCode == StreamOOMCode) {
    ReportOutOfMemory(cx);
    return false;
  }

  cx->runtime()->reportStreamErrorCallback(cx, errorCode);
  return RejectWithPendingException(cx, promise);
}

class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer {
  // The stream progresses monotonically through these states; the helper
  // thread wait()s for streamState_ to reach Closed.
  enum StreamState { Env, Code, Tail, Closed };
  ExclusiveWaitableData<StreamState> streamState_;

  // Immutable:
  const bool instantiate_;
  const PersistentRootedObject importObj_;

  // Immutable after noteResponseURLs() which is called at most once before
  // first call on stream thread:
  const MutableCompileArgs compileArgs_;

  // Immutable after Env state:
  Bytes envBytes_;
  BytecodeRange codeSection_;

  // The code section vector is resized once during the Env state and filled
  // in chunk by chunk during the Code state, updating the end-pointer after
  // each chunk:
  Bytes codeBytes_;
  uint8_t* codeBytesEnd_;
  ExclusiveBytesPtr exclusiveCodeBytesEnd_;

  // Immutable after Tail state:
  Bytes tailBytes_;
  ExclusiveStreamEndData exclusiveStreamEnd_;

  // Written once before Closed state and read in Closed state on main thread:
  SharedModule module_;
  Maybe<size_t> streamError_;
  UniqueChars compileError_;
  UniqueCharsVector warnings_;

  // Set on stream thread and read racily on helper thread to abort compilation:
  mozilla::Atomic<bool> streamFailed_;

  // Called on some thread before consumeChunk(), streamEnd(), streamError()):

  void noteResponseURLs(const char* url, const char* sourceMapUrl) override {
    if (url) {
      compileArgs_->scriptedCaller.filename = DuplicateString(url);
      compileArgs_->scriptedCaller.filenameIsURL = true;
    }
    if (sourceMapUrl) {
      compileArgs_->sourceMapURL = DuplicateString(sourceMapUrl);
    }
  }

  // Called on a stream thread:

  // Until StartOffThreadPromiseHelperTask succeeds, we are responsible for
  // dispatching ourselves back to the JS thread.
  //
  // Warning: After this function returns, 'this' can be deleted at any time, so
  // the caller must immediately return from the stream callback.
  void setClosedAndDestroyBeforeHelperThreadStarted() {
    streamState_.lock().get() = Closed;
    dispatchResolveAndDestroy();
  }

  // See setClosedAndDestroyBeforeHelperThreadStarted() comment.
  bool rejectAndDestroyBeforeHelperThreadStarted(size_t errorNumber) {
    MOZ_ASSERT(streamState_.lock() == Env);
    MOZ_ASSERT(!streamError_);
    streamError_ = Some(errorNumber);
    setClosedAndDestroyBeforeHelperThreadStarted();
    return false;
  }

  // Once StartOffThreadPromiseHelperTask succeeds, the helper thread will
  // dispatchResolveAndDestroy() after execute() returns, but execute()
  // wait()s for state to be Closed.
  //
  // Warning: After this function returns, 'this' can be deleted at any time, so
  // the caller must immediately return from the stream callback.
  void setClosedAndDestroyAfterHelperThreadStarted() {
    auto streamState = streamState_.lock();
    MOZ_ASSERT(streamState != Closed);
    streamState.get() = Closed;
    streamState.notify_one(/* stream closed */);
  }

  // See setClosedAndDestroyAfterHelperThreadStarted() comment.
  bool rejectAndDestroyAfterHelperThreadStarted(size_t errorNumber) {
    MOZ_ASSERT(!streamError_);
    streamError_ = Some(errorNumber);
    streamFailed_ = true;
    exclusiveCodeBytesEnd_.lock().notify_one();
    exclusiveStreamEnd_.lock().notify_one();
    setClosedAndDestroyAfterHelperThreadStarted();
    return false;
  }

  bool consumeChunk(const uint8_t* begin, size_t length) override {
    switch (streamState_.lock().get()) {
      case Env: {
        if (!envBytes_.append(begin, length)) {
          return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
        }

        if (!StartsCodeSection(envBytes_.begin(), envBytes_.end(),
                               &codeSection_)) {
          return true;
        }

        uint32_t extraBytes = envBytes_.length() - codeSection_.start;
        if (extraBytes) {
          envBytes_.shrinkTo(codeSection_.start);
        }

        if (codeSection_.size > MaxCodeSectionBytes) {
          return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
        }

        if (!codeBytes_.resize(codeSection_.size)) {
          return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
        }

        codeBytesEnd_ = codeBytes_.begin();
        exclusiveCodeBytesEnd_.lock().get() = codeBytesEnd_;

        if (!StartOffThreadPromiseHelperTask(this)) {
          return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
        }

        // Set the state to Code iff StartOffThreadPromiseHelperTask()
        // succeeds so that the state tells us whether we are before or
        // after the helper thread started.
        streamState_.lock().get() = Code;

        if (extraBytes) {
          return consumeChunk(begin + length - extraBytes, extraBytes);
        }

        return true;
      }
      case Code: {
        size_t copyLength =
            std::min<size_t>(length, codeBytes_.end() - codeBytesEnd_);
        memcpy(codeBytesEnd_, begin, copyLength);
        codeBytesEnd_ += copyLength;

        {
          auto codeStreamEnd = exclusiveCodeBytesEnd_.lock();
          codeStreamEnd.get() = codeBytesEnd_;
          codeStreamEnd.notify_one();
        }

        if (codeBytesEnd_ != codeBytes_.end()) {
          return true;
        }

        streamState_.lock().get() = Tail;

        if (uint32_t extraBytes = length - copyLength) {
          return consumeChunk(begin + copyLength, extraBytes);
        }

        return true;
      }
      case Tail: {
        if (!tailBytes_.append(begin, length)) {
          return rejectAndDestroyAfterHelperThreadStarted(StreamOOMCode);
        }

        return true;
      }
      case Closed:
        MOZ_CRASH("consumeChunk() in Closed state");
    }
    MOZ_CRASH("unreachable");
  }

  void streamEnd(
      JS::OptimizedEncodingListener* completeTier2Listener) override {
    switch (streamState_.lock().get()) {
      case Env: {
        SharedBytes bytecode = js_new<ShareableBytes>(std::move(envBytes_));
        if (!bytecode) {
          rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
          return;
        }
        module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_,
                                &warnings_, nullptr);
        setClosedAndDestroyBeforeHelperThreadStarted();
        return;
      }
      case Code:
      case Tail:
        // Unlock exclusiveStreamEnd_ before locking streamState_.
        {
          auto streamEnd = exclusiveStreamEnd_.lock();
          MOZ_ASSERT(!streamEnd->reached);
          streamEnd->reached = true;
          streamEnd->tailBytes = &tailBytes_;
          streamEnd->completeTier2Listener = completeTier2Listener;
          streamEnd.notify_one();
        }
        setClosedAndDestroyAfterHelperThreadStarted();
        return;
      case Closed:
        MOZ_CRASH("streamEnd() in Closed state");
    }
  }

  void streamError(size_t errorCode) override {
    MOZ_ASSERT(errorCode != StreamOOMCode);
    switch (streamState_.lock().get()) {
      case Env:
        rejectAndDestroyBeforeHelperThreadStarted(errorCode);
        return;
      case Tail:
      case Code:
        rejectAndDestroyAfterHelperThreadStarted(errorCode);
        return;
      case Closed:
        MOZ_CRASH("streamError() in Closed state");
    }
  }

  void consumeOptimizedEncoding(const uint8_t* begin, size_t length) override {
    module_ = Module::deserialize(begin, length);

    MOZ_ASSERT(streamState_.lock().get() == Env);
    setClosedAndDestroyBeforeHelperThreadStarted();
  }

  // Called on a helper thread:

  void execute() override {
    module_ = CompileStreaming(*compileArgs_, envBytes_, codeBytes_,
                               exclusiveCodeBytesEnd_, exclusiveStreamEnd_,
                               streamFailed_, &compileError_, &warnings_);

    // When execute() returns, the CompileStreamTask will be dispatched
    // back to its JS thread to call resolve() and then be destroyed. We
    // can't let this happen until the stream has been closed lest
    // consumeChunk() or streamEnd() be called on a dead object.
    auto streamState = streamState_.lock();
    while (streamState != Closed) {
      streamState.wait(/* stream closed */);
    }
  }

  // Called on a JS thread after streaming compilation completes/errors:

  bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
    MOZ_ASSERT(streamState_.lock() == Closed);

    if (!ReportCompileWarnings(cx, warnings_)) {
      return false;
    }
    if (module_) {
      MOZ_ASSERT(!streamFailed_ && !streamError_ && !compileError_);
      if (instantiate_) {
        return AsyncInstantiate(cx, *module_, importObj_, Ret::Pair, promise);
      }
      return ResolveCompile(cx, *module_, promise);
    }

    if (streamError_) {
      return RejectWithStreamErrorNumber(cx, *streamError_, promise);
    }

    return Reject(cx, *compileArgs_, promise, compileError_);
  }

 public:
  CompileStreamTask(JSContext* cx, Handle<PromiseObject*> promise,
                    CompileArgs& compileArgs, bool instantiate,
                    HandleObject importObj)
      : PromiseHelperTask(cx, promise),
        streamState_(mutexid::WasmStreamStatus, Env),
        instantiate_(instantiate),
        importObj_(cx, importObj),
        compileArgs_(&compileArgs),
        codeSection_{},
        codeBytesEnd_(nullptr),
        exclusiveCodeBytesEnd_(mutexid::WasmCodeBytesEnd, nullptr),
        exclusiveStreamEnd_(mutexid::WasmStreamEnd),
        streamFailed_(false) {
    MOZ_ASSERT_IF(importObj_, instantiate_);
  }
};

// A short-lived object that captures the arguments of a
// WebAssembly.{compileStreaming,instantiateStreaming} while waiting for
// the Promise<Response> to resolve to a (hopefully) Promise.
class ResolveResponseClosure : public NativeObject {
  static const unsigned COMPILE_ARGS_SLOT = 0;
  static const unsigned PROMISE_OBJ_SLOT = 1;
  static const unsigned INSTANTIATE_SLOT = 2;
  static const unsigned IMPORT_OBJ_SLOT = 3;
  static const JSClassOps classOps_;

  static void finalize(JS::GCContext* gcx, JSObject* obj) {
    auto& closure = obj->as<ResolveResponseClosure>();
    gcx->release(obj, &closure.compileArgs(),
                 MemoryUse::WasmResolveResponseClosure);
  }

 public:
  static const unsigned RESERVED_SLOTS = 4;
  static const JSClass class_;

  static ResolveResponseClosure* create(JSContext* cx, const CompileArgs& args,
                                        HandleObject promise, bool instantiate,
                                        HandleObject importObj) {
    MOZ_ASSERT_IF(importObj, instantiate);

    AutoSetNewObjectMetadata metadata(cx);
    auto* obj = NewObjectWithGivenProto<ResolveResponseClosure>(cx, nullptr);
    if (!obj) {
      return nullptr;
    }

    args.AddRef();
    InitReservedSlot(obj, COMPILE_ARGS_SLOT, const_cast<CompileArgs*>(&args),
                     MemoryUse::WasmResolveResponseClosure);
    obj->setReservedSlot(PROMISE_OBJ_SLOT, ObjectValue(*promise));
    obj->setReservedSlot(INSTANTIATE_SLOT, BooleanValue(instantiate));
    obj->setReservedSlot(IMPORT_OBJ_SLOT, ObjectOrNullValue(importObj));
    return obj;
  }

  CompileArgs& compileArgs() const {
    return *(CompileArgs*)getReservedSlot(COMPILE_ARGS_SLOT).toPrivate();
  }
  PromiseObject& promise() const {
    return getReservedSlot(PROMISE_OBJ_SLOT).toObject().as<PromiseObject>();
  }
  bool instantiate() const {
    return getReservedSlot(INSTANTIATE_SLOT).toBoolean();
  }
  JSObject* importObj() const {
    return getReservedSlot(IMPORT_OBJ_SLOT).toObjectOrNull();
  }
};

const JSClassOps ResolveResponseClosure::classOps_ = {
    nullptr,                           // addProperty
    nullptr,                           // delProperty
    nullptr,                           // enumerate
    nullptr,                           // newEnumerate
    nullptr,                           // resolve
    nullptr,                           // mayResolve
    ResolveResponseClosure::finalize,  // finalize
    nullptr,                           // call
    nullptr,                           // construct
    nullptr,                           // trace
};

const JSClass ResolveResponseClosure::class_ = {
    "WebAssembly ResolveResponseClosure",
    JSCLASS_DELAY_METADATA_BUILDER |
        JSCLASS_HAS_RESERVED_SLOTS(ResolveResponseClosure::RESERVED_SLOTS) |
        JSCLASS_FOREGROUND_FINALIZE,
    &ResolveResponseClosure::classOps_,
};

static ResolveResponseClosure* ToResolveResponseClosure(const CallArgs& args) {
  return &args.callee()
              .as<JSFunction>()
              .getExtendedSlot(0)
              .toObject()
              .as<ResolveResponseClosure>();
}

static bool RejectWithErrorNumber(JSContext* cx, uint32_t errorNumber,
                                  Handle<PromiseObject*> promise) {
  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
  return RejectWithPendingException(cx, promise);
}

static bool ResolveResponse_OnFulfilled(JSContext* cx, unsigned argc,
                                        Value* vp) {
  CallArgs callArgs = CallArgsFromVp(argc, vp);

  Rooted<ResolveResponseClosure*> closure(cx,
                                          ToResolveResponseClosure(callArgs));
  Rooted<PromiseObject*> promise(cx, &closure->promise());
  CompileArgs& compileArgs = closure->compileArgs();
  bool instantiate = closure->instantiate();
  Rooted<JSObject*> importObj(cx, closure->importObj());

  auto task = cx->make_unique<CompileStreamTask>(cx, promise, compileArgs,
                                                 instantiate, importObj);
  if (!task || !task->init(cx)) {
    return false;
  }

  if (!callArgs.get(0).isObject()) {
    return RejectWithErrorNumber(cx, JSMSG_WASM_BAD_RESPONSE_VALUE, promise);
  }

  RootedObject response(cx, &callArgs.get(0).toObject());
  if (!cx->runtime()->consumeStreamCallback(cx, response, JS::MimeType::Wasm,
                                            task.get())) {
    return RejectWithPendingException(cx, promise);
  }

  (void)task.release();

  callArgs.rval().setUndefined();
  return true;
}

static bool ResolveResponse_OnRejected(JSContext* cx, unsigned argc,
                                       Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  Rooted<ResolveResponseClosure*> closure(cx, ToResolveResponseClosure(args));
  Rooted<PromiseObject*> promise(cx, &closure->promise());

  if (!PromiseObject::reject(cx, promise, args.get(0))) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}

static bool ResolveResponse(JSContext* cx, Handle<Value> responsePromise,
                            Handle<Value> featureOptions,
                            Handle<PromiseObject*> resultPromise,
                            bool instantiate = false,
                            HandleObject importObj = nullptr) {
  MOZ_ASSERT_IF(importObj, instantiate);

  const char* introducer = instantiate ? "WebAssembly.instantiateStreaming"
                                       : "WebAssembly.compileStreaming";

  FeatureOptions options;
  if (!options.init(cx, featureOptions)) {
    return false;
  }

  SharedCompileArgs compileArgs = InitCompileArgs(cx, options, introducer);
  if (!compileArgs) {
    return false;
  }

  RootedObject closure(
      cx, ResolveResponseClosure::create(cx, *compileArgs, resultPromise,
                                         instantiate, importObj));
  if (!closure) {
    return false;
  }

  RootedFunction onResolved(
      cx, NewNativeFunction(cx, ResolveResponse_OnFulfilled, 1, nullptr,
                            gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
  if (!onResolved) {
    return false;
  }

  RootedFunction onRejected(
      cx, NewNativeFunction(cx, ResolveResponse_OnRejected, 1, nullptr,
                            gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
  if (!onRejected) {
    return false;
  }

  onResolved->setExtendedSlot(0, ObjectValue(*closure));
  onRejected->setExtendedSlot(0, ObjectValue(*closure));

  RootedObject resolve(cx,
                       PromiseObject::unforgeableResolve(cx, responsePromise));
  if (!resolve) {
    return false;
  }

  return JS::AddPromiseReactions(cx, resolve, onResolved, onRejected);
}

static bool WebAssembly_compileStreaming(JSContext* cx, unsigned argc,
                                         Value* vp) {
  if (!EnsureStreamSupport(cx)) {
    return false;
  }

  Log(cx, "async compileStreaming() started");

  Rooted<PromiseObject*> resultPromise(
      cx, PromiseObject::createSkippingExecutor(cx));
  if (!resultPromise) {
    return false;
  }

  CallArgs callArgs = CallArgsFromVp(argc, vp);

  JS::RootedVector<JSString*> parameterStrings(cx);
  JS::RootedVector<Value> parameterArgs(cx);
  bool canCompileStrings = false;
  if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
                                   JS::CompilationType::Undefined,
                                   parameterStrings, nullptr, parameterArgs,
                                   NullHandleValue, &canCompileStrings)) {
    return RejectWithPendingException(cx, resultPromise, callArgs);
  }
  if (!canCompileStrings) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_CSP_BLOCKED_WASM,
                              "WebAssembly.compileStreaming");
    return RejectWithPendingException(cx, resultPromise, callArgs);
  }

  Rooted<Value> responsePromise(cx, callArgs.get(0));
  Rooted<Value> featureOptions(cx, callArgs.get(1));
  if (!ResolveResponse(cx, responsePromise, featureOptions, resultPromise)) {
    return RejectWithPendingException(cx, resultPromise, callArgs);
  }

  callArgs.rval().setObject(*resultPromise);
  return true;
}

static bool WebAssembly_instantiateStreaming(JSContext* cx, unsigned argc,
                                             Value* vp) {
  if (!EnsureStreamSupport(cx)) {
    return false;
  }

  Log(cx, "async instantiateStreaming() started");

  Rooted<PromiseObject*> resultPromise(
      cx, PromiseObject::createSkippingExecutor(cx));
  if (!resultPromise) {
    return false;
  }

  CallArgs callArgs = CallArgsFromVp(argc, vp);

  JS::RootedVector<JSString*> parameterStrings(cx);
  JS::RootedVector<Value> parameterArgs(cx);
  bool canCompileStrings = false;
  if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
                                   JS::CompilationType::Undefined,
                                   parameterStrings, nullptr, parameterArgs,
                                   NullHandleValue, &canCompileStrings)) {
    return RejectWithPendingException(cx, resultPromise, callArgs);
  }
  if (!canCompileStrings) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_CSP_BLOCKED_WASM,
                              "WebAssembly.instantiateStreaming");
    return RejectWithPendingException(cx, resultPromise, callArgs);
  }

  Rooted<JSObject*> firstArg(cx);
  Rooted<JSObject*> importObj(cx);
  Rooted<Value> featureOptions(cx);
  if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj,
                          &featureOptions)) {
    return RejectWithPendingException(cx, resultPromise, callArgs);
  }
  Rooted<Value> responsePromise(cx, ObjectValue(*firstArg.get()));

  if (!ResolveResponse(cx, responsePromise, featureOptions, resultPromise, true,
                       importObj)) {
    return RejectWithPendingException(cx, resultPromise, callArgs);
  }

  callArgs.rval().setObject(*resultPromise);
  return true;
}

#ifdef ENABLE_WASM_JSPI
const ClassSpec WasmSuspendingObject::classSpec_ = {
    GenericCreateConstructor<construct, 1, gc::AllocKind::FUNCTION>,
    GenericCreatePrototype<WasmSuspendingObject>,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

const JSClass WasmSuspendingObject::class_ = {
    "Suspending",
    JSCLASS_HAS_RESERVED_SLOTS(WasmSuspendingObject::RESERVED_SLOTS),
    JS_NULL_CLASS_OPS,
    &classSpec_,
};

const JSClass& WasmSuspendingObject::protoClass_ = PlainObject::class_;

/* static */
bool WasmSuspendingObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!ThrowIfNotConstructing(cx, args, "WebAssembly.Suspending")) {
    return false;
  }

  if (!args.requireAtLeast(cx, "WebAssembly.Suspending", 1)) {
    return false;
  }

  if (!IsCallableNonCCW(args[0])) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_FUNCTION_VALUE);
    return false;
  }

  RootedObject callable(cx, &args[0].toObject());
  Rooted<WasmSuspendingObject*> suspending(
      cx, NewBuiltinClassInstance<WasmSuspendingObject>(cx));
  if (!suspending) {
    return false;
  }
  suspending->setWrappedFunction(callable);
  args.rval().setObject(*suspending);
  return true;
}

static bool WebAssembly_promising(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!args.requireAtLeast(cx, "WebAssembly.promising", 1)) {
    return false;
  }

  if (!IsWasmFunction(args[0])) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_FUNCTION_VALUE);
    return false;
  }

  RootedObject func(cx, &args[0].toObject());
  RootedFunction promise(
      cx, WasmPromisingFunctionCreate(cx, func, wasm::ValTypeVector(),
                                      wasm::ValTypeVector()));
  if (!promise) {
    return false;
  }
  args.rval().setObject(*promise);
  return true;
}

static const JSFunctionSpec WebAssembly_jspi_methods[] = {
    JS_FN("promising", WebAssembly_promising, 1, JSPROP_ENUMERATE),
    JS_FS_END,
};

bool js::IsWasmSuspendingObject(JSObject* obj) {
  return obj->is<WasmSuspendingObject>();
}

JSObject* js::MaybeUnwrapSuspendingObject(JSObject* wrapper) {
  if (!wrapper->is<WasmSuspendingObject>()) {
    return nullptr;
  }
  return wrapper->as<WasmSuspendingObject>().wrappedFunction();
}
#else
bool js::IsWasmSuspendingObject(JSObject* obj) { return false; }
#endif  // ENABLE_WASM_JSPI

#ifdef ENABLE_WASM_MOZ_INTGEMM

static bool WebAssembly_mozIntGemm(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  Rooted<WasmModuleObject*> module(cx);
  if (!wasm::CompileBuiltinModule(cx, wasm::BuiltinModuleId::IntGemm,
                                  &module)) {
    ReportOutOfMemory(cx);
    return false;
  }
  args.rval().set(ObjectValue(*module.get()));
  return true;
}

static const JSFunctionSpec WebAssembly_mozIntGemm_methods[] = {
    JS_FN("mozIntGemm", WebAssembly_mozIntGemm, 0, JSPROP_ENUMERATE),
    JS_FS_END,
};

#endif  // ENABLE_WASM_MOZ_INTGEMM

static const JSFunctionSpec WebAssembly_static_methods[] = {
    JS_FN("toSource", WebAssembly_toSource, 0, 0),
    JS_FN("compile", WebAssembly_compile, 1, JSPROP_ENUMERATE),
    JS_FN("instantiate", WebAssembly_instantiate, 1, JSPROP_ENUMERATE),
    JS_FN("validate", WebAssembly_validate, 1, JSPROP_ENUMERATE),
    JS_FN("compileStreaming", WebAssembly_compileStreaming, 1,
          JSPROP_ENUMERATE),
    JS_FN("instantiateStreaming", WebAssembly_instantiateStreaming, 1,
          JSPROP_ENUMERATE),
    JS_FS_END,
};

static const JSPropertySpec WebAssembly_static_properties[] = {
    JS_STRING_SYM_PS(toStringTag, "WebAssembly", JSPROP_READONLY),
    JS_PS_END,
};

static JSObject* CreateWebAssemblyObject(JSContext* cx, JSProtoKey key) {
  MOZ_RELEASE_ASSERT(HasSupport(cx));

  RootedObject proto(cx, &cx->global()->getObjectPrototype());
  return NewTenuredObjectWithGivenProto(cx, &WasmNamespaceObject::class_,
                                        proto);
}

struct NameAndProtoKey {
  const charconst name;
  JSProtoKey key;
};

static bool WebAssemblyDefineConstructor(JSContext* cx,
                                         Handle<WasmNamespaceObject*> wasm,
                                         NameAndProtoKey entry,
                                         MutableHandleValue ctorValue,
                                         MutableHandleId id) {
  JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, entry.key);
  if (!ctor) {
    return false;
  }
  ctorValue.setObject(*ctor);

  JSAtom* className = Atomize(cx, entry.name, strlen(entry.name));
  if (!className) {
    return false;
  }
  id.set(AtomToId(className));

  return DefineDataProperty(cx, wasm, id, ctorValue, 0);
}

static bool WebAssemblyClassFinish(JSContext* cx, HandleObject object,
                                   HandleObject proto) {
  Handle<WasmNamespaceObject*> wasm = object.as<WasmNamespaceObject>();

  constexpr NameAndProtoKey entries[] = {
      {"Module", JSProto_WasmModule},
      {"Instance", JSProto_WasmInstance},
      {"Memory", JSProto_WasmMemory},
      {"Table", JSProto_WasmTable},
      {"Global", JSProto_WasmGlobal},
      {"CompileError", GetExceptionProtoKey(JSEXN_WASMCOMPILEERROR)},
      {"LinkError", GetExceptionProtoKey(JSEXN_WASMLINKERROR)},
      {"RuntimeError", GetExceptionProtoKey(JSEXN_WASMRUNTIMEERROR)},
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
      {"Function", JSProto_WasmFunction},
#endif
  };
  RootedValue ctorValue(cx);
  RootedId id(cx);
  for (const auto& entry : entries) {
    if (!WebAssemblyDefineConstructor(cx, wasm, entry, &ctorValue, &id)) {
      return false;
    }
  }

  constexpr NameAndProtoKey exceptionEntries[] = {
      {"Tag", JSProto_WasmTag},
      {"Exception", JSProto_WasmException},
  };
  for (const auto& entry : exceptionEntries) {
    if (!WebAssemblyDefineConstructor(cx, wasm, entry, &ctorValue, &id)) {
      return false;
    }
  }

  RootedObject tagProto(
      cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmTag));
  if (!tagProto) {
    ReportOutOfMemory(cx);
    return false;
  }

  SharedTagType wrappedJSValueTagType(sWrappedJSValueTagType);
  WasmTagObject* wrappedJSValueTagObject =
      WasmTagObject::create(cx, wrappedJSValueTagType, tagProto);
  if (!wrappedJSValueTagObject) {
    return false;
  }

  wasm->setWrappedJSValueTag(wrappedJSValueTagObject);

  if (ExnRefAvailable(cx)) {
    RootedId jsTagName(cx, NameToId(cx->names().jsTag));
    RootedValue jsTagValue(cx, ObjectValue(*wrappedJSValueTagObject));
    if (!DefineDataProperty(cx, wasm, jsTagName, jsTagValue,
                            JSPROP_READONLY | JSPROP_ENUMERATE)) {
      return false;
    }
  }

#ifdef ENABLE_WASM_JSPI
  constexpr NameAndProtoKey jspiEntries[] = {
      {"Suspending", JSProto_WasmSuspending},
  };
  if (JSPromiseIntegrationAvailable(cx)) {
    if (!JS_DefineFunctions(cx, wasm, WebAssembly_jspi_methods)) {
      return false;
    }
    for (const auto& entry : jspiEntries) {
      if (!WebAssemblyDefineConstructor(cx, wasm, entry, &ctorValue, &id)) {
        return false;
      }
    }
  }
#endif

#ifdef ENABLE_WASM_MOZ_INTGEMM
  if (MozIntGemmAvailable(cx) &&
      !JS_DefineFunctions(cx, wasm, WebAssembly_mozIntGemm_methods)) {
    return false;
  }
#endif

  return true;
}

WasmNamespaceObject* WasmNamespaceObject::getOrCreate(JSContext* cx) {
  JSObject* wasm =
      GlobalObject::getOrCreateConstructor(cx, JSProto_WebAssembly);
  if (!wasm) {
    return nullptr;
  }
  return &wasm->as<WasmNamespaceObject>();
}

static const ClassSpec WebAssemblyClassSpec = {
    CreateWebAssemblyObject,       nullptr, WebAssembly_static_methods,
    WebAssembly_static_properties, nullptr, nullptr,
    WebAssemblyClassFinish,
};

const JSClass js::WasmNamespaceObject::class_ = {
    "WebAssembly",
    JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly) |
        JSCLASS_HAS_RESERVED_SLOTS(WasmNamespaceObject::RESERVED_SLOTS),
    JS_NULL_CLASS_OPS,
    &WebAssemblyClassSpec,
};

// Sundry

Messung V0.5 in Prozent
C=93 H=99 G=95

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.98Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-26) ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge