summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmTable.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/wasm/WasmTable.cpp513
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;
+}