diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/wasm/WasmTable.cpp | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/js/src/wasm/WasmTable.cpp b/js/src/wasm/WasmTable.cpp new file mode 100644 index 0000000000..b1b228293b --- /dev/null +++ b/js/src/wasm/WasmTable.cpp @@ -0,0 +1,513 @@ +/* -*- 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/WasmTable.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/PodOperations.h" + +#include "vm/JSContext.h" +#include "vm/Realm.h" +#include "wasm/WasmInstance.h" +#include "wasm/WasmJS.h" +#include "wasm/WasmValue.h" + +#include "gc/StableCellHasher-inl.h" +#include "wasm/WasmInstance-inl.h" + +using namespace js; +using namespace js::wasm; +using mozilla::CheckedInt; +using mozilla::PodZero; + +Table::Table(JSContext* cx, const TableDesc& desc, + Handle<WasmTableObject*> maybeObject, FuncRefVector&& functions) + : maybeObject_(maybeObject), + observers_(cx->zone()), + functions_(std::move(functions)), + elemType_(desc.elemType), + isAsmJS_(desc.isAsmJS), + length_(desc.initialLength), + maximum_(desc.maximumLength) { + // Acquire a strong reference to the type definition this table may be + // referencing. + elemType_.AddRef(); + MOZ_ASSERT(repr() == TableRepr::Func); +} + +Table::Table(JSContext* cx, const TableDesc& desc, + Handle<WasmTableObject*> maybeObject, TableAnyRefVector&& objects) + : maybeObject_(maybeObject), + observers_(cx->zone()), + objects_(std::move(objects)), + elemType_(desc.elemType), + isAsmJS_(desc.isAsmJS), + length_(desc.initialLength), + maximum_(desc.maximumLength) { + // Acquire a strong reference to the type definition this table may be + // referencing. + elemType_.AddRef(); + MOZ_ASSERT(repr() == TableRepr::Ref); +} + +Table::~Table() { + // Release the strong reference, if any. + elemType_.Release(); +} + +/* static */ +SharedTable Table::create(JSContext* cx, const TableDesc& desc, + Handle<WasmTableObject*> maybeObject) { + // Tables are initialized with init_expr values at Instance::init or + // WasmTableObject::create. + + switch (desc.elemType.tableRepr()) { + case TableRepr::Func: { + FuncRefVector functions; + if (!functions.resize(desc.initialLength)) { + ReportOutOfMemory(cx); + return nullptr; + } + return SharedTable( + cx->new_<Table>(cx, desc, maybeObject, std::move(functions))); + } + case TableRepr::Ref: { + TableAnyRefVector objects; + if (!objects.resize(desc.initialLength)) { + ReportOutOfMemory(cx); + return nullptr; + } + return SharedTable( + cx->new_<Table>(cx, desc, maybeObject, std::move(objects))); + } + } + MOZ_CRASH("switch is exhaustive"); +} + +void Table::tracePrivate(JSTracer* trc) { + // If this table has a WasmTableObject, then this method is only called by + // WasmTableObject's trace hook so maybeObject_ must already be marked. + // TraceEdge is called so that the pointer can be updated during a moving + // GC. + TraceNullableEdge(trc, &maybeObject_, "wasm table object"); + + switch (repr()) { + case TableRepr::Func: { + if (isAsmJS_) { +#ifdef DEBUG + for (uint32_t i = 0; i < length_; i++) { + MOZ_ASSERT(!functions_[i].instance); + } +#endif + break; + } + + for (uint32_t i = 0; i < length_; i++) { + if (functions_[i].instance) { + wasm::TraceInstanceEdge(trc, functions_[i].instance, + "wasm table instance"); + } else { + MOZ_ASSERT(!functions_[i].code); + } + } + break; + } + case TableRepr::Ref: { + objects_.trace(trc); + break; + } + } +} + +void Table::trace(JSTracer* trc) { + // The trace hook of WasmTableObject will call Table::tracePrivate at + // which point we can mark the rest of the children. If there is no + // WasmTableObject, call Table::tracePrivate directly. Redirecting through + // the WasmTableObject avoids marking the entire Table on each incoming + // edge (once per dependent Instance). + if (maybeObject_) { + TraceEdge(trc, &maybeObject_, "wasm table object"); + } else { + tracePrivate(trc); + } +} + +uint8_t* Table::instanceElements() const { + if (repr() == TableRepr::Ref) { + return (uint8_t*)objects_.begin(); + } + return (uint8_t*)functions_.begin(); +} + +const FunctionTableElem& Table::getFuncRef(uint32_t index) const { + MOZ_ASSERT(isFunction()); + return functions_[index]; +} + +bool Table::getFuncRef(JSContext* cx, uint32_t index, + MutableHandleFunction fun) const { + MOZ_ASSERT(isFunction()); + + const FunctionTableElem& elem = getFuncRef(index); + if (!elem.code) { + fun.set(nullptr); + return true; + } + + Instance& instance = *elem.instance; + const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code); + + Rooted<WasmInstanceObject*> instanceObj(cx, instance.object()); + return instanceObj->getExportedFunction(cx, instanceObj, + codeRange.funcIndex(), fun); +} + +void Table::setFuncRef(uint32_t index, JSFunction* fun) { + MOZ_ASSERT(isFunction()); + MOZ_ASSERT(fun->isWasm()); + + // Tables can store references to wasm functions from other instances. To + // preserve the === function identity required by the JS embedding spec, we + // must set the element to the function's underlying + // CodeRange.funcCheckedCallEntry and Instance so that Table.get()s always + // produce the same function object as was imported. + WasmInstanceObject* instanceObj = ExportedFunctionToInstanceObject(fun); + Instance& instance = instanceObj->instance(); + Tier tier = instance.code().bestTier(); + const CodeRange& calleeCodeRange = + instanceObj->getExportedFunctionCodeRange(fun, tier); + void* code = instance.codeBase(tier) + calleeCodeRange.funcCheckedCallEntry(); + setFuncRef(index, code, &instance); +} + +void Table::setFuncRef(uint32_t index, void* code, Instance* instance) { + MOZ_ASSERT(isFunction()); + + FunctionTableElem& elem = functions_[index]; + if (elem.instance) { + gc::PreWriteBarrier(elem.instance->objectUnbarriered()); + } + + if (!isAsmJS_) { + elem.code = code; + elem.instance = instance; + MOZ_ASSERT(elem.instance->objectUnbarriered()->isTenured(), + "no postWriteBarrier (Table::set)"); + } else { + elem.code = code; + elem.instance = nullptr; + } +} + +void Table::fillFuncRef(uint32_t index, uint32_t fillCount, FuncRef ref, + JSContext* cx) { + MOZ_ASSERT(isFunction()); + + if (ref.isNull()) { + for (uint32_t i = index, end = index + fillCount; i != end; i++) { + setNull(i); + } + return; + } + + RootedFunction fun(cx, ref.asJSFunction()); + MOZ_RELEASE_ASSERT(IsWasmExportedFunction(fun)); + + Rooted<WasmInstanceObject*> instanceObj( + cx, ExportedFunctionToInstanceObject(fun)); + uint32_t funcIndex = ExportedFunctionToFuncIndex(fun); + +#ifdef DEBUG + RootedFunction f(cx); + MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f)); + MOZ_ASSERT(fun == f); +#endif + + Instance& instance = instanceObj->instance(); + Tier tier = instance.code().bestTier(); + const MetadataTier& metadata = instance.metadata(tier); + const CodeRange& codeRange = + metadata.codeRange(metadata.lookupFuncExport(funcIndex)); + void* code = instance.codeBase(tier) + codeRange.funcCheckedCallEntry(); + for (uint32_t i = index, end = index + fillCount; i != end; i++) { + setFuncRef(i, code, &instance); + } +} + +AnyRef Table::getAnyRef(uint32_t index) const { + MOZ_ASSERT(!isFunction()); + return objects_[index]; +} + +void Table::setAnyRef(uint32_t index, AnyRef ref) { + MOZ_ASSERT(!isFunction()); + objects_[index] = ref; +} + +void Table::fillAnyRef(uint32_t index, uint32_t fillCount, AnyRef ref) { + MOZ_ASSERT(!isFunction()); + for (uint32_t i = index, end = index + fillCount; i != end; i++) { + objects_[i] = ref; + } +} + +void Table::setRef(uint32_t index, AnyRef ref) { + if (ref.isNull()) { + setNull(index); + } else if (isFunction()) { + JSFunction* func = &ref.toJSObject().as<JSFunction>(); + setFuncRef(index, func); + } else { + setAnyRef(index, ref); + } +} + +bool Table::getValue(JSContext* cx, uint32_t index, + MutableHandleValue result) const { + switch (repr()) { + case TableRepr::Func: { + MOZ_RELEASE_ASSERT(!isAsmJS()); + RootedFunction fun(cx); + if (!getFuncRef(cx, index, &fun)) { + return false; + } + result.setObjectOrNull(fun); + return true; + } + case TableRepr::Ref: { + if (!ValType(elemType_).isExposable()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_VAL_TYPE); + return false; + } + return ToJSValue(cx, &objects_[index], ValType(elemType_), result); + } + default: + MOZ_CRASH(); + } +} + +void Table::setNull(uint32_t index) { + switch (repr()) { + case TableRepr::Func: { + MOZ_RELEASE_ASSERT(!isAsmJS_); + FunctionTableElem& elem = functions_[index]; + if (elem.instance) { + gc::PreWriteBarrier(elem.instance->objectUnbarriered()); + } + + elem.code = nullptr; + elem.instance = nullptr; + break; + } + case TableRepr::Ref: { + setAnyRef(index, AnyRef::null()); + break; + } + } +} + +bool Table::copy(JSContext* cx, const Table& srcTable, uint32_t dstIndex, + uint32_t srcIndex) { + MOZ_RELEASE_ASSERT(!srcTable.isAsmJS_); + switch (repr()) { + case TableRepr::Func: { + MOZ_RELEASE_ASSERT(elemType().isFuncHierarchy() && + srcTable.elemType().isFuncHierarchy()); + FunctionTableElem& dst = functions_[dstIndex]; + if (dst.instance) { + gc::PreWriteBarrier(dst.instance->objectUnbarriered()); + } + + const FunctionTableElem& src = srcTable.functions_[srcIndex]; + dst.code = src.code; + dst.instance = src.instance; + + if (dst.instance) { + MOZ_ASSERT(dst.code); + MOZ_ASSERT(dst.instance->objectUnbarriered()->isTenured(), + "no postWriteBarrier (Table::copy)"); + } else { + MOZ_ASSERT(!dst.code); + } + break; + } + case TableRepr::Ref: { + switch (srcTable.repr()) { + case TableRepr::Ref: { + setAnyRef(dstIndex, srcTable.getAnyRef(srcIndex)); + break; + } + case TableRepr::Func: { + MOZ_RELEASE_ASSERT(srcTable.elemType().isFuncHierarchy()); + // Upcast. + RootedFunction fun(cx); + if (!srcTable.getFuncRef(cx, srcIndex, &fun)) { + // OOM, so just pass it on. + return false; + } + setAnyRef(dstIndex, AnyRef::fromJSObject(*fun)); + break; + } + } + break; + } + } + return true; +} + +uint32_t Table::grow(uint32_t delta) { + // This isn't just an optimization: movingGrowable() assumes that + // onMovingGrowTable does not fire when length == maximum. + if (!delta) { + return length_; + } + + uint32_t oldLength = length_; + + CheckedInt<uint32_t> newLength = oldLength; + newLength += delta; + if (!newLength.isValid() || newLength.value() > MaxTableLength) { + return -1; + } + + if (maximum_ && newLength.value() > maximum_.value()) { + return -1; + } + + MOZ_ASSERT(movingGrowable()); + + switch (repr()) { + case TableRepr::Func: { + MOZ_RELEASE_ASSERT(!isAsmJS_); + if (!functions_.resize(newLength.value())) { + return -1; + } + break; + } + case TableRepr::Ref: { + if (!objects_.resize(newLength.value())) { + return -1; + } + break; + } + } + + if (auto* object = maybeObject_.unbarrieredGet()) { + RemoveCellMemory(object, gcMallocBytes(), MemoryUse::WasmTableTable); + } + + length_ = newLength.value(); + + if (auto* object = maybeObject_.unbarrieredGet()) { + AddCellMemory(object, gcMallocBytes(), MemoryUse::WasmTableTable); + } + + for (InstanceSet::Range r = observers_.all(); !r.empty(); r.popFront()) { + r.front()->instance().onMovingGrowTable(this); + } + + return oldLength; +} + +bool Table::movingGrowable() const { + return !maximum_ || length_ < maximum_.value(); +} + +bool Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance) { + MOZ_ASSERT(movingGrowable()); + + // A table 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; +} + +void Table::fillUninitialized(uint32_t index, uint32_t fillCount, + HandleAnyRef ref, JSContext* cx) { +#ifdef DEBUG + assertRangeNull(index, fillCount); +#endif // DEBUG + switch (repr()) { + case TableRepr::Func: { + MOZ_RELEASE_ASSERT(!isAsmJS_); + fillFuncRef(index, fillCount, FuncRef::fromAnyRefUnchecked(ref), cx); + break; + } + case TableRepr::Ref: { + fillAnyRef(index, fillCount, ref); + break; + } + } +} + +#ifdef DEBUG +void Table::assertRangeNull(uint32_t index, uint32_t length) const { + switch (repr()) { + case TableRepr::Func: + for (uint32_t i = index; i < index + length; i++) { + MOZ_ASSERT(getFuncRef(i).instance == nullptr); + MOZ_ASSERT(getFuncRef(i).code == nullptr); + } + break; + case TableRepr::Ref: + for (uint32_t i = index; i < index + length; i++) { + MOZ_ASSERT(getAnyRef(i).isNull()); + } + break; + } +} + +void Table::assertRangeNotNull(uint32_t index, uint32_t length) const { + switch (repr()) { + case TableRepr::Func: + for (uint32_t i = index; i < index + length; i++) { + MOZ_ASSERT_IF(!isAsmJS_, getFuncRef(i).instance != nullptr); + MOZ_ASSERT(getFuncRef(i).code != nullptr); + } + break; + case TableRepr::Ref: + for (uint32_t i = index; i < index + length; i++) { + MOZ_ASSERT(!getAnyRef(i).isNull()); + } + break; + } +} +#endif // DEBUG + +size_t Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { + if (isFunction()) { + return functions_.sizeOfExcludingThis(mallocSizeOf); + } + return objects_.sizeOfExcludingThis(mallocSizeOf); +} + +size_t Table::gcMallocBytes() const { + size_t size = sizeof(*this); + if (isFunction()) { + size += length() * sizeof(FunctionTableElem); + } else { + size += length() * sizeof(TableAnyRefVector::ElementType); + } + return size; +} |