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