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:
3 *
4 * Copyright 2016 Mozilla Foundation
5 *
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
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
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.
17 */
18
19 #include "wasm/WasmTable.h"
20
21 #include "mozilla/CheckedInt.h"
22
23 #include "vm/JSContext.h"
24 #include "vm/Realm.h"
25 #include "wasm/WasmInstance.h"
26 #include "wasm/WasmJS.h"
27
28 using namespace js;
29 using namespace js::wasm;
30 using mozilla::CheckedInt;
31
Table(JSContext * cx,const TableDesc & desc,HandleWasmTableObject maybeObject,UniqueFuncRefArray functions)32 Table::Table(JSContext* cx, const TableDesc& desc,
33 HandleWasmTableObject maybeObject, UniqueFuncRefArray functions)
34 : maybeObject_(maybeObject),
35 observers_(cx->zone()),
36 functions_(std::move(functions)),
37 elemType_(desc.elemType),
38 isAsmJS_(desc.isAsmJS),
39 length_(desc.initialLength),
40 maximum_(desc.maximumLength) {
41 MOZ_ASSERT(repr() == TableRepr::Func);
42 }
43
Table(JSContext * cx,const TableDesc & desc,HandleWasmTableObject maybeObject,TableAnyRefVector && objects)44 Table::Table(JSContext* cx, const TableDesc& desc,
45 HandleWasmTableObject maybeObject, TableAnyRefVector&& objects)
46 : maybeObject_(maybeObject),
47 observers_(cx->zone()),
48 objects_(std::move(objects)),
49 elemType_(desc.elemType),
50 isAsmJS_(desc.isAsmJS),
51 length_(desc.initialLength),
52 maximum_(desc.maximumLength) {
53 MOZ_ASSERT(repr() == TableRepr::Ref);
54 }
55
56 /* static */
create(JSContext * cx,const TableDesc & desc,HandleWasmTableObject maybeObject)57 SharedTable Table::create(JSContext* cx, const TableDesc& desc,
58 HandleWasmTableObject maybeObject) {
59 // We don't support non-nullable references in tables yet.
60 MOZ_RELEASE_ASSERT(desc.elemType.isNullable());
61
62 switch (desc.elemType.tableRepr()) {
63 case TableRepr::Func: {
64 UniqueFuncRefArray functions(
65 cx->pod_calloc<FunctionTableElem>(desc.initialLength));
66 if (!functions) {
67 return nullptr;
68 }
69 return SharedTable(
70 cx->new_<Table>(cx, desc, maybeObject, std::move(functions)));
71 }
72 case TableRepr::Ref: {
73 TableAnyRefVector objects;
74 if (!objects.resize(desc.initialLength)) {
75 return nullptr;
76 }
77 return SharedTable(
78 cx->new_<Table>(cx, desc, maybeObject, std::move(objects)));
79 }
80 }
81 MOZ_CRASH("switch is exhaustive");
82 }
83
tracePrivate(JSTracer * trc)84 void Table::tracePrivate(JSTracer* trc) {
85 // If this table has a WasmTableObject, then this method is only called by
86 // WasmTableObject's trace hook so maybeObject_ must already be marked.
87 // TraceEdge is called so that the pointer can be updated during a moving
88 // GC.
89 if (maybeObject_) {
90 MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_));
91 TraceEdge(trc, &maybeObject_, "wasm table object");
92 }
93
94 switch (repr()) {
95 case TableRepr::Func: {
96 if (isAsmJS_) {
97 #ifdef DEBUG
98 for (uint32_t i = 0; i < length_; i++) {
99 MOZ_ASSERT(!functions_[i].tls);
100 }
101 #endif
102 break;
103 }
104
105 for (uint32_t i = 0; i < length_; i++) {
106 if (functions_[i].tls) {
107 functions_[i].tls->instance->trace(trc);
108 } else {
109 MOZ_ASSERT(!functions_[i].code);
110 }
111 }
112 break;
113 }
114 case TableRepr::Ref: {
115 objects_.trace(trc);
116 break;
117 }
118 }
119 }
120
trace(JSTracer * trc)121 void Table::trace(JSTracer* trc) {
122 // The trace hook of WasmTableObject will call Table::tracePrivate at
123 // which point we can mark the rest of the children. If there is no
124 // WasmTableObject, call Table::tracePrivate directly. Redirecting through
125 // the WasmTableObject avoids marking the entire Table on each incoming
126 // edge (once per dependent Instance).
127 if (maybeObject_) {
128 TraceEdge(trc, &maybeObject_, "wasm table object");
129 } else {
130 tracePrivate(trc);
131 }
132 }
133
functionBase() const134 uint8_t* Table::functionBase() const {
135 if (repr() == TableRepr::Ref) {
136 return nullptr;
137 }
138 return (uint8_t*)functions_.get();
139 }
140
getFuncRef(uint32_t index) const141 const FunctionTableElem& Table::getFuncRef(uint32_t index) const {
142 MOZ_ASSERT(isFunction());
143 return functions_[index];
144 }
145
getFuncRef(JSContext * cx,uint32_t index,MutableHandleFunction fun) const146 bool Table::getFuncRef(JSContext* cx, uint32_t index,
147 MutableHandleFunction fun) const {
148 MOZ_ASSERT(isFunction());
149
150 const FunctionTableElem& elem = getFuncRef(index);
151 if (!elem.code) {
152 fun.set(nullptr);
153 return true;
154 }
155
156 Instance& instance = *elem.tls->instance;
157 const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code);
158
159 RootedWasmInstanceObject instanceObj(cx, instance.object());
160 return instanceObj->getExportedFunction(cx, instanceObj,
161 codeRange.funcIndex(), fun);
162 }
163
setFuncRef(uint32_t index,void * code,const Instance * instance)164 void Table::setFuncRef(uint32_t index, void* code, const Instance* instance) {
165 MOZ_ASSERT(isFunction());
166
167 FunctionTableElem& elem = functions_[index];
168 if (elem.tls) {
169 gc::PreWriteBarrier(elem.tls->instance->objectUnbarriered());
170 }
171
172 if (!isAsmJS_) {
173 elem.code = code;
174 elem.tls = instance->tlsData();
175 MOZ_ASSERT(elem.tls->instance->objectUnbarriered()->isTenured(),
176 "no postWriteBarrier (Table::set)");
177 } else {
178 elem.code = code;
179 elem.tls = nullptr;
180 }
181 }
182
fillFuncRef(uint32_t index,uint32_t fillCount,FuncRef ref,JSContext * cx)183 void Table::fillFuncRef(uint32_t index, uint32_t fillCount, FuncRef ref,
184 JSContext* cx) {
185 MOZ_ASSERT(isFunction());
186
187 if (ref.isNull()) {
188 for (uint32_t i = index, end = index + fillCount; i != end; i++) {
189 setNull(i);
190 }
191 return;
192 }
193
194 RootedFunction fun(cx, ref.asJSFunction());
195 MOZ_RELEASE_ASSERT(IsWasmExportedFunction(fun));
196
197 RootedWasmInstanceObject instanceObj(cx,
198 ExportedFunctionToInstanceObject(fun));
199 uint32_t funcIndex = ExportedFunctionToFuncIndex(fun);
200
201 #ifdef DEBUG
202 RootedFunction f(cx);
203 MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
204 MOZ_ASSERT(fun == f);
205 #endif
206
207 Instance& instance = instanceObj->instance();
208 Tier tier = instance.code().bestTier();
209 const MetadataTier& metadata = instance.metadata(tier);
210 const CodeRange& codeRange =
211 metadata.codeRange(metadata.lookupFuncExport(funcIndex));
212 void* code = instance.codeBase(tier) + codeRange.funcCheckedCallEntry();
213 for (uint32_t i = index, end = index + fillCount; i != end; i++) {
214 setFuncRef(i, code, &instance);
215 }
216 }
217
getAnyRef(uint32_t index) const218 AnyRef Table::getAnyRef(uint32_t index) const {
219 MOZ_ASSERT(!isFunction());
220 // TODO/AnyRef-boxing: With boxed immediates and strings, the write barrier
221 // is going to have to be more complicated.
222 ASSERT_ANYREF_IS_JSOBJECT;
223 return AnyRef::fromJSObject(objects_[index]);
224 }
225
fillAnyRef(uint32_t index,uint32_t fillCount,AnyRef ref)226 void Table::fillAnyRef(uint32_t index, uint32_t fillCount, AnyRef ref) {
227 MOZ_ASSERT(!isFunction());
228 // TODO/AnyRef-boxing: With boxed immediates and strings, the write barrier
229 // is going to have to be more complicated.
230 ASSERT_ANYREF_IS_JSOBJECT;
231 for (uint32_t i = index, end = index + fillCount; i != end; i++) {
232 objects_[i] = ref.asJSObject();
233 }
234 }
235
setNull(uint32_t index)236 void Table::setNull(uint32_t index) {
237 switch (repr()) {
238 case TableRepr::Func: {
239 MOZ_RELEASE_ASSERT(!isAsmJS_);
240 FunctionTableElem& elem = functions_[index];
241 if (elem.tls) {
242 gc::PreWriteBarrier(elem.tls->instance->objectUnbarriered());
243 }
244
245 elem.code = nullptr;
246 elem.tls = nullptr;
247 break;
248 }
249 case TableRepr::Ref: {
250 fillAnyRef(index, 1, AnyRef::null());
251 break;
252 }
253 }
254 }
255
copy(const Table & srcTable,uint32_t dstIndex,uint32_t srcIndex)256 bool Table::copy(const Table& srcTable, uint32_t dstIndex, uint32_t srcIndex) {
257 MOZ_RELEASE_ASSERT(!srcTable.isAsmJS_);
258 switch (repr()) {
259 case TableRepr::Func: {
260 MOZ_RELEASE_ASSERT(elemType().isFunc() && srcTable.elemType().isFunc());
261 FunctionTableElem& dst = functions_[dstIndex];
262 if (dst.tls) {
263 gc::PreWriteBarrier(dst.tls->instance->objectUnbarriered());
264 }
265
266 FunctionTableElem& src = srcTable.functions_[srcIndex];
267 dst.code = src.code;
268 dst.tls = src.tls;
269
270 if (dst.tls) {
271 MOZ_ASSERT(dst.code);
272 MOZ_ASSERT(dst.tls->instance->objectUnbarriered()->isTenured(),
273 "no postWriteBarrier (Table::copy)");
274 } else {
275 MOZ_ASSERT(!dst.code);
276 }
277 break;
278 }
279 case TableRepr::Ref: {
280 switch (srcTable.repr()) {
281 case TableRepr::Ref: {
282 fillAnyRef(dstIndex, 1, srcTable.getAnyRef(srcIndex));
283 break;
284 }
285 case TableRepr::Func: {
286 MOZ_RELEASE_ASSERT(srcTable.elemType().isFunc());
287 // Upcast. Possibly suboptimal to grab the cx here for every iteration
288 // of the outer copy loop.
289 JSContext* cx = TlsContext.get();
290 RootedFunction fun(cx);
291 if (!srcTable.getFuncRef(cx, srcIndex, &fun)) {
292 // OOM, so just pass it on.
293 return false;
294 }
295 fillAnyRef(dstIndex, 1, AnyRef::fromJSObject(fun));
296 break;
297 }
298 }
299 break;
300 }
301 }
302 return true;
303 }
304
grow(uint32_t delta)305 uint32_t Table::grow(uint32_t delta) {
306 // This isn't just an optimization: movingGrowable() assumes that
307 // onMovingGrowTable does not fire when length == maximum.
308 if (!delta) {
309 return length_;
310 }
311
312 uint32_t oldLength = length_;
313
314 CheckedInt<uint32_t> newLength = oldLength;
315 newLength += delta;
316 if (!newLength.isValid() || newLength.value() > MaxTableLength) {
317 return -1;
318 }
319
320 if (maximum_ && newLength.value() > maximum_.value()) {
321 return -1;
322 }
323
324 MOZ_ASSERT(movingGrowable());
325
326 switch (repr()) {
327 case TableRepr::Func: {
328 MOZ_RELEASE_ASSERT(!isAsmJS_);
329 // Note that realloc does not release functions_'s pointee on failure
330 // which is exactly what we need here.
331 FunctionTableElem* newFunctions = js_pod_realloc<FunctionTableElem>(
332 functions_.get(), length_, newLength.value());
333 if (!newFunctions) {
334 return -1;
335 }
336 (void)functions_.release();
337 functions_.reset(newFunctions);
338
339 // Realloc does not zero the delta for us.
340 PodZero(newFunctions + length_, delta);
341 break;
342 }
343 case TableRepr::Ref: {
344 if (!objects_.resize(newLength.value())) {
345 return -1;
346 }
347 break;
348 }
349 }
350
351 if (auto* object = maybeObject_.unbarrieredGet()) {
352 RemoveCellMemory(object, gcMallocBytes(), MemoryUse::WasmTableTable);
353 }
354
355 length_ = newLength.value();
356
357 if (auto* object = maybeObject_.unbarrieredGet()) {
358 AddCellMemory(object, gcMallocBytes(), MemoryUse::WasmTableTable);
359 }
360
361 for (InstanceSet::Range r = observers_.all(); !r.empty(); r.popFront()) {
362 r.front()->instance().onMovingGrowTable(this);
363 }
364
365 return oldLength;
366 }
367
movingGrowable() const368 bool Table::movingGrowable() const {
369 return !maximum_ || length_ < maximum_.value();
370 }
371
addMovingGrowObserver(JSContext * cx,WasmInstanceObject * instance)372 bool Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance) {
373 MOZ_ASSERT(movingGrowable());
374
375 // A table can be imported multiple times into an instance, but we only
376 // register the instance as an observer once.
377
378 if (!observers_.put(instance)) {
379 ReportOutOfMemory(cx);
380 return false;
381 }
382
383 return true;
384 }
385
sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const386 size_t Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
387 if (isFunction()) {
388 return mallocSizeOf(functions_.get());
389 }
390 return objects_.sizeOfExcludingThis(mallocSizeOf);
391 }
392
gcMallocBytes() const393 size_t Table::gcMallocBytes() const {
394 size_t size = sizeof(*this);
395 if (isFunction()) {
396 size += length() * sizeof(FunctionTableElem);
397 } else {
398 size += length() * sizeof(TableAnyRefVector::ElementType);
399 }
400 return size;
401 }
402