Source code

Revision control

Other Tools

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