WasmTable.cpp (13927B)
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 #include "wasm/WasmValue.h" 28 29 #include "gc/StableCellHasher-inl.h" 30 #include "wasm/WasmInstance-inl.h" 31 32 using namespace js; 33 using namespace js::wasm; 34 using mozilla::CheckedInt; 35 36 Table::Table(JSContext* cx, const TableDesc& desc, 37 Handle<WasmTableObject*> maybeObject, FuncRefVector&& functions) 38 : maybeObject_(maybeObject), 39 observers_(cx->zone()), 40 functions_(std::move(functions)), 41 addressType_(desc.addressType()), 42 elemType_(desc.elemType), 43 isAsmJS_(desc.isAsmJS), 44 length_(desc.initialLength()), 45 maximum_(desc.maximumLength()) { 46 // Acquire a strong reference to the type definition this table may be 47 // referencing. 48 elemType_.AddRef(); 49 MOZ_ASSERT(repr() == TableRepr::Func); 50 MOZ_ASSERT(length_ <= MaxTableElemsRuntime); 51 } 52 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 addressType_(desc.addressType()), 59 elemType_(desc.elemType), 60 isAsmJS_(desc.isAsmJS), 61 length_(desc.initialLength()), 62 maximum_(desc.maximumLength()) { 63 // Acquire a strong reference to the type definition this table may be 64 // referencing. 65 elemType_.AddRef(); 66 MOZ_ASSERT(repr() == TableRepr::Ref); 67 MOZ_ASSERT(length_ <= MaxTableElemsRuntime); 68 } 69 70 Table::~Table() { 71 // Release the strong reference, if any. 72 elemType_.Release(); 73 } 74 75 /* static */ 76 SharedTable Table::create(JSContext* cx, const TableDesc& desc, 77 Handle<WasmTableObject*> maybeObject) { 78 // Tables are initialized with init_expr values at Instance::init or 79 // WasmTableObject::create. 80 81 switch (desc.elemType.tableRepr()) { 82 case TableRepr::Func: { 83 FuncRefVector functions; 84 if (!functions.resize(desc.initialLength())) { 85 ReportOutOfMemory(cx); 86 return nullptr; 87 } 88 return SharedTable( 89 cx->new_<Table>(cx, desc, maybeObject, std::move(functions))); 90 } 91 case TableRepr::Ref: { 92 TableAnyRefVector objects; 93 if (!objects.resize(desc.initialLength())) { 94 ReportOutOfMemory(cx); 95 return nullptr; 96 } 97 return SharedTable( 98 cx->new_<Table>(cx, desc, maybeObject, std::move(objects))); 99 } 100 } 101 MOZ_CRASH("switch is exhaustive"); 102 } 103 104 void Table::tracePrivate(JSTracer* trc) { 105 // If this table has a WasmTableObject, then this method is only called by 106 // WasmTableObject's trace hook so maybeObject_ must already be marked. 107 // TraceEdge is called so that the pointer can be updated during a moving 108 // GC. 109 TraceNullableEdge(trc, &maybeObject_, "wasm table object"); 110 111 switch (repr()) { 112 case TableRepr::Func: { 113 if (isAsmJS_) { 114 #ifdef DEBUG 115 for (uint32_t i = 0; i < length_; i++) { 116 MOZ_ASSERT(!functions_[i].instance); 117 } 118 #endif 119 break; 120 } 121 122 for (uint32_t i = 0; i < length_; i++) { 123 if (functions_[i].instance) { 124 wasm::TraceInstanceEdge(trc, functions_[i].instance, 125 "wasm table instance"); 126 } else { 127 MOZ_ASSERT(!functions_[i].code); 128 } 129 } 130 break; 131 } 132 case TableRepr::Ref: { 133 objects_.trace(trc); 134 break; 135 } 136 } 137 } 138 139 void Table::trace(JSTracer* trc) { 140 // The trace hook of WasmTableObject will call Table::tracePrivate at 141 // which point we can mark the rest of the children. If there is no 142 // WasmTableObject, call Table::tracePrivate directly. Redirecting through 143 // the WasmTableObject avoids marking the entire Table on each incoming 144 // edge (once per dependent Instance). 145 if (maybeObject_) { 146 TraceEdge(trc, &maybeObject_, "wasm table object"); 147 } else { 148 tracePrivate(trc); 149 } 150 } 151 152 uint8_t* Table::instanceElements() const { 153 if (repr() == TableRepr::Ref) { 154 return (uint8_t*)objects_.begin(); 155 } 156 return (uint8_t*)functions_.begin(); 157 } 158 159 const FunctionTableElem& Table::getFuncRef(uint32_t address) const { 160 MOZ_ASSERT(isFunction()); 161 return functions_[address]; 162 } 163 164 bool Table::getFuncRef(JSContext* cx, uint32_t address, 165 MutableHandleFunction fun) const { 166 MOZ_ASSERT(isFunction()); 167 168 const FunctionTableElem& elem = getFuncRef(address); 169 if (!elem.code) { 170 fun.set(nullptr); 171 return true; 172 } 173 174 Instance& instance = *elem.instance; 175 const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code); 176 return instance.getExportedFunction(cx, codeRange.funcIndex(), fun); 177 } 178 179 void Table::setFuncRef(uint32_t address, JSFunction* fun) { 180 MOZ_ASSERT(isFunction()); 181 MOZ_ASSERT(fun->isWasm()); 182 183 // Tables can store references to wasm functions from other instances. To 184 // preserve the === function identity required by the JS embedding spec, we 185 // must set the element to the function's underlying 186 // CodeRange.funcCheckedCallEntry and Instance so that Table.get()s always 187 // produce the same function object as was imported. 188 setFuncRef(address, fun->wasmCheckedCallEntry(), &fun->wasmInstance()); 189 } 190 191 void Table::setFuncRef(uint32_t address, void* code, Instance* instance) { 192 MOZ_ASSERT(isFunction()); 193 194 FunctionTableElem& elem = functions_[address]; 195 if (elem.instance) { 196 gc::PreWriteBarrier(elem.instance->objectUnbarriered()); 197 } 198 199 if (!isAsmJS_) { 200 elem.code = code; 201 elem.instance = instance; 202 MOZ_ASSERT(elem.instance->objectUnbarriered()->isTenured(), 203 "no postWriteBarrier (Table::set)"); 204 } else { 205 elem.code = code; 206 elem.instance = nullptr; 207 } 208 } 209 210 void Table::fillFuncRef(uint32_t address, uint32_t fillCount, FuncRef ref, 211 JSContext* cx) { 212 MOZ_ASSERT(isFunction()); 213 214 if (ref.isNull()) { 215 for (uint32_t i = address, end = address + fillCount; i != end; i++) { 216 setNull(i); 217 } 218 return; 219 } 220 221 RootedFunction fun(cx, ref.asJSFunction()); 222 void* code = fun->wasmCheckedCallEntry(); 223 Instance& instance = fun->wasmInstance(); 224 for (uint32_t i = address, end = address + fillCount; i != end; i++) { 225 setFuncRef(i, code, &instance); 226 } 227 } 228 229 AnyRef Table::getAnyRef(uint32_t address) const { 230 MOZ_ASSERT(!isFunction()); 231 return objects_[address]; 232 } 233 234 void Table::setAnyRef(uint32_t address, AnyRef ref) { 235 MOZ_ASSERT(!isFunction()); 236 objects_[address] = ref; 237 } 238 239 void Table::fillAnyRef(uint32_t address, uint32_t fillCount, AnyRef ref) { 240 MOZ_ASSERT(!isFunction()); 241 for (uint32_t i = address, end = address + fillCount; i != end; i++) { 242 objects_[i] = ref; 243 } 244 } 245 246 void Table::setRef(uint32_t address, AnyRef ref) { 247 if (ref.isNull()) { 248 setNull(address); 249 } else if (isFunction()) { 250 JSFunction* func = &ref.toJSObject().as<JSFunction>(); 251 setFuncRef(address, func); 252 } else { 253 setAnyRef(address, ref); 254 } 255 } 256 257 bool Table::getValue(JSContext* cx, uint32_t address, 258 MutableHandleValue result) const { 259 switch (repr()) { 260 case TableRepr::Func: { 261 MOZ_RELEASE_ASSERT(!isAsmJS()); 262 RootedFunction fun(cx); 263 if (!getFuncRef(cx, address, &fun)) { 264 return false; 265 } 266 result.setObjectOrNull(fun); 267 return true; 268 } 269 case TableRepr::Ref: { 270 if (!ValType(elemType_).isExposable()) { 271 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 272 JSMSG_WASM_BAD_VAL_TYPE); 273 return false; 274 } 275 return ToJSValue(cx, &objects_[address], ValType(elemType_), result); 276 } 277 default: 278 MOZ_CRASH(); 279 } 280 } 281 282 void Table::setNull(uint32_t address) { 283 switch (repr()) { 284 case TableRepr::Func: { 285 MOZ_RELEASE_ASSERT(!isAsmJS_); 286 FunctionTableElem& elem = functions_[address]; 287 if (elem.instance) { 288 gc::PreWriteBarrier(elem.instance->objectUnbarriered()); 289 } 290 291 elem.code = nullptr; 292 elem.instance = nullptr; 293 break; 294 } 295 case TableRepr::Ref: { 296 setAnyRef(address, AnyRef::null()); 297 break; 298 } 299 } 300 } 301 302 bool Table::copy(JSContext* cx, const Table& srcTable, uint32_t dstIndex, 303 uint32_t srcIndex) { 304 MOZ_RELEASE_ASSERT(!srcTable.isAsmJS_); 305 switch (repr()) { 306 case TableRepr::Func: { 307 MOZ_RELEASE_ASSERT(elemType().isFuncHierarchy() && 308 srcTable.elemType().isFuncHierarchy()); 309 FunctionTableElem& dst = functions_[dstIndex]; 310 if (dst.instance) { 311 gc::PreWriteBarrier(dst.instance->objectUnbarriered()); 312 } 313 314 const FunctionTableElem& src = srcTable.functions_[srcIndex]; 315 dst.code = src.code; 316 dst.instance = src.instance; 317 318 if (dst.instance) { 319 MOZ_ASSERT(dst.code); 320 MOZ_ASSERT(dst.instance->objectUnbarriered()->isTenured(), 321 "no postWriteBarrier (Table::copy)"); 322 } else { 323 MOZ_ASSERT(!dst.code); 324 } 325 break; 326 } 327 case TableRepr::Ref: { 328 switch (srcTable.repr()) { 329 case TableRepr::Ref: { 330 setAnyRef(dstIndex, srcTable.getAnyRef(srcIndex)); 331 break; 332 } 333 case TableRepr::Func: { 334 MOZ_RELEASE_ASSERT(srcTable.elemType().isFuncHierarchy()); 335 // Upcast. 336 RootedFunction fun(cx); 337 if (!srcTable.getFuncRef(cx, srcIndex, &fun)) { 338 // OOM, so just pass it on. 339 return false; 340 } 341 setAnyRef(dstIndex, AnyRef::fromJSObject(*fun)); 342 break; 343 } 344 } 345 break; 346 } 347 } 348 return true; 349 } 350 351 uint32_t Table::grow(uint32_t delta) { 352 // This isn't just an optimization: movingGrowable() assumes that 353 // onMovingGrowTable does not fire when length == maximum. 354 if (!delta) { 355 return length_; 356 } 357 358 uint32_t oldLength = length_; 359 360 CheckedInt<uint32_t> newLength = oldLength; 361 newLength += delta; 362 if (!newLength.isValid() || newLength.value() > MaxTableElemsRuntime) { 363 return -1; 364 } 365 366 if (maximum_ && newLength.value() > maximum_.value()) { 367 return -1; 368 } 369 370 MOZ_ASSERT(movingGrowable()); 371 372 switch (repr()) { 373 case TableRepr::Func: { 374 MOZ_RELEASE_ASSERT(!isAsmJS_); 375 if (!functions_.resize(newLength.value())) { 376 return -1; 377 } 378 break; 379 } 380 case TableRepr::Ref: { 381 if (!objects_.resize(newLength.value())) { 382 return -1; 383 } 384 break; 385 } 386 } 387 388 if (auto* object = maybeObject_.unbarrieredGet()) { 389 RemoveCellMemory(object, gcMallocBytes(), MemoryUse::WasmTableTable); 390 } 391 392 length_ = newLength.value(); 393 394 if (auto* object = maybeObject_.unbarrieredGet()) { 395 AddCellMemory(object, gcMallocBytes(), MemoryUse::WasmTableTable); 396 } 397 398 for (InstanceSet::Range r = observers_.all(); !r.empty(); r.popFront()) { 399 r.front()->instance().onMovingGrowTable(this); 400 } 401 402 return oldLength; 403 } 404 405 bool Table::movingGrowable() const { 406 return !maximum_ || length_ < maximum_.value(); 407 } 408 409 bool Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance) { 410 MOZ_ASSERT(movingGrowable()); 411 412 // A table can be imported multiple times into an instance, but we only 413 // register the instance as an observer once. 414 415 if (!observers_.put(instance)) { 416 ReportOutOfMemory(cx); 417 return false; 418 } 419 420 return true; 421 } 422 423 void Table::fillUninitialized(uint32_t address, uint32_t fillCount, 424 HandleAnyRef ref, JSContext* cx) { 425 #ifdef DEBUG 426 assertRangeNull(address, fillCount); 427 #endif // DEBUG 428 switch (repr()) { 429 case TableRepr::Func: { 430 MOZ_RELEASE_ASSERT(!isAsmJS_); 431 fillFuncRef(address, fillCount, FuncRef::fromAnyRefUnchecked(ref), cx); 432 break; 433 } 434 case TableRepr::Ref: { 435 fillAnyRef(address, fillCount, ref); 436 break; 437 } 438 } 439 } 440 441 #ifdef DEBUG 442 void Table::assertRangeNull(uint32_t address, uint32_t length) const { 443 switch (repr()) { 444 case TableRepr::Func: 445 for (uint32_t i = address; i < address + length; i++) { 446 MOZ_ASSERT(getFuncRef(i).instance == nullptr); 447 MOZ_ASSERT(getFuncRef(i).code == nullptr); 448 } 449 break; 450 case TableRepr::Ref: 451 for (uint32_t i = address; i < address + length; i++) { 452 MOZ_ASSERT(getAnyRef(i).isNull()); 453 } 454 break; 455 } 456 } 457 458 void Table::assertRangeNotNull(uint32_t address, uint32_t length) const { 459 switch (repr()) { 460 case TableRepr::Func: 461 for (uint32_t i = address; i < address + length; i++) { 462 MOZ_ASSERT_IF(!isAsmJS_, getFuncRef(i).instance != nullptr); 463 MOZ_ASSERT(getFuncRef(i).code != nullptr); 464 } 465 break; 466 case TableRepr::Ref: 467 for (uint32_t i = address; i < address + length; i++) { 468 MOZ_ASSERT(!getAnyRef(i).isNull()); 469 } 470 break; 471 } 472 } 473 #endif // DEBUG 474 475 size_t Table::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { 476 if (isFunction()) { 477 return functions_.sizeOfExcludingThis(mallocSizeOf); 478 } 479 return objects_.sizeOfExcludingThis(mallocSizeOf); 480 } 481 482 size_t Table::gcMallocBytes() const { 483 size_t size = sizeof(*this); 484 if (isFunction()) { 485 size += length() * sizeof(FunctionTableElem); 486 } else { 487 size += length() * sizeof(TableAnyRefVector::ElementType); 488 } 489 return size; 490 }