tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }