/* * Copyright (C) 2013, 2016 Apple Inc. All rights reserved. * Copyright (C) 2015 Yusuke Suzuki . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "CopiedAllocator.h" #include "CopyVisitorInlines.h" #include "ExceptionHelpers.h" #include "JSCJSValueInlines.h" #include "MapData.h" #include "SlotVisitorInlines.h" #include #include namespace JSC { template inline void MapDataImpl::clear() { m_cellKeyedTable.clear(); m_valueKeyedTable.clear(); m_stringKeyedTable.clear(); m_symbolKeyedTable.clear(); m_capacity = 0; m_size = 0; m_deletedCount = 0; m_entries.clear(); m_iterators.forEach([](JSIterator* iterator, JSIterator*) { iterator->iteratorData()->didRemoveAllEntries(); }); } template inline Entry* MapDataImpl::find(ExecState* exec, KeyType key) { if (key.value.isString()) { auto iter = m_stringKeyedTable.find(asString(key.value)->value(exec).impl()); if (iter == m_stringKeyedTable.end()) return 0; return &m_entries.get()[iter->value]; } if (key.value.isSymbol()) { auto iter = m_symbolKeyedTable.find(asSymbol(key.value)->privateName().uid()); if (iter == m_symbolKeyedTable.end()) return 0; return &m_entries.get()[iter->value]; } if (key.value.isCell()) { auto iter = m_cellKeyedTable.find(key.value.asCell()); if (iter == m_cellKeyedTable.end()) return 0; return &m_entries.get()[iter->value]; } auto iter = m_valueKeyedTable.find(JSValue::encode(key.value)); if (iter == m_valueKeyedTable.end()) return 0; return &m_entries.get()[iter->value]; } template inline bool MapDataImpl::contains(ExecState* exec, KeyType key) { return find(exec, key); } template template inline Entry* MapDataImpl::add(ExecState* exec, JSCell* owner, Map& map, Key key, KeyType keyValue) { typename Map::iterator location = map.find(key); if (location != map.end()) return &m_entries.get()[location->value]; if (!ensureSpaceForAppend(exec, owner)) return 0; auto result = map.add(key, m_size); RELEASE_ASSERT(result.isNewEntry); Entry* entry = &m_entries.get()[m_size++]; new (entry) Entry(); entry->setKey(exec->vm(), owner, keyValue.value); return entry; } template inline void MapDataImpl::set(ExecState* exec, JSCell* owner, KeyType key, JSValue value) { Entry* location = add(exec, owner, key); if (!location) return; location->setValue(exec->vm(), owner, value); } template inline Entry* MapDataImpl::add(ExecState* exec, JSCell* owner, KeyType key) { if (key.value.isString()) return add(exec, owner, m_stringKeyedTable, asString(key.value)->value(exec).impl(), key); if (key.value.isSymbol()) return add(exec, owner, m_symbolKeyedTable, asSymbol(key.value)->privateName().uid(), key); if (key.value.isCell()) return add(exec, owner, m_cellKeyedTable, key.value.asCell(), key); return add(exec, owner, m_valueKeyedTable, JSValue::encode(key.value), key); } template inline JSValue MapDataImpl::get(ExecState* exec, KeyType key) { if (Entry* entry = find(exec, key)) return entry->value().get(); return JSValue(); } template inline bool MapDataImpl::remove(ExecState* exec, KeyType key) { int32_t location; if (key.value.isString()) { auto iter = m_stringKeyedTable.find(asString(key.value)->value(exec).impl()); if (iter == m_stringKeyedTable.end()) return false; location = iter->value; m_stringKeyedTable.remove(iter); } else if (key.value.isSymbol()) { auto iter = m_symbolKeyedTable.find(asSymbol(key.value)->privateName().uid()); if (iter == m_symbolKeyedTable.end()) return false; location = iter->value; m_symbolKeyedTable.remove(iter); } else if (key.value.isCell()) { auto iter = m_cellKeyedTable.find(key.value.asCell()); if (iter == m_cellKeyedTable.end()) return false; location = iter->value; m_cellKeyedTable.remove(iter); } else { auto iter = m_valueKeyedTable.find(JSValue::encode(key.value)); if (iter == m_valueKeyedTable.end()) return false; location = iter->value; m_valueKeyedTable.remove(iter); } m_entries.get()[location].clear(); m_deletedCount++; return true; } template inline void MapDataImpl::replaceAndPackBackingStore(Entry* destination, int32_t newCapacity) { ASSERT(shouldPack()); int32_t newEnd = 0; RELEASE_ASSERT(newCapacity > 0); for (int32_t i = 0; i < m_size; i++) { Entry& entry = m_entries.get()[i]; if (!entry.key()) { m_iterators.forEach([newEnd](JSIterator* iterator, JSIterator*) { iterator->iteratorData()->didRemoveEntry(newEnd); }); continue; } ASSERT(newEnd < newCapacity); destination[newEnd] = entry; // We overwrite the old entry with a forwarding index for the new entry, // so that we can fix up our hash tables below without doing additional // hash lookups entry.setKeyWithoutWriteBarrier(jsNumber(newEnd)); newEnd++; } // Fixup for the hashmaps for (auto ptr = m_valueKeyedTable.begin(); ptr != m_valueKeyedTable.end(); ++ptr) ptr->value = m_entries.get()[ptr->value].key().get().asInt32(); for (auto ptr = m_cellKeyedTable.begin(); ptr != m_cellKeyedTable.end(); ++ptr) ptr->value = m_entries.get()[ptr->value].key().get().asInt32(); for (auto ptr = m_stringKeyedTable.begin(); ptr != m_stringKeyedTable.end(); ++ptr) ptr->value = m_entries.get()[ptr->value].key().get().asInt32(); for (auto ptr = m_symbolKeyedTable.begin(); ptr != m_symbolKeyedTable.end(); ++ptr) ptr->value = m_entries.get()[ptr->value].key().get().asInt32(); ASSERT((m_size - newEnd) == m_deletedCount); m_deletedCount = 0; m_capacity = newCapacity; m_size = newEnd; m_entries.setWithoutBarrier(destination); } template inline void MapDataImpl::replaceBackingStore(Entry* destination, int32_t newCapacity) { ASSERT(!shouldPack()); RELEASE_ASSERT(newCapacity > 0); ASSERT(newCapacity >= m_capacity); memcpy(destination, m_entries.get(), sizeof(Entry) * m_size); m_capacity = newCapacity; m_entries.setWithoutBarrier(destination); } template inline CheckedBoolean MapDataImpl::ensureSpaceForAppend(ExecState* exec, JSCell* owner) { if (m_capacity > m_size) return true; size_t requiredSize = std::max(m_capacity + (m_capacity / 2) + 1, static_cast(minimumMapSize)); void* newStorage = nullptr; DeferGC defer(*exec->heap()); if (!exec->heap()->tryAllocateStorage(owner, requiredSize * sizeof(Entry), &newStorage)) { throwOutOfMemoryError(exec); return false; } Entry* newEntries = static_cast(newStorage); if (shouldPack()) replaceAndPackBackingStore(newEntries, requiredSize); else replaceBackingStore(newEntries, requiredSize); exec->heap()->writeBarrier(owner); return true; } template inline void MapDataImpl::visitChildren(JSCell* owner, SlotVisitor& visitor) { Entry* entries = m_entries.get(); if (!entries) return; if (m_deletedCount) { for (int32_t i = 0; i < m_size; i++) { if (!entries[i].key()) continue; entries[i].visitChildren(visitor); } } else { // Guaranteed that all fields of Entry type is WriteBarrier. visitor.appendValues(reinterpret_cast*>(&entries[0]), m_size * (sizeof(Entry) / sizeof(WriteBarrier))); } visitor.copyLater(owner, MapBackingStoreCopyToken, m_entries.get(), capacityInBytes()); } template inline void MapDataImpl::copyBackingStore(CopyVisitor& visitor, CopyToken token) { if (token == MapBackingStoreCopyToken && visitor.checkIfShouldCopy(m_entries.get())) { Entry* oldEntries = m_entries.get(); Entry* newEntries = static_cast(visitor.allocateNewSpace(capacityInBytes())); if (shouldPack()) replaceAndPackBackingStore(newEntries, m_capacity); else replaceBackingStore(newEntries, m_capacity); visitor.didCopy(oldEntries, capacityInBytes()); } } template inline auto MapDataImpl::createIteratorData(JSIterator* iterator) -> IteratorData { m_iterators.set(iterator, iterator); return IteratorData(this); } }