summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmTable.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/wasm/WasmTable.cpp401
1 files changed, 401 insertions, 0 deletions
diff --git a/js/src/wasm/WasmTable.cpp b/js/src/wasm/WasmTable.cpp
new file mode 100644
index 0000000000..e910140496
--- /dev/null
+++ b/js/src/wasm/WasmTable.cpp
@@ -0,0 +1,401 @@
+/* -*- 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 "vm/JSContext.h"
+#include "vm/Realm.h"
+#include "wasm/WasmInstance.h"
+#include "wasm/WasmJS.h"
+
+using namespace js;
+using namespace js::wasm;
+using mozilla::CheckedInt;
+
+Table::Table(JSContext* cx, const TableDesc& desc,
+ HandleWasmTableObject maybeObject, UniqueFuncRefArray functions)
+ : maybeObject_(maybeObject),
+ observers_(cx->zone()),
+ functions_(std::move(functions)),
+ elemType_(desc.elemType),
+ isAsmJS_(desc.isAsmJS),
+ length_(desc.initialLength),
+ maximum_(desc.maximumLength) {
+ MOZ_ASSERT(repr() == TableRepr::Func);
+}
+
+Table::Table(JSContext* cx, const TableDesc& desc,
+ HandleWasmTableObject maybeObject, TableAnyRefVector&& objects)
+ : maybeObject_(maybeObject),
+ observers_(cx->zone()),
+ objects_(std::move(objects)),
+ elemType_(desc.elemType),
+ isAsmJS_(desc.isAsmJS),
+ length_(desc.initialLength),
+ maximum_(desc.maximumLength) {
+ MOZ_ASSERT(repr() == TableRepr::Ref);
+}
+
+/* static */
+SharedTable Table::create(JSContext* cx, const TableDesc& desc,
+ HandleWasmTableObject maybeObject) {
+ // We don't support non-nullable references in tables yet.
+ MOZ_RELEASE_ASSERT(desc.elemType.isNullable());
+
+ switch (desc.elemType.tableRepr()) {
+ case TableRepr::Func: {
+ UniqueFuncRefArray functions(
+ cx->pod_calloc<FunctionTableElem>(desc.initialLength));
+ if (!functions) {
+ return nullptr;
+ }
+ return SharedTable(
+ cx->new_<Table>(cx, desc, maybeObject, std::move(functions)));
+ }
+ case TableRepr::Ref: {
+ TableAnyRefVector objects;
+ if (!objects.resize(desc.initialLength)) {
+ return nullptr;
+ }
+ return SharedTable(
+ cx->new_<Table>(cx, desc, maybeObject, std::move(objects)));
+ }
+ }
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("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.
+ if (maybeObject_) {
+ MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_));
+ TraceEdge(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].tls);
+ }
+#endif
+ break;
+ }
+
+ for (uint32_t i = 0; i < length_; i++) {
+ if (functions_[i].tls) {
+ functions_[i].tls->instance->trace(trc);
+ } 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::functionBase() const {
+ if (repr() == TableRepr::Ref) {
+ return nullptr;
+ }
+ return (uint8_t*)functions_.get();
+}
+
+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.tls->instance;
+ const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code);
+
+ RootedWasmInstanceObject instanceObj(cx, instance.object());
+ return instanceObj->getExportedFunction(cx, instanceObj,
+ codeRange.funcIndex(), fun);
+}
+
+void Table::setFuncRef(uint32_t index, void* code, const Instance* instance) {
+ MOZ_ASSERT(isFunction());
+
+ FunctionTableElem& elem = functions_[index];
+ if (elem.tls) {
+ gc::PreWriteBarrier(elem.tls->instance->objectUnbarriered());
+ }
+
+ if (!isAsmJS_) {
+ elem.code = code;
+ elem.tls = instance->tlsData();
+ MOZ_ASSERT(elem.tls->instance->objectUnbarriered()->isTenured(),
+ "no postWriteBarrier (Table::set)");
+ } else {
+ elem.code = code;
+ elem.tls = 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));
+
+ RootedWasmInstanceObject 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());
+ // TODO/AnyRef-boxing: With boxed immediates and strings, the write barrier
+ // is going to have to be more complicated.
+ ASSERT_ANYREF_IS_JSOBJECT;
+ return AnyRef::fromJSObject(objects_[index]);
+}
+
+void Table::fillAnyRef(uint32_t index, uint32_t fillCount, AnyRef ref) {
+ MOZ_ASSERT(!isFunction());
+ // TODO/AnyRef-boxing: With boxed immediates and strings, the write barrier
+ // is going to have to be more complicated.
+ ASSERT_ANYREF_IS_JSOBJECT;
+ for (uint32_t i = index, end = index + fillCount; i != end; i++) {
+ objects_[i] = ref.asJSObject();
+ }
+}
+
+void Table::setNull(uint32_t index) {
+ switch (repr()) {
+ case TableRepr::Func: {
+ MOZ_RELEASE_ASSERT(!isAsmJS_);
+ FunctionTableElem& elem = functions_[index];
+ if (elem.tls) {
+ gc::PreWriteBarrier(elem.tls->instance->objectUnbarriered());
+ }
+
+ elem.code = nullptr;
+ elem.tls = nullptr;
+ break;
+ }
+ case TableRepr::Ref: {
+ fillAnyRef(index, 1, AnyRef::null());
+ break;
+ }
+ }
+}
+
+bool Table::copy(const Table& srcTable, uint32_t dstIndex, uint32_t srcIndex) {
+ MOZ_RELEASE_ASSERT(!srcTable.isAsmJS_);
+ switch (repr()) {
+ case TableRepr::Func: {
+ MOZ_RELEASE_ASSERT(elemType().isFunc() && srcTable.elemType().isFunc());
+ FunctionTableElem& dst = functions_[dstIndex];
+ if (dst.tls) {
+ gc::PreWriteBarrier(dst.tls->instance->objectUnbarriered());
+ }
+
+ FunctionTableElem& src = srcTable.functions_[srcIndex];
+ dst.code = src.code;
+ dst.tls = src.tls;
+
+ if (dst.tls) {
+ MOZ_ASSERT(dst.code);
+ MOZ_ASSERT(dst.tls->instance->objectUnbarriered()->isTenured(),
+ "no postWriteBarrier (Table::copy)");
+ } else {
+ MOZ_ASSERT(!dst.code);
+ }
+ break;
+ }
+ case TableRepr::Ref: {
+ switch (srcTable.repr()) {
+ case TableRepr::Ref: {
+ fillAnyRef(dstIndex, 1, srcTable.getAnyRef(srcIndex));
+ break;
+ }
+ case TableRepr::Func: {
+ MOZ_RELEASE_ASSERT(srcTable.elemType().isFunc());
+ // Upcast. Possibly suboptimal to grab the cx here for every iteration
+ // of the outer copy loop.
+ JSContext* cx = TlsContext.get();
+ RootedFunction fun(cx);
+ if (!srcTable.getFuncRef(cx, srcIndex, &fun)) {
+ // OOM, so just pass it on.
+ return false;
+ }
+ fillAnyRef(dstIndex, 1, 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_);
+ // Note that realloc does not release functions_'s pointee on failure
+ // which is exactly what we need here.
+ FunctionTableElem* newFunctions = js_pod_realloc<FunctionTableElem>(
+ functions_.get(), length_, newLength.value());
+ if (!newFunctions) {
+ return -1;
+ }
+ Unused << functions_.release();
+ functions_.reset(newFunctions);
+
+ // Realloc does not zero the delta for us.
+ PodZero(newFunctions + length_, delta);
+ 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;
+}
+
+size_t Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
+ if (isFunction()) {
+ return mallocSizeOf(functions_.get());
+ }
+ 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;
+}