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"
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"
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
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
65 MOZ_ASSERT(repr() == TableRepr::Ref
);
69 // Release the strong reference, if any.
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
);
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
);
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
107 TraceNullableEdge(trc
, &maybeObject_
, "wasm table object");
110 case TableRepr::Func
: {
113 for (uint32_t i
= 0; i
< length_
; i
++) {
114 MOZ_ASSERT(!functions_
[i
].instance
);
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");
125 MOZ_ASSERT(!functions_
[i
].code
);
130 case TableRepr::Ref
: {
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).
144 TraceEdge(trc
, &maybeObject_
, "wasm table object");
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
);
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
];
203 gc::PreWriteBarrier(elem
.instance
->objectUnbarriered());
208 elem
.instance
= instance
;
209 MOZ_ASSERT(elem
.instance
->objectUnbarriered()->isTenured(),
210 "no postWriteBarrier (Table::set)");
213 elem
.instance
= nullptr;
217 void Table::fillFuncRef(uint32_t index
, uint32_t fillCount
, FuncRef ref
,
219 MOZ_ASSERT(isFunction());
222 for (uint32_t i
= index
, end
= index
+ fillCount
; i
!= end
; i
++) {
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
);
236 RootedFunction
f(cx
);
237 MOZ_ASSERT(instanceObj
->getExportedFunction(cx
, instanceObj
, funcIndex
, &f
));
238 MOZ_ASSERT(fun
== f
);
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
++) {
269 void Table::setRef(uint32_t index
, AnyRef ref
) {
272 } else if (isFunction()) {
273 JSFunction
* func
= &ref
.toJSObject().as
<JSFunction
>();
274 setFuncRef(index
, func
);
276 setAnyRef(index
, ref
);
280 bool Table::getValue(JSContext
* cx
, uint32_t index
,
281 MutableHandleValue result
) const {
283 case TableRepr::Func
: {
284 MOZ_RELEASE_ASSERT(!isAsmJS());
285 RootedFunction
fun(cx
);
286 if (!getFuncRef(cx
, index
, &fun
)) {
289 result
.setObjectOrNull(fun
);
292 case TableRepr::Ref
: {
293 if (!ValType(elemType_
).isExposable()) {
294 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
295 JSMSG_WASM_BAD_VAL_TYPE
);
298 return ToJSValue(cx
, &objects_
[index
], ValType(elemType_
), result
);
305 void Table::setNull(uint32_t index
) {
307 case TableRepr::Func
: {
308 MOZ_RELEASE_ASSERT(!isAsmJS_
);
309 FunctionTableElem
& elem
= functions_
[index
];
311 gc::PreWriteBarrier(elem
.instance
->objectUnbarriered());
315 elem
.instance
= nullptr;
318 case TableRepr::Ref
: {
319 setAnyRef(index
, AnyRef::null());
325 bool Table::copy(JSContext
* cx
, const Table
& srcTable
, uint32_t dstIndex
,
327 MOZ_RELEASE_ASSERT(!srcTable
.isAsmJS_
);
329 case TableRepr::Func
: {
330 MOZ_RELEASE_ASSERT(elemType().isFuncHierarchy() &&
331 srcTable
.elemType().isFuncHierarchy());
332 FunctionTableElem
& dst
= functions_
[dstIndex
];
334 gc::PreWriteBarrier(dst
.instance
->objectUnbarriered());
337 const FunctionTableElem
& src
= srcTable
.functions_
[srcIndex
];
339 dst
.instance
= src
.instance
;
342 MOZ_ASSERT(dst
.code
);
343 MOZ_ASSERT(dst
.instance
->objectUnbarriered()->isTenured(),
344 "no postWriteBarrier (Table::copy)");
346 MOZ_ASSERT(!dst
.code
);
350 case TableRepr::Ref
: {
351 switch (srcTable
.repr()) {
352 case TableRepr::Ref
: {
353 setAnyRef(dstIndex
, srcTable
.getAnyRef(srcIndex
));
356 case TableRepr::Func
: {
357 MOZ_RELEASE_ASSERT(srcTable
.elemType().isFuncHierarchy());
359 RootedFunction
fun(cx
);
360 if (!srcTable
.getFuncRef(cx
, srcIndex
, &fun
)) {
361 // OOM, so just pass it on.
364 setAnyRef(dstIndex
, AnyRef::fromJSObject(*fun
));
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.
381 uint32_t oldLength
= length_
;
383 CheckedInt
<uint32_t> newLength
= oldLength
;
385 if (!newLength
.isValid() || newLength
.value() > MaxTableLength
) {
389 if (maximum_
&& newLength
.value() > maximum_
.value()) {
393 MOZ_ASSERT(movingGrowable());
396 case TableRepr::Func
: {
397 MOZ_RELEASE_ASSERT(!isAsmJS_
);
398 if (!functions_
.resize(newLength
.value())) {
403 case TableRepr::Ref
: {
404 if (!objects_
.resize(newLength
.value())) {
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);
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
);
446 void Table::fillUninitialized(uint32_t index
, uint32_t fillCount
,
447 HandleAnyRef ref
, JSContext
* cx
) {
449 assertRangeNull(index
, fillCount
);
452 case TableRepr::Func
: {
453 MOZ_RELEASE_ASSERT(!isAsmJS_
);
454 fillFuncRef(index
, fillCount
, FuncRef::fromAnyRefUnchecked(ref
), cx
);
457 case TableRepr::Ref
: {
458 fillAnyRef(index
, fillCount
, ref
);
465 void Table::assertRangeNull(uint32_t index
, uint32_t length
) const {
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);
474 for (uint32_t i
= index
; i
< index
+ length
; i
++) {
475 MOZ_ASSERT(getAnyRef(i
).isNull());
481 void Table::assertRangeNotNull(uint32_t index
, uint32_t length
) const {
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);
490 for (uint32_t i
= index
; i
< index
+ length
; i
++) {
491 MOZ_ASSERT(!getAnyRef(i
).isNull());
498 size_t Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf
) const {
500 return functions_
.sizeOfExcludingThis(mallocSizeOf
);
502 return objects_
.sizeOfExcludingThis(mallocSizeOf
);
505 size_t Table::gcMallocBytes() const {
506 size_t size
= sizeof(*this);
508 size
+= length() * sizeof(FunctionTableElem
);
510 size
+= length() * sizeof(TableAnyRefVector::ElementType
);