Backed out changeset 88fbb17e3c20 (bug 1865637) for causing animation related mochite...
[gecko.git] / js / src / wasm / WasmTable.cpp
blobb1b228293bc29107a45f0ded643b442a87962bb3
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
4 * Copyright 2016 Mozilla Foundation
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 #include "wasm/WasmTable.h"
21 #include "mozilla/CheckedInt.h"
22 #include "mozilla/PodOperations.h"
24 #include "vm/JSContext.h"
25 #include "vm/Realm.h"
26 #include "wasm/WasmInstance.h"
27 #include "wasm/WasmJS.h"
28 #include "wasm/WasmValue.h"
30 #include "gc/StableCellHasher-inl.h"
31 #include "wasm/WasmInstance-inl.h"
33 using namespace js;
34 using namespace js::wasm;
35 using mozilla::CheckedInt;
36 using mozilla::PodZero;
38 Table::Table(JSContext* cx, const TableDesc& desc,
39 Handle<WasmTableObject*> maybeObject, FuncRefVector&& functions)
40 : maybeObject_(maybeObject),
41 observers_(cx->zone()),
42 functions_(std::move(functions)),
43 elemType_(desc.elemType),
44 isAsmJS_(desc.isAsmJS),
45 length_(desc.initialLength),
46 maximum_(desc.maximumLength) {
47 // Acquire a strong reference to the type definition this table may be
48 // referencing.
49 elemType_.AddRef();
50 MOZ_ASSERT(repr() == TableRepr::Func);
53 Table::Table(JSContext* cx, const TableDesc& desc,
54 Handle<WasmTableObject*> maybeObject, TableAnyRefVector&& objects)
55 : maybeObject_(maybeObject),
56 observers_(cx->zone()),
57 objects_(std::move(objects)),
58 elemType_(desc.elemType),
59 isAsmJS_(desc.isAsmJS),
60 length_(desc.initialLength),
61 maximum_(desc.maximumLength) {
62 // Acquire a strong reference to the type definition this table may be
63 // referencing.
64 elemType_.AddRef();
65 MOZ_ASSERT(repr() == TableRepr::Ref);
68 Table::~Table() {
69 // Release the strong reference, if any.
70 elemType_.Release();
73 /* static */
74 SharedTable Table::create(JSContext* cx, const TableDesc& desc,
75 Handle<WasmTableObject*> maybeObject) {
76 // Tables are initialized with init_expr values at Instance::init or
77 // WasmTableObject::create.
79 switch (desc.elemType.tableRepr()) {
80 case TableRepr::Func: {
81 FuncRefVector functions;
82 if (!functions.resize(desc.initialLength)) {
83 ReportOutOfMemory(cx);
84 return nullptr;
86 return SharedTable(
87 cx->new_<Table>(cx, desc, maybeObject, std::move(functions)));
89 case TableRepr::Ref: {
90 TableAnyRefVector objects;
91 if (!objects.resize(desc.initialLength)) {
92 ReportOutOfMemory(cx);
93 return nullptr;
95 return SharedTable(
96 cx->new_<Table>(cx, desc, maybeObject, std::move(objects)));
99 MOZ_CRASH("switch is exhaustive");
102 void Table::tracePrivate(JSTracer* trc) {
103 // If this table has a WasmTableObject, then this method is only called by
104 // WasmTableObject's trace hook so maybeObject_ must already be marked.
105 // TraceEdge is called so that the pointer can be updated during a moving
106 // GC.
107 TraceNullableEdge(trc, &maybeObject_, "wasm table object");
109 switch (repr()) {
110 case TableRepr::Func: {
111 if (isAsmJS_) {
112 #ifdef DEBUG
113 for (uint32_t i = 0; i < length_; i++) {
114 MOZ_ASSERT(!functions_[i].instance);
116 #endif
117 break;
120 for (uint32_t i = 0; i < length_; i++) {
121 if (functions_[i].instance) {
122 wasm::TraceInstanceEdge(trc, functions_[i].instance,
123 "wasm table instance");
124 } else {
125 MOZ_ASSERT(!functions_[i].code);
128 break;
130 case TableRepr::Ref: {
131 objects_.trace(trc);
132 break;
137 void Table::trace(JSTracer* trc) {
138 // The trace hook of WasmTableObject will call Table::tracePrivate at
139 // which point we can mark the rest of the children. If there is no
140 // WasmTableObject, call Table::tracePrivate directly. Redirecting through
141 // the WasmTableObject avoids marking the entire Table on each incoming
142 // edge (once per dependent Instance).
143 if (maybeObject_) {
144 TraceEdge(trc, &maybeObject_, "wasm table object");
145 } else {
146 tracePrivate(trc);
150 uint8_t* Table::instanceElements() const {
151 if (repr() == TableRepr::Ref) {
152 return (uint8_t*)objects_.begin();
154 return (uint8_t*)functions_.begin();
157 const FunctionTableElem& Table::getFuncRef(uint32_t index) const {
158 MOZ_ASSERT(isFunction());
159 return functions_[index];
162 bool Table::getFuncRef(JSContext* cx, uint32_t index,
163 MutableHandleFunction fun) const {
164 MOZ_ASSERT(isFunction());
166 const FunctionTableElem& elem = getFuncRef(index);
167 if (!elem.code) {
168 fun.set(nullptr);
169 return true;
172 Instance& instance = *elem.instance;
173 const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code);
175 Rooted<WasmInstanceObject*> instanceObj(cx, instance.object());
176 return instanceObj->getExportedFunction(cx, instanceObj,
177 codeRange.funcIndex(), fun);
180 void Table::setFuncRef(uint32_t index, JSFunction* fun) {
181 MOZ_ASSERT(isFunction());
182 MOZ_ASSERT(fun->isWasm());
184 // Tables can store references to wasm functions from other instances. To
185 // preserve the === function identity required by the JS embedding spec, we
186 // must set the element to the function's underlying
187 // CodeRange.funcCheckedCallEntry and Instance so that Table.get()s always
188 // produce the same function object as was imported.
189 WasmInstanceObject* instanceObj = ExportedFunctionToInstanceObject(fun);
190 Instance& instance = instanceObj->instance();
191 Tier tier = instance.code().bestTier();
192 const CodeRange& calleeCodeRange =
193 instanceObj->getExportedFunctionCodeRange(fun, tier);
194 void* code = instance.codeBase(tier) + calleeCodeRange.funcCheckedCallEntry();
195 setFuncRef(index, code, &instance);
198 void Table::setFuncRef(uint32_t index, void* code, Instance* instance) {
199 MOZ_ASSERT(isFunction());
201 FunctionTableElem& elem = functions_[index];
202 if (elem.instance) {
203 gc::PreWriteBarrier(elem.instance->objectUnbarriered());
206 if (!isAsmJS_) {
207 elem.code = code;
208 elem.instance = instance;
209 MOZ_ASSERT(elem.instance->objectUnbarriered()->isTenured(),
210 "no postWriteBarrier (Table::set)");
211 } else {
212 elem.code = code;
213 elem.instance = nullptr;
217 void Table::fillFuncRef(uint32_t index, uint32_t fillCount, FuncRef ref,
218 JSContext* cx) {
219 MOZ_ASSERT(isFunction());
221 if (ref.isNull()) {
222 for (uint32_t i = index, end = index + fillCount; i != end; i++) {
223 setNull(i);
225 return;
228 RootedFunction fun(cx, ref.asJSFunction());
229 MOZ_RELEASE_ASSERT(IsWasmExportedFunction(fun));
231 Rooted<WasmInstanceObject*> instanceObj(
232 cx, ExportedFunctionToInstanceObject(fun));
233 uint32_t funcIndex = ExportedFunctionToFuncIndex(fun);
235 #ifdef DEBUG
236 RootedFunction f(cx);
237 MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
238 MOZ_ASSERT(fun == f);
239 #endif
241 Instance& instance = instanceObj->instance();
242 Tier tier = instance.code().bestTier();
243 const MetadataTier& metadata = instance.metadata(tier);
244 const CodeRange& codeRange =
245 metadata.codeRange(metadata.lookupFuncExport(funcIndex));
246 void* code = instance.codeBase(tier) + codeRange.funcCheckedCallEntry();
247 for (uint32_t i = index, end = index + fillCount; i != end; i++) {
248 setFuncRef(i, code, &instance);
252 AnyRef Table::getAnyRef(uint32_t index) const {
253 MOZ_ASSERT(!isFunction());
254 return objects_[index];
257 void Table::setAnyRef(uint32_t index, AnyRef ref) {
258 MOZ_ASSERT(!isFunction());
259 objects_[index] = ref;
262 void Table::fillAnyRef(uint32_t index, uint32_t fillCount, AnyRef ref) {
263 MOZ_ASSERT(!isFunction());
264 for (uint32_t i = index, end = index + fillCount; i != end; i++) {
265 objects_[i] = ref;
269 void Table::setRef(uint32_t index, AnyRef ref) {
270 if (ref.isNull()) {
271 setNull(index);
272 } else if (isFunction()) {
273 JSFunction* func = &ref.toJSObject().as<JSFunction>();
274 setFuncRef(index, func);
275 } else {
276 setAnyRef(index, ref);
280 bool Table::getValue(JSContext* cx, uint32_t index,
281 MutableHandleValue result) const {
282 switch (repr()) {
283 case TableRepr::Func: {
284 MOZ_RELEASE_ASSERT(!isAsmJS());
285 RootedFunction fun(cx);
286 if (!getFuncRef(cx, index, &fun)) {
287 return false;
289 result.setObjectOrNull(fun);
290 return true;
292 case TableRepr::Ref: {
293 if (!ValType(elemType_).isExposable()) {
294 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
295 JSMSG_WASM_BAD_VAL_TYPE);
296 return false;
298 return ToJSValue(cx, &objects_[index], ValType(elemType_), result);
300 default:
301 MOZ_CRASH();
305 void Table::setNull(uint32_t index) {
306 switch (repr()) {
307 case TableRepr::Func: {
308 MOZ_RELEASE_ASSERT(!isAsmJS_);
309 FunctionTableElem& elem = functions_[index];
310 if (elem.instance) {
311 gc::PreWriteBarrier(elem.instance->objectUnbarriered());
314 elem.code = nullptr;
315 elem.instance = nullptr;
316 break;
318 case TableRepr::Ref: {
319 setAnyRef(index, AnyRef::null());
320 break;
325 bool Table::copy(JSContext* cx, const Table& srcTable, uint32_t dstIndex,
326 uint32_t srcIndex) {
327 MOZ_RELEASE_ASSERT(!srcTable.isAsmJS_);
328 switch (repr()) {
329 case TableRepr::Func: {
330 MOZ_RELEASE_ASSERT(elemType().isFuncHierarchy() &&
331 srcTable.elemType().isFuncHierarchy());
332 FunctionTableElem& dst = functions_[dstIndex];
333 if (dst.instance) {
334 gc::PreWriteBarrier(dst.instance->objectUnbarriered());
337 const FunctionTableElem& src = srcTable.functions_[srcIndex];
338 dst.code = src.code;
339 dst.instance = src.instance;
341 if (dst.instance) {
342 MOZ_ASSERT(dst.code);
343 MOZ_ASSERT(dst.instance->objectUnbarriered()->isTenured(),
344 "no postWriteBarrier (Table::copy)");
345 } else {
346 MOZ_ASSERT(!dst.code);
348 break;
350 case TableRepr::Ref: {
351 switch (srcTable.repr()) {
352 case TableRepr::Ref: {
353 setAnyRef(dstIndex, srcTable.getAnyRef(srcIndex));
354 break;
356 case TableRepr::Func: {
357 MOZ_RELEASE_ASSERT(srcTable.elemType().isFuncHierarchy());
358 // Upcast.
359 RootedFunction fun(cx);
360 if (!srcTable.getFuncRef(cx, srcIndex, &fun)) {
361 // OOM, so just pass it on.
362 return false;
364 setAnyRef(dstIndex, AnyRef::fromJSObject(*fun));
365 break;
368 break;
371 return true;
374 uint32_t Table::grow(uint32_t delta) {
375 // This isn't just an optimization: movingGrowable() assumes that
376 // onMovingGrowTable does not fire when length == maximum.
377 if (!delta) {
378 return length_;
381 uint32_t oldLength = length_;
383 CheckedInt<uint32_t> newLength = oldLength;
384 newLength += delta;
385 if (!newLength.isValid() || newLength.value() > MaxTableLength) {
386 return -1;
389 if (maximum_ && newLength.value() > maximum_.value()) {
390 return -1;
393 MOZ_ASSERT(movingGrowable());
395 switch (repr()) {
396 case TableRepr::Func: {
397 MOZ_RELEASE_ASSERT(!isAsmJS_);
398 if (!functions_.resize(newLength.value())) {
399 return -1;
401 break;
403 case TableRepr::Ref: {
404 if (!objects_.resize(newLength.value())) {
405 return -1;
407 break;
411 if (auto* object = maybeObject_.unbarrieredGet()) {
412 RemoveCellMemory(object, gcMallocBytes(), MemoryUse::WasmTableTable);
415 length_ = newLength.value();
417 if (auto* object = maybeObject_.unbarrieredGet()) {
418 AddCellMemory(object, gcMallocBytes(), MemoryUse::WasmTableTable);
421 for (InstanceSet::Range r = observers_.all(); !r.empty(); r.popFront()) {
422 r.front()->instance().onMovingGrowTable(this);
425 return oldLength;
428 bool Table::movingGrowable() const {
429 return !maximum_ || length_ < maximum_.value();
432 bool Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance) {
433 MOZ_ASSERT(movingGrowable());
435 // A table can be imported multiple times into an instance, but we only
436 // register the instance as an observer once.
438 if (!observers_.put(instance)) {
439 ReportOutOfMemory(cx);
440 return false;
443 return true;
446 void Table::fillUninitialized(uint32_t index, uint32_t fillCount,
447 HandleAnyRef ref, JSContext* cx) {
448 #ifdef DEBUG
449 assertRangeNull(index, fillCount);
450 #endif // DEBUG
451 switch (repr()) {
452 case TableRepr::Func: {
453 MOZ_RELEASE_ASSERT(!isAsmJS_);
454 fillFuncRef(index, fillCount, FuncRef::fromAnyRefUnchecked(ref), cx);
455 break;
457 case TableRepr::Ref: {
458 fillAnyRef(index, fillCount, ref);
459 break;
464 #ifdef DEBUG
465 void Table::assertRangeNull(uint32_t index, uint32_t length) const {
466 switch (repr()) {
467 case TableRepr::Func:
468 for (uint32_t i = index; i < index + length; i++) {
469 MOZ_ASSERT(getFuncRef(i).instance == nullptr);
470 MOZ_ASSERT(getFuncRef(i).code == nullptr);
472 break;
473 case TableRepr::Ref:
474 for (uint32_t i = index; i < index + length; i++) {
475 MOZ_ASSERT(getAnyRef(i).isNull());
477 break;
481 void Table::assertRangeNotNull(uint32_t index, uint32_t length) const {
482 switch (repr()) {
483 case TableRepr::Func:
484 for (uint32_t i = index; i < index + length; i++) {
485 MOZ_ASSERT_IF(!isAsmJS_, getFuncRef(i).instance != nullptr);
486 MOZ_ASSERT(getFuncRef(i).code != nullptr);
488 break;
489 case TableRepr::Ref:
490 for (uint32_t i = index; i < index + length; i++) {
491 MOZ_ASSERT(!getAnyRef(i).isNull());
493 break;
496 #endif // DEBUG
498 size_t Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
499 if (isFunction()) {
500 return functions_.sizeOfExcludingThis(mallocSizeOf);
502 return objects_.sizeOfExcludingThis(mallocSizeOf);
505 size_t Table::gcMallocBytes() const {
506 size_t size = sizeof(*this);
507 if (isFunction()) {
508 size += length() * sizeof(FunctionTableElem);
509 } else {
510 size += length() * sizeof(TableAnyRefVector::ElementType);
512 return size;