runtime/dict-builtins.cpp (1,129 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "dict-builtins.h"
#include "builtins.h"
#include "frame.h"
#include "globals.h"
#include "int-builtins.h"
#include "interpreter.h"
#include "objects.h"
#include "runtime.h"
#include "str-builtins.h"
#include "thread.h"
#include "type-builtins.h"
#include "utils.h"
namespace py {
namespace {
// Helper functions for accessing sparse array from dict.indices().
static const uint32_t kTombstoneValue = 0xFFFFFFFE;
static const uint32_t kEmptyValue = 0xFFFFFFFF;
static const word kNumBytesExponent = 2;
static word probeBegin(word num_indices, word hash, word* indices_mask,
uword* perturb) {
DCHECK(Utils::isPowerOfTwo(num_indices), "%ld is not a power of 2",
num_indices);
DCHECK(num_indices > 0, "indicesxs size <= 0");
DCHECK(RawSmallInt::isValid(hash), "hash out of range");
*perturb = static_cast<uword>(hash);
*indices_mask = num_indices - 1;
return *indices_mask & hash;
}
static word probeNext(word current, word indices_mask, uword* perturb) {
// Given that current stands for the index into dict.indices, this advances
// current to (5 * current + 1 + perturb). Note that it's guaranteed that
// keeping calling this function returns a permutation of all indices when
// the number of the indices is power of two. See
// https://en.wikipedia.org/wiki/Linear_congruential_generator#c_%E2%89%A0_0.
*perturb >>= 5;
return (current * 5 + 1 + *perturb) & indices_mask;
}
// Returns the bytes offset into the indices array given an index
static word indexOffset(word index) { return index << kNumBytesExponent; }
// Return the item index stored at indices[bytes_offset].
static uint32_t itemIndexAt(RawMutableBytes indices, word bytes_offset) {
DCHECK(bytes_offset % 4 == 0, "bytes_offset must be a multiple of 4");
return indices.uint32At(bytes_offset);
}
// Set `item_index` at indices[bytes_offset].
static void itemIndexAtPut(RawMutableBytes indices, word bytes_offset,
uint32_t item_index) {
indices.uint32AtPut(bytes_offset, item_index);
}
// Set a tombstone at indices[bytes_offset] given `data_length` from
// data.length().
static void indicesSetTombstone(RawMutableBytes indices, word bytes_offset) {
itemIndexAtPut(indices, bytes_offset, kTombstoneValue);
}
// Return true if the indices[bytes_offset] is filled with an active item.
static bool indicesIsFull(RawMutableBytes indices, word bytes_offset,
uint32_t* item_index) {
*item_index = itemIndexAt(indices, bytes_offset);
return *item_index < kTombstoneValue;
}
// Return true if the indices[bytes_offset] is never used.
static bool indicesIsEmpty(RawMutableBytes indices, word bytes_offset) {
return itemIndexAt(indices, bytes_offset) == kEmptyValue;
}
// Helper functions for accessing dict items from dict.data().
// Data tuple Layout.
static const word kItemHashOffset = 0;
static const word kItemKeyOffset = 1;
static const word kItemValueOffset = 2;
static const word kItemNumPointers = 3;
static RawObject itemKey(RawMutableTuple data, word index) {
return data.at(index + kItemKeyOffset);
}
static RawObject itemValue(RawMutableTuple data, word index) {
return data.at(index + kItemValueOffset);
}
static word itemHash(RawMutableTuple data, word index) {
return SmallInt::cast(data.at(index + kItemHashOffset)).value();
}
static RawObject itemHashRaw(RawMutableTuple data, word index) {
return data.at(index + kItemHashOffset);
}
static void itemSet(RawMutableTuple data, word index, word hash, RawObject key,
RawObject value) {
data.atPut(index + kItemHashOffset, SmallInt::fromWordTruncated(hash));
data.atPut(index + kItemKeyOffset, key);
data.atPut(index + kItemValueOffset, value);
}
static void itemSetTombstone(RawMutableTuple data, word index) {
data.atPut(index + kItemHashOffset, Unbound::object());
data.atPut(index + kItemKeyOffset, NoneType::object());
data.atPut(index + kItemValueOffset, NoneType::object());
}
static void itemSetValue(RawMutableTuple data, word index, RawObject value) {
data.atPut(index + kItemValueOffset, value);
}
static bool itemIsEmpty(RawMutableTuple data, word index) {
return data.at(index + kItemHashOffset).isNoneType();
}
static bool itemIsFull(RawMutableTuple data, word index) {
return data.at(index + kItemHashOffset).isSmallInt();
}
static bool itemIsTombstone(RawMutableTuple data, word index) {
return data.at(index + kItemHashOffset).isUnbound();
}
} // namespace
// Returns one of the three possible values:
// - `key` was found at indices[bytes_offset] : SmallInt::fromWord(bytes_offset)
// - `key` was not found : SmallInt::fromWord(-1)
// - Exception that was raised from key comparison __eq__ function.
static RawObject dictLookup(Thread* thread, const MutableTuple& data,
MutableBytes& indices, word num_indices,
const Object& key, word hash) {
DCHECK(data.length() > 0, "data shouldn't be empty");
uword perturb;
word indices_mask;
RawSmallInt hash_int = SmallInt::fromWord(hash);
for (word current_index =
probeBegin(num_indices, hash, &indices_mask, &perturb);
; current_index = probeNext(current_index, indices_mask, &perturb)) {
word bytes_offset = indexOffset(current_index);
uint32_t item_index;
if (indicesIsFull(*indices, bytes_offset, &item_index)) {
if (itemHashRaw(*data, item_index) == hash_int) {
RawObject eq =
Runtime::objectEquals(thread, itemKey(*data, item_index), *key);
if (eq == Bool::trueObj()) {
return SmallInt::fromWord(bytes_offset);
}
if (UNLIKELY(eq.isErrorException())) {
return eq;
}
}
continue;
}
if (indicesIsEmpty(*indices, bytes_offset)) {
return SmallInt::fromWord(-1);
}
}
UNREACHABLE("Expected to have found an empty index");
}
// Returns one of the three possible values:
// - `key` was found at indices[bytes_offset] : SmallInt::fromWord(bytes_offset)
// - `key` was not found, but insertion can be done to indices[bytes_offset] :
// SmallInt::fromWord(bytes_offset - data.length())
// - Exception that was raised from key comparison __eq__ function.
static RawObject dictLookupForInsertion(Thread* thread,
const MutableTuple& data,
MutableBytes& indices, word num_indices,
const Object& key, word hash) {
DCHECK(data.length() > 0, "data shouldn't be empty");
word next_free_index = -1;
uword perturb;
word indices_mask;
RawSmallInt hash_int = SmallInt::fromWord(hash);
for (word current_index =
probeBegin(num_indices, hash, &indices_mask, &perturb);
; current_index = probeNext(current_index, indices_mask, &perturb)) {
word bytes_offset = indexOffset(current_index);
uint32_t item_index;
if (indicesIsFull(*indices, bytes_offset, &item_index)) {
if (itemHashRaw(*data, item_index) == hash_int) {
RawObject eq =
Runtime::objectEquals(thread, itemKey(*data, item_index), *key);
if (eq == Bool::trueObj()) {
return SmallInt::fromWord(bytes_offset);
}
if (UNLIKELY(eq.isErrorException())) {
return eq;
}
}
continue;
}
if (next_free_index == -1) {
next_free_index = bytes_offset;
}
if (indicesIsEmpty(*indices, bytes_offset)) {
return SmallInt::fromWord(next_free_index - indexOffset(num_indices));
}
}
UNREACHABLE("Expected to have found an empty index");
}
static word nextItemIndex(RawObject data, word* index, word end) {
for (word i = *index; i < end; i += kItemNumPointers) {
if (!itemIsFull(MutableTuple::cast(data), i)) continue;
*index = i + kItemNumPointers;
return i;
}
return -1;
}
bool dictNextItem(const Dict& dict, word* index, Object* key_out,
Object* value_out) {
RawObject data = dict.data();
word next_item_index = nextItemIndex(data, index, dict.firstEmptyItemIndex());
if (next_item_index < 0) {
return false;
}
*key_out = itemKey(MutableTuple::cast(data), next_item_index);
*value_out = itemValue(MutableTuple::cast(data), next_item_index);
return true;
}
bool dictNextItemHash(const Dict& dict, word* index, Object* key_out,
Object* value_out, word* hash_out) {
RawObject data = dict.data();
word next_item_index = nextItemIndex(data, index, dict.firstEmptyItemIndex());
if (next_item_index < 0) {
return false;
}
*key_out = itemKey(MutableTuple::cast(data), next_item_index);
*value_out = itemValue(MutableTuple::cast(data), next_item_index);
*hash_out = itemHash(MutableTuple::cast(data), next_item_index);
return true;
}
bool dictNextKey(const Dict& dict, word* index, Object* key_out) {
RawObject data = dict.data();
word next_item_index = nextItemIndex(data, index, dict.firstEmptyItemIndex());
if (next_item_index < 0) {
return false;
}
*key_out = itemKey(MutableTuple::cast(data), next_item_index);
return true;
}
bool dictNextKeyHash(const Dict& dict, word* index, Object* key_out,
word* hash_out) {
RawObject data = dict.data();
word next_item_index = nextItemIndex(data, index, dict.firstEmptyItemIndex());
if (next_item_index < 0) {
return false;
}
*key_out = itemKey(MutableTuple::cast(data), next_item_index);
*hash_out = itemHash(MutableTuple::cast(data), next_item_index);
return true;
}
bool dictNextValue(const Dict& dict, word* index, Object* value_out) {
RawObject data = dict.data();
word next_item_index = nextItemIndex(data, index, dict.firstEmptyItemIndex());
if (next_item_index < 0) {
return false;
}
*value_out = itemValue(MutableTuple::cast(data), next_item_index);
return true;
}
static word sizeOfDataTuple(word num_indices) { return (num_indices * 2) / 3; }
static const word kDictGrowthFactor = 2;
// Initial size of the dict. According to comments in CPython's
// dictobject.c this accommodates the majority of dictionaries without needing
// a resize (obviously this depends on the load factor used to resize the
// dict).
static const word kInitialDictIndicesLength = 8;
void dictAllocateArrays(Thread* thread, const Dict& dict, word num_indices) {
num_indices = Utils::maximum(num_indices, kInitialDictIndicesLength);
DCHECK(Utils::isPowerOfTwo(num_indices), "%ld is not a power of 2",
num_indices);
Runtime* runtime = thread->runtime();
dict.setData(runtime->newMutableTuple(sizeOfDataTuple(num_indices) *
kItemNumPointers));
MutableTuple::cast(dict.data()).fill(NoneType::object());
dict.setIndices(
runtime->mutableBytesWith(indexOffset(num_indices), kMaxByte));
dict.setFirstEmptyItemIndex(0);
}
// Return true if `dict` has an available item for insertion.
static bool dictHasUsableItem(const Dict& dict) {
return dict.firstEmptyItemIndex() <
RawMutableTuple::cast(dict.data()).length();
}
// Insert `key`/`value` into dictionary assuming no item with an equivalent
// key and no tombstones exist.
static void dictInsertNoUpdate(const MutableTuple& data,
const MutableBytes& indices, word num_indices,
word item_count, const Object& key, word hash,
const Object& value) {
DCHECK(data.length() > 0, "dict must not be empty");
uword perturb;
word indices_mask;
for (word current_index =
probeBegin(num_indices, hash, &indices_mask, &perturb);
; current_index = probeNext(current_index, indices_mask, &perturb)) {
word bytes_offset = indexOffset(current_index);
if (indicesIsEmpty(*indices, bytes_offset)) {
uint32_t item_index = item_count * kItemNumPointers;
itemSet(*data, item_index, hash, *key, *value);
itemIndexAtPut(*indices, bytes_offset, item_index);
return;
}
}
}
static void dictEnsureCapacity(Thread* thread, const Dict& dict) {
DCHECK(dict.numIndices() && Utils::isPowerOfTwo(dict.numIndices()),
"dict capacity must be power of two, greater than zero");
if (dictHasUsableItem(dict)) {
return;
}
// TODO(T44247845): Handle overflow here.
word new_num_indices = dict.numIndices() * kDictGrowthFactor;
DCHECK(new_num_indices < kTombstoneValue,
"new_num_indices is expected to be less than kTombstoneValue");
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
MutableTuple new_data(
&scope, runtime->newMutableTuple(sizeOfDataTuple(new_num_indices) *
kItemNumPointers));
new_data.fill(NoneType::object());
MutableBytes new_indices(&scope, runtime->mutableBytesWith(
indexOffset(new_num_indices), kMaxByte));
// Re-insert items
Object key(&scope, NoneType::object());
Object value(&scope, NoneType::object());
word hash;
word item_count = 0;
for (word i = 0; dictNextItemHash(dict, &i, &key, &value, &hash);
item_count++) {
dictInsertNoUpdate(new_data, new_indices, new_num_indices, item_count, key,
hash, value);
}
DCHECK(item_count == dict.numItems(), "found entries != dictNumItems()");
dict.setData(*new_data);
dict.setIndices(*new_indices);
dict.setFirstEmptyItemIndex(dict.numItems() * kItemNumPointers);
}
RawObject dictAtPut(Thread* thread, const Dict& dict, const Object& key,
word hash, const Object& value) {
if (dict.indices() == SmallInt::fromWord(0)) {
dictAllocateArrays(thread, dict, kInitialDictIndicesLength);
}
HandleScope scope(thread);
MutableTuple data(&scope, dict.data());
MutableBytes indices(&scope, dict.indices());
word num_indices = dict.numIndices();
RawObject lookup_result =
dictLookupForInsertion(thread, data, indices, num_indices, key, hash);
if (UNLIKELY(lookup_result.isErrorException())) {
return lookup_result;
}
word bytes_offset = SmallInt::cast(lookup_result).value();
if (bytes_offset >= 0) {
uint32_t item_index = itemIndexAt(*indices, bytes_offset);
itemSetValue(*data, item_index, *value);
return NoneType::object();
}
bytes_offset += indexOffset(num_indices);
word item_index = dict.firstEmptyItemIndex();
DCHECK(itemIsEmpty(*data, item_index), "item is expected to be empty");
itemSet(*data, item_index, hash, *key, *value);
itemIndexAtPut(*indices, bytes_offset, item_index);
dict.setNumItems(dict.numItems() + 1);
dict.setFirstEmptyItemIndex(dict.firstEmptyItemIndex() + kItemNumPointers);
dictEnsureCapacity(thread, dict);
DCHECK(dictHasUsableItem(dict), "dict must have an empty item left");
return NoneType::object();
}
void dictAtPutByStr(Thread* thread, const Dict& dict, const Object& name,
const Object& value) {
word hash = strHash(thread, *name);
UNUSED RawObject result = dictAtPut(thread, dict, name, hash, value);
DCHECK(!result.isError(), "result must not be an error");
}
void dictAtPutById(Thread* thread, const Dict& dict, SymbolId id,
const Object& value) {
HandleScope scope(thread);
Object name(&scope, thread->runtime()->symbols()->at(id));
dictAtPutByStr(thread, dict, name, value);
}
RawObject dictAt(Thread* thread, const Dict& dict, const Object& key,
word hash) {
if (dict.numItems() == 0) {
return Error::notFound();
}
HandleScope scope(thread);
MutableTuple data(&scope, dict.data());
MutableBytes indices(&scope, dict.indices());
RawObject lookup_result =
dictLookup(thread, data, indices, dict.numIndices(), key, hash);
if (UNLIKELY(lookup_result.isErrorException())) {
return lookup_result;
}
word bytes_offset = SmallInt::cast(lookup_result).value();
if (bytes_offset >= 0) {
uint32_t item_index = itemIndexAt(*indices, bytes_offset);
return itemValue(*data, item_index);
}
return Error::notFound();
}
RawObject dictAtByStr(Thread* thread, const Dict& dict, const Object& name) {
word hash = strHash(thread, *name);
return dictAt(thread, dict, name, hash);
}
RawObject dictAtById(Thread* thread, const Dict& dict, SymbolId id) {
HandleScope scope(thread);
Object name(&scope, thread->runtime()->symbols()->at(id));
return dictAtByStr(thread, dict, name);
}
RawObject dictAtPutInValueCellByStr(Thread* thread, const Dict& dict,
const Object& name, const Object& value) {
if (dict.indices() == SmallInt::fromWord(0)) {
dictAllocateArrays(thread, dict, kInitialDictIndicesLength);
}
HandleScope scope(thread);
MutableTuple data(&scope, dict.data());
MutableBytes indices(&scope, dict.indices());
word num_indices = dict.numIndices();
word hash = strHash(thread, *name);
RawObject lookup_result =
dictLookupForInsertion(thread, data, indices, num_indices, name, hash);
if (UNLIKELY(lookup_result.isErrorException())) {
return lookup_result;
}
word bytes_offset = SmallInt::cast(lookup_result).value();
if (bytes_offset >= 0) {
uint32_t item_index = itemIndexAt(*indices, bytes_offset);
RawValueCell value_cell = ValueCell::cast(itemValue(*data, item_index));
value_cell.setValue(*value);
return value_cell;
}
bytes_offset += indexOffset(num_indices);
word item_index = dict.firstEmptyItemIndex();
DCHECK(itemIsEmpty(*data, item_index), "item is expected to be empty");
ValueCell value_cell(&scope, thread->runtime()->newValueCell());
itemSet(*data, item_index, hash, *name, *value_cell);
itemIndexAtPut(*indices, bytes_offset, item_index);
dict.setNumItems(dict.numItems() + 1);
dict.setFirstEmptyItemIndex(dict.firstEmptyItemIndex() + kItemNumPointers);
dictEnsureCapacity(thread, dict);
DCHECK(dictHasUsableItem(dict), "dict must have an empty item left");
value_cell.setValue(*value);
return *value_cell;
}
void dictClear(Thread* thread, const Dict& dict) {
if (dict.indices() == SmallInt::fromWord(0)) return;
HandleScope scope(thread);
MutableTuple data(&scope, dict.data());
data.fill(NoneType::object());
MutableBytes indices(&scope, dict.indices());
indices.replaceFromWithByte(0, kMaxByte, indices.length());
dict.setNumItems(0);
dict.setFirstEmptyItemIndex(0);
}
RawObject dictIncludes(Thread* thread, const Dict& dict, const Object& key,
word hash) {
if (dict.numItems() == 0) {
return Bool::falseObj();
}
HandleScope scope(thread);
MutableTuple data(&scope, dict.data());
MutableBytes indices(&scope, dict.indices());
word num_indices = dict.numIndices();
RawObject lookup_result =
dictLookup(thread, data, indices, num_indices, key, hash);
if (UNLIKELY(lookup_result.isErrorException())) {
return lookup_result;
}
return Bool::fromBool(SmallInt::cast(lookup_result).value() >= 0);
}
RawObject dictRemoveByStr(Thread* thread, const Dict& dict,
const Object& name) {
word hash = strHash(thread, *name);
return dictRemove(thread, dict, name, hash);
}
RawObject dictRemove(Thread* thread, const Dict& dict, const Object& key,
word hash) {
if (dict.numItems() == 0) {
return Error::notFound();
}
HandleScope scope(thread);
MutableTuple data(&scope, dict.data());
MutableBytes indices(&scope, dict.indices());
word num_indices = dict.numIndices();
RawObject lookup_result =
dictLookup(thread, data, indices, num_indices, key, hash);
if (UNLIKELY(lookup_result.isErrorException())) {
return lookup_result;
}
word bytes_offset = SmallInt::cast(lookup_result).value();
if (bytes_offset < 0) {
return Error::notFound();
}
uint32_t item_index = itemIndexAt(*indices, bytes_offset);
Object result(&scope, itemValue(*data, item_index));
itemSetTombstone(*data, item_index);
indicesSetTombstone(*indices, bytes_offset);
dict.setNumItems(dict.numItems() - 1);
return *result;
}
RawObject dictKeys(Thread* thread, const Dict& dict) {
word len = dict.numItems();
Runtime* runtime = thread->runtime();
if (len == 0) {
return runtime->newList();
}
HandleScope scope(thread);
MutableTuple keys(&scope, runtime->newMutableTuple(len));
Object key(&scope, NoneType::object());
word num_keys = 0;
for (word i = 0; dictNextKey(dict, &i, &key);) {
DCHECK(num_keys < keys.length(), "%ld ! < %ld", num_keys, keys.length());
keys.atPut(num_keys, *key);
num_keys++;
}
List result(&scope, runtime->newList());
result.setItems(*keys);
result.setNumItems(len);
return *result;
}
RawObject dictCopy(Thread* thread, const Dict& dict) {
HandleScope scope(thread);
Dict copy(&scope, thread->runtime()->newDict());
Object result(&scope, dictMergeError(thread, copy, dict));
if (result.isError()) {
return *result;
}
return *copy;
}
namespace {
enum class Override {
kIgnore,
kOverride,
kError,
};
RawObject dictMergeDict(Thread* thread, const Dict& dict, const Object& mapping,
Override do_override) {
HandleScope scope(thread);
if (*mapping == *dict) return NoneType::object();
Object key(&scope, NoneType::object());
Object value(&scope, NoneType::object());
word hash;
Dict other(&scope, *mapping);
Object included(&scope, NoneType::object());
Object dict_result(&scope, NoneType::object());
for (word i = 0; dictNextItemHash(other, &i, &key, &value, &hash);) {
if (do_override == Override::kOverride) {
dict_result = dictAtPut(thread, dict, key, hash, value);
if (dict_result.isErrorException()) return *dict_result;
} else {
included = dictIncludes(thread, dict, key, hash);
if (included.isErrorException()) return *included;
if (included == Bool::falseObj()) {
dict_result = dictAtPut(thread, dict, key, hash, value);
if (dict_result.isErrorException()) return *dict_result;
} else if (do_override == Override::kError) {
return thread->raise(LayoutId::kKeyError, *key);
}
}
}
return NoneType::object();
}
RawObject dictMergeImpl(Thread* thread, const Dict& dict, const Object& mapping,
Override do_override) {
Runtime* runtime = thread->runtime();
if (runtime->isInstanceOfDict(*mapping)) {
return dictMergeDict(thread, dict, mapping, do_override);
}
HandleScope scope(thread);
Object key(&scope, NoneType::object());
Object hash_obj(&scope, NoneType::object());
Object value(&scope, NoneType::object());
Object included(&scope, NoneType::object());
Object keys_method(&scope,
runtime->attributeAtById(thread, mapping, ID(keys)));
if (keys_method.isError()) {
return *keys_method;
}
// Generic mapping, use keys() and __getitem__()
Object subscr_method(
&scope, runtime->attributeAtById(thread, mapping, ID(__getitem__)));
if (subscr_method.isError()) {
return *subscr_method;
}
Object keys(&scope, Interpreter::callMethod1(thread, keys_method, mapping));
if (keys.isError()) return *keys;
if (keys.isList()) {
List keys_list(&scope, *keys);
Object dict_result(&scope, NoneType::object());
for (word i = 0; i < keys_list.numItems(); ++i) {
key = keys_list.at(i);
hash_obj = Interpreter::hash(thread, key);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
if (do_override == Override::kOverride) {
value = Interpreter::callMethod2(thread, subscr_method, mapping, key);
if (value.isError()) return *value;
dict_result = dictAtPut(thread, dict, key, hash, value);
if (dict_result.isErrorException()) return *dict_result;
} else {
included = dictIncludes(thread, dict, key, hash);
if (included.isErrorException()) return *included;
if (included == Bool::falseObj()) {
value = Interpreter::callMethod2(thread, subscr_method, mapping, key);
if (value.isError()) return *value;
dict_result = dictAtPut(thread, dict, key, hash, value);
if (dict_result.isErrorException()) return *dict_result;
} else if (do_override == Override::kError) {
return thread->raise(LayoutId::kKeyError, *key);
}
}
}
return NoneType::object();
}
if (keys.isTuple()) {
Tuple keys_tuple(&scope, *keys);
Object dict_result(&scope, NoneType::object());
for (word i = 0; i < keys_tuple.length(); ++i) {
key = keys_tuple.at(i);
hash_obj = Interpreter::hash(thread, key);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
if (do_override == Override::kOverride) {
value = Interpreter::callMethod2(thread, subscr_method, mapping, key);
if (value.isError()) return *value;
dict_result = dictAtPut(thread, dict, key, hash, value);
if (dict_result.isErrorException()) return *dict_result;
} else {
included = dictIncludes(thread, dict, key, hash);
if (included.isErrorException()) return *included;
if (included == Bool::falseObj()) {
value = Interpreter::callMethod2(thread, subscr_method, mapping, key);
if (value.isError()) return *value;
dict_result = dictAtPut(thread, dict, key, hash, value);
if (dict_result.isErrorException()) return *dict_result;
} else if (do_override == Override::kError) {
return thread->raise(LayoutId::kKeyError, *key);
}
}
}
return NoneType::object();
}
// keys is probably an iterator
Object iter_method(&scope,
Interpreter::lookupMethod(thread, keys, ID(__iter__)));
if (iter_method.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError, "keys() is not iterable");
}
Object iterator(&scope, Interpreter::callMethod1(thread, iter_method, keys));
if (iterator.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError, "keys() is not iterable");
}
Object next_method(&scope,
Interpreter::lookupMethod(thread, iterator, ID(__next__)));
if (next_method.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError, "keys() is not iterable");
}
Object dict_result(&scope, NoneType::object());
for (;;) {
key = Interpreter::callMethod1(thread, next_method, iterator);
if (key.isError()) {
if (thread->clearPendingStopIteration()) break;
return *key;
}
hash_obj = Interpreter::hash(thread, key);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
if (do_override == Override::kOverride) {
value = Interpreter::callMethod2(thread, subscr_method, mapping, key);
if (value.isError()) return *value;
dict_result = dictAtPut(thread, dict, key, hash, value);
if (dict_result.isErrorException()) return *dict_result;
} else {
included = dictIncludes(thread, dict, key, hash);
if (included.isErrorException()) return *included;
if (included == Bool::falseObj()) {
value = Interpreter::callMethod2(thread, subscr_method, mapping, key);
if (value.isError()) return *value;
dict_result = dictAtPut(thread, dict, key, hash, value);
if (dict_result.isErrorException()) return *dict_result;
} else if (do_override == Override::kError) {
return thread->raise(LayoutId::kKeyError, *key);
}
}
}
return NoneType::object();
}
} // namespace
RawObject dictMergeOverride(Thread* thread, const Dict& dict,
const Object& mapping) {
return dictMergeImpl(thread, dict, mapping, Override::kOverride);
}
RawObject dictMergeError(Thread* thread, const Dict& dict,
const Object& mapping) {
return dictMergeImpl(thread, dict, mapping, Override::kError);
}
RawObject dictMergeIgnore(Thread* thread, const Dict& dict,
const Object& mapping) {
return dictMergeImpl(thread, dict, mapping, Override::kIgnore);
}
RawObject dictEq(Thread* thread, const Dict& left, const Dict& right) {
if (left.numItems() != right.numItems()) {
return Bool::falseObj();
}
HandleScope scope(thread);
Object key(&scope, NoneType::object());
Object left_value(&scope, NoneType::object());
word hash;
Object right_value(&scope, NoneType::object());
Object result(&scope, NoneType::object());
for (word i = 0; dictNextItemHash(left, &i, &key, &left_value, &hash);) {
right_value = dictAt(thread, right, key, hash);
if (right_value.isErrorNotFound()) {
return Bool::falseObj();
}
if (left_value == right_value) {
continue;
}
result = Interpreter::compareOperation(thread, EQ, left_value, right_value);
if (result.isErrorException()) {
// equality comparison raised
return *result;
}
result = Interpreter::isTrue(thread, *result);
if (result != Bool::trueObj()) {
// bool conversion raised or returned false
return *result;
}
}
return Bool::trueObj();
}
RawObject dictItemIteratorNext(Thread* thread, const DictItemIterator& iter) {
HandleScope scope(thread);
Dict dict(&scope, iter.iterable());
Object key(&scope, NoneType::object());
Object value(&scope, NoneType::object());
word i = iter.index();
if (dictNextItem(dict, &i, &key, &value)) {
iter.setIndex(i);
iter.setNumFound(iter.numFound() + 1);
return thread->runtime()->newTupleWith2(key, value);
}
// We hit the end.
iter.setIndex(i);
return Error::noMoreItems();
}
RawObject dictKeyIteratorNext(Thread* thread, const DictKeyIterator& iter) {
HandleScope scope(thread);
Dict dict(&scope, iter.iterable());
Object key(&scope, NoneType::object());
word i = iter.index();
if (dictNextKey(dict, &i, &key)) {
iter.setIndex(i);
iter.setNumFound(iter.numFound() + 1);
return *key;
}
// We hit the end.
iter.setIndex(i);
return Error::noMoreItems();
}
RawObject dictValueIteratorNext(Thread* thread, const DictValueIterator& iter) {
HandleScope scope(thread);
Dict dict(&scope, iter.iterable());
Object value(&scope, NoneType::object());
word i = iter.index();
if (dictNextValue(dict, &i, &value)) {
iter.setIndex(i);
iter.setNumFound(iter.numFound() + 1);
return *value;
}
// We hit the end.
iter.setIndex(i);
return Error::noMoreItems();
}
static const BuiltinAttribute kDictAttributes[] = {
{ID(_dict__num_items), RawDict::kNumItemsOffset, AttributeFlags::kHidden},
{ID(_dict__data), RawDict::kDataOffset, AttributeFlags::kHidden},
{ID(_dict__sparse), RawDict::kIndicesOffset, AttributeFlags::kHidden},
{ID(_dict__first_empty_item_index), RawDict::kFirstEmptyItemIndexOffset,
AttributeFlags::kHidden},
};
static const BuiltinAttribute kDictItemIteratorAttributes[] = {
{ID(_dict_item_iterator__iterable), RawDictItemIterator::kIterableOffset,
AttributeFlags::kHidden},
{ID(_dict_item_iterator__index), RawDictItemIterator::kIndexOffset,
AttributeFlags::kHidden},
{ID(_dict_item_iterator__num_found), RawDictItemIterator::kNumFoundOffset,
AttributeFlags::kHidden},
};
static const BuiltinAttribute kDictItemsAttributes[] = {
{ID(_dict_items__dict), RawDictItems::kDictOffset, AttributeFlags::kHidden},
};
static const BuiltinAttribute kDictKeyIteratorAttributes[] = {
{ID(_dict_key_iterator__iterable), RawDictKeyIterator::kIterableOffset,
AttributeFlags::kHidden},
{ID(_dict_key_iterator__index), RawDictKeyIterator::kIndexOffset,
AttributeFlags::kHidden},
{ID(_dict_key_iterator__num_found), RawDictKeyIterator::kNumFoundOffset,
AttributeFlags::kHidden},
};
static const BuiltinAttribute kDictKeysAttributes[] = {
{ID(_dict_keys__dict), RawDictKeys::kDictOffset, AttributeFlags::kHidden},
};
static const BuiltinAttribute kDictValueIteratorAttributes[] = {
{ID(_dict_value_iterator__iterable), RawDictValueIterator::kIterableOffset,
AttributeFlags::kHidden},
{ID(_dict_value_iterator__index), RawDictValueIterator::kIndexOffset,
AttributeFlags::kHidden},
{ID(_dict_value_iterator__num_found), RawDictValueIterator::kNumFoundOffset,
AttributeFlags::kHidden},
};
static const BuiltinAttribute kDictValuesAttributes[] = {
{ID(_dict_values__dict), RawDictValues::kDictOffset,
AttributeFlags::kHidden},
};
void initializeDictTypes(Thread* thread) {
addBuiltinType(thread, ID(dict), LayoutId::kDict,
/*superclass_id=*/LayoutId::kObject, kDictAttributes,
Dict::kSize, /*basetype=*/true);
addBuiltinType(thread, ID(dict_itemiterator), LayoutId::kDictItemIterator,
/*superclass_id=*/LayoutId::kObject,
kDictItemIteratorAttributes, DictItemIterator::kSize,
/*basetype=*/false);
addBuiltinType(thread, ID(dict_items), LayoutId::kDictItems,
/*superclass_id=*/LayoutId::kObject, kDictItemsAttributes,
DictItems::kSize, /*basetype=*/false);
addBuiltinType(thread, ID(dict_keyiterator), LayoutId::kDictKeyIterator,
/*superclass_id=*/LayoutId::kObject,
kDictKeyIteratorAttributes, DictKeyIterator::kSize,
/*basetype=*/false);
addBuiltinType(thread, ID(dict_keys), LayoutId::kDictKeys,
/*superclass_id=*/LayoutId::kObject, kDictKeysAttributes,
DictKeys::kSize, /*basetype=*/false);
addBuiltinType(thread, ID(dict_valueiterator), LayoutId::kDictValueIterator,
/*superclass_id=*/LayoutId::kObject,
kDictValueIteratorAttributes, DictValueIterator::kSize,
/*basetype=*/false);
addBuiltinType(thread, ID(dict_values), LayoutId::kDictValues,
/*superclass_id=*/LayoutId::kObject, kDictValuesAttributes,
DictValues::kSize, /*basetype=*/false);
}
RawObject METH(dict, clear)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
dictClear(thread, dict);
return NoneType::object();
}
RawObject METH(dict, __delitem__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object key(&scope, args.get(1));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
Object hash_obj(&scope, Interpreter::hash(thread, key));
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
// Remove the key. If it doesn't exist, throw a KeyError.
if (dictRemove(thread, dict, key, hash).isError()) {
return thread->raise(LayoutId::kKeyError, *key);
}
return NoneType::object();
}
RawObject METH(dict, __eq__)(Thread* thread, Arguments args) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object self_obj(&scope, args.get(0));
if (!runtime->isInstanceOfDict(*self_obj)) {
return thread->raiseRequiresType(self_obj, ID(dict));
}
Object other_obj(&scope, args.get(1));
if (!runtime->isInstanceOfDict(*other_obj)) {
return NotImplementedType::object();
}
Dict a(&scope, *self_obj);
Dict b(&scope, *other_obj);
return dictEq(thread, a, b);
}
RawObject METH(dict, __len__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
return SmallInt::fromWord(dict.numItems());
}
RawObject METH(dict, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
// .iter() on a dict returns a keys iterator
return runtime->newDictKeyIterator(thread, dict);
}
RawObject METH(dict, items)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
return runtime->newDictItems(thread, dict);
}
RawObject METH(dict, keys)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
return runtime->newDictKeys(thread, dict);
}
RawObject METH(dict, popitem)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
if (dict.numItems() == 0) {
return thread->raiseWithFmt(LayoutId::kKeyError,
"popitem(): dictionary is empty");
}
MutableTuple data(&scope, dict.data());
for (word item_index = dict.firstEmptyItemIndex() - kItemNumPointers;
item_index >= 0; item_index -= kItemNumPointers) {
if (!itemIsEmpty(*data, item_index) &&
!itemIsTombstone(*data, item_index)) {
Object key(&scope, itemKey(*data, item_index));
Object value(&scope, itemValue(*data, item_index));
Tuple result(&scope, runtime->newTupleWith2(key, value));
// Find the item for the entry and set a tombstone in it.
// Note that this would take amortized cost O(1).
word indices_index = -1;
MutableBytes indices(&scope, dict.indices());
uword perturb;
word indices_mask;
word hash = itemHash(*data, item_index);
word num_indices = dict.numIndices();
for (word current_index =
probeBegin(num_indices, hash, &indices_mask, &perturb);
; current_index = probeNext(current_index, indices_mask, &perturb)) {
word bytes_offset = indexOffset(current_index);
uint32_t comp;
if (indicesIsFull(*indices, bytes_offset, &comp) &&
comp == item_index) {
indices_index = current_index;
break;
}
}
DCHECK(indices_index >= 0, "cannot find index for entry in dict.sparse");
itemSetTombstone(*data, item_index);
indicesSetTombstone(*indices, indexOffset(indices_index));
dict.setNumItems(dict.numItems() - 1);
return *result;
}
}
UNREACHABLE("dict.numItems() > 0, but couldn't find any active item");
}
RawObject METH(dict, values)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
return runtime->newDictValues(thread, dict);
}
RawObject METH(dict, __new__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object type_obj(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfType(*type_obj)) {
return thread->raiseWithFmt(LayoutId::kTypeError, "not a type object");
}
Type type(&scope, *type_obj);
if (type.builtinBase() != LayoutId::kDict) {
return thread->raiseWithFmt(LayoutId::kTypeError, "not a subtype of dict");
}
Layout layout(&scope, type.instanceLayout());
Dict result(&scope, runtime->newInstance(layout));
result.setNumItems(0);
result.setData(SmallInt::fromWord(0));
result.setIndices(SmallInt::fromWord(0));
result.setFirstEmptyItemIndex(0);
return *result;
}
RawObject METH(dict, __contains__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object key(&scope, args.get(1));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Object hash_obj(&scope, Interpreter::hash(thread, key));
if (hash_obj.isErrorException()) {
return *hash_obj;
}
Dict dict(&scope, *self);
word hash = SmallInt::cast(*hash_obj).value();
return dictIncludes(thread, dict, key, hash);
}
RawObject METH(dict, pop)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object key(&scope, args.get(1));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfDict(*self)) {
return thread->raiseRequiresType(self, ID(dict));
}
Dict dict(&scope, *self);
Object hash_obj(&scope, Interpreter::hash(thread, key));
if (hash_obj.isErrorException()) {
return *hash_obj;
}
word hash = SmallInt::cast(*hash_obj).value();
Object result(&scope, dictRemove(thread, dict, key, hash));
if (result.isErrorNotFound()) {
Object default_obj(&scope, args.get(2));
return default_obj.isUnbound() ? thread->raise(LayoutId::kKeyError, *key)
: *default_obj;
}
return *result;
}
// TODO(T35787656): Instead of re-writing everything for every class, make a
// helper function that takes a member function (type check) and string for the
// Python symbol name
RawObject METH(dict_itemiterator, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictItemIterator()) {
return thread->raiseRequiresType(self, ID(dict_itemiterator));
}
return *self;
}
RawObject METH(dict_itemiterator, __next__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictItemIterator()) {
return thread->raiseRequiresType(self, ID(dict_itemiterator));
}
DictItemIterator iter(&scope, *self);
Object value(&scope, dictItemIteratorNext(thread, iter));
if (value.isError()) {
return thread->raise(LayoutId::kStopIteration, NoneType::object());
}
return *value;
}
RawObject METH(dict_itemiterator, __length_hint__)(Thread* thread,
Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictItemIterator()) {
return thread->raiseRequiresType(self, ID(dict_itemiterator));
}
DictItemIterator iter(&scope, *self);
Dict dict(&scope, iter.iterable());
return SmallInt::fromWord(dict.numItems() - iter.numFound());
}
RawObject METH(dict_items, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictItems()) {
return thread->raiseRequiresType(self, ID(dict_items));
}
Dict dict(&scope, DictItems::cast(*self).dict());
return thread->runtime()->newDictItemIterator(thread, dict);
}
RawObject METH(dict_items, __len__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictItems()) {
return thread->raiseRequiresType(self, ID(dict_items));
}
Dict dict(&scope, DictItems::cast(*self).dict());
return thread->runtime()->newInt(dict.numItems());
}
RawObject METH(dict_keyiterator, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictKeyIterator()) {
return thread->raiseRequiresType(self, ID(dict_keyiterator));
}
return *self;
}
RawObject METH(dict_keyiterator, __next__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictKeyIterator()) {
return thread->raiseRequiresType(self, ID(dict_keyiterator));
}
DictKeyIterator iter(&scope, *self);
Object value(&scope, dictKeyIteratorNext(thread, iter));
if (value.isError()) {
return thread->raise(LayoutId::kStopIteration, NoneType::object());
}
return *value;
}
RawObject METH(dict_keyiterator, __length_hint__)(Thread* thread,
Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictKeyIterator()) {
return thread->raiseRequiresType(self, ID(dict_keyiterator));
}
DictKeyIterator iter(&scope, *self);
Dict dict(&scope, iter.iterable());
return SmallInt::fromWord(dict.numItems() - iter.numFound());
}
RawObject METH(dict_keys, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictKeys()) {
return thread->raiseRequiresType(self, ID(dict_keys));
}
Dict dict(&scope, DictKeys::cast(*self).dict());
return thread->runtime()->newDictKeyIterator(thread, dict);
}
RawObject METH(dict_keys, __len__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictKeys()) {
return thread->raiseRequiresType(self, ID(dict_keys));
}
Dict dict(&scope, DictKeys::cast(*self).dict());
return thread->runtime()->newInt(dict.numItems());
}
RawObject METH(dict_valueiterator, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictValueIterator()) {
return thread->raiseRequiresType(self, ID(dict_valueiterator));
}
return *self;
}
RawObject METH(dict_valueiterator, __next__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictValueIterator()) {
return thread->raiseRequiresType(self, ID(dict_valueiterator));
}
DictValueIterator iter(&scope, *self);
Object value(&scope, dictValueIteratorNext(thread, iter));
if (value.isError()) {
return thread->raise(LayoutId::kStopIteration, NoneType::object());
}
return *value;
}
RawObject METH(dict_valueiterator, __length_hint__)(Thread* thread,
Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictValueIterator()) {
return thread->raiseRequiresType(self, ID(dict_valueiterator));
}
DictValueIterator iter(&scope, *self);
Dict dict(&scope, iter.iterable());
return SmallInt::fromWord(dict.numItems() - iter.numFound());
}
RawObject METH(dict_values, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictValues()) {
return thread->raiseRequiresType(self, ID(dict_values));
}
Dict dict(&scope, DictValues::cast(*self).dict());
return thread->runtime()->newDictValueIterator(thread, dict);
}
RawObject METH(dict_values, __len__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isDictValues()) {
return thread->raiseRequiresType(self, ID(dict_values));
}
Dict dict(&scope, DictValues::cast(*self).dict());
return thread->runtime()->newInt(dict.numItems());
}
} // namespace py