runtime/list-builtins.cpp (540 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "list-builtins.h"
#include "builtins.h"
#include "frame.h"
#include "globals.h"
#include "int-builtins.h"
#include "objects.h"
#include "runtime.h"
#include "slice-builtins.h"
#include "thread.h"
#include "tuple-builtins.h"
#include "type-builtins.h"
namespace py {
void listExtend(Thread* thread, const List& dst, const Tuple& src,
word src_length) {
if (src_length == 0) {
return;
}
word old_length = dst.numItems();
word new_length = old_length + src_length;
thread->runtime()->listEnsureCapacity(thread, dst, new_length);
dst.setNumItems(new_length);
MutableTuple::cast(dst.items()).replaceFromWith(old_length, *src, src_length);
}
void listInsert(Thread* thread, const List& list, const Object& value,
word index) {
thread->runtime()->listAdd(thread, list, value);
word last_index = list.numItems() - 1;
if (index < 0) {
index = last_index + index;
}
index = Utils::maximum(word{0}, Utils::minimum(last_index, index));
// Shift elements over to the right
list.replaceFromWithStartAt(index + 1, *list, last_index - index, index);
list.atPut(index, *value);
}
RawObject listPop(Thread* thread, const List& list, word index) {
HandleScope scope(thread);
Object popped(&scope, list.at(index));
list.atPut(index, NoneType::object());
word last_index = list.numItems() - 1;
if (index < last_index) {
list.replaceFromWithStartAt(index, *list, last_index - index, index + 1);
}
list.setNumItems(last_index);
return *popped;
}
RawObject listReplicate(Thread* thread, const List& list, word ntimes) {
Runtime* runtime = thread->runtime();
word len = list.numItems();
word result_len = ntimes * len;
if (result_len == 0) {
return runtime->newList();
}
HandleScope scope(thread);
Tuple list_items(&scope, list.items());
MutableTuple items(&scope, runtime->newMutableTuple(result_len));
for (word i = 0; i < result_len; i += len) {
items.replaceFromWith(i, *list_items, len);
}
List result(&scope, runtime->newList());
result.setItems(*items);
result.setNumItems(result_len);
return *result;
}
void listReverse(Thread* thread, const List& list) {
if (list.numItems() == 0) {
return;
}
HandleScope scope(thread);
Object elt(&scope, NoneType::object());
for (word low = 0, high = list.numItems() - 1; low < high; ++low, --high) {
elt = list.at(low);
list.atPut(low, list.at(high));
list.atPut(high, *elt);
}
}
RawObject listSlice(Thread* thread, const List& list, word start, word stop,
word step) {
Runtime* runtime = thread->runtime();
word length = Slice::length(start, stop, step);
if (length == 0) {
return runtime->newList();
}
HandleScope scope(thread);
MutableTuple items(&scope, runtime->newMutableTuple(length));
Tuple src(&scope, list.items());
for (word i = 0, j = start; i < length; i++, j += step) {
items.atPut(i, src.at(j));
}
List result(&scope, runtime->newList());
result.setItems(*items);
result.setNumItems(length);
return *result;
}
// TODO(T63900795): Investigate this threshold on a realistic benchmark.
static const int kListInsertionSortSize = 8;
static RawObject objectLessThan(Thread* thread, const Object& left,
const Object& right,
const Object& compare_func) {
if (left.isSmallInt() && right.isSmallInt()) {
return Bool::fromBool(SmallInt::cast(*left).value() <
SmallInt::cast(*right).value());
}
if (left.isStr() && right.isStr()) {
return Bool::fromBool(Str::cast(*left).compare(Str::cast(*right)) < 0);
}
return Interpreter::call2(thread, compare_func, left, right);
}
static RawObject listInsertionSort(Thread* thread, const MutableTuple& data,
const Object& compare_func, word start,
word end) {
HandleScope scope(thread);
Object item_i(&scope, NoneType::object());
Object item_j(&scope, NoneType::object());
Object compare_result(&scope, NoneType::object());
for (word i = start + 1; i < end; i++) {
item_i = data.at(i);
word j = i - 1;
for (; j >= start; j--) {
item_j = data.at(j);
compare_result = objectLessThan(thread, item_i, item_j, compare_func);
if (compare_result.isError()) {
return *compare_result;
}
compare_result = Interpreter::isTrue(thread, *compare_result);
if (compare_result.isError()) {
return *compare_result;
}
if (!Bool::cast(*compare_result).value()) {
break;
}
data.atPut(j + 1, *item_j);
}
data.atPut(j + 1, *item_i);
}
return NoneType::object();
}
// Merge 2 sublists, input[left: right], input[right: end] into
// output[left: end].
static RawObject listMerge(Thread* thread, const MutableTuple& input,
const MutableTuple& output,
const Object& compare_func, word left, word right,
word end) {
HandleScope scope(thread);
Object item_i(&scope, NoneType::object());
Object item_j(&scope, NoneType::object());
Object compare_result(&scope, NoneType::object());
word i = left;
word j = right;
word k = left;
while (i < right && j < end) {
item_i = input.at(i);
item_j = input.at(j);
compare_result = objectLessThan(thread, item_j, item_i, compare_func);
if (compare_result.isError()) {
return *compare_result;
}
compare_result = Interpreter::isTrue(thread, *compare_result);
if (compare_result.isError()) {
return *compare_result;
}
if (*compare_result == Bool::trueObj()) {
output.atPut(k++, *item_j);
j++;
} else {
DCHECK(compare_result == Bool::falseObj(), "expected to be false");
output.atPut(k++, *item_i);
i++;
}
}
for (; i < right; i++) {
output.atPut(k++, input.at(i));
}
for (; j < end; j++) {
output.atPut(k++, input.at(j));
}
DCHECK(k == end, "sublists were not fully copied");
return NoneType::object();
}
RawObject listSort(Thread* thread, const List& list) {
return listSortWithCompareMethod(thread, list, ID(_lt));
}
// This uses an adaptation of merge sort. It sorts sublists of size
// kListInsertionSortSize or less using insertion sort first. Afterwards, it is
// using a bottom-up merge sort to increase the sublists' size by power of 2.
// Also, this function allocates a temporary space to ease merging and swaps
// `input` and `output` to avoid further allocation.
// TODO(T39107329): Consider using Timsort for further optimization.
RawObject listSortWithCompareMethod(Thread* thread, const List& list,
SymbolId compare_method) {
word num_items = list.numItems();
if (num_items == 0) {
return NoneType::object();
}
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Object compare_func(&scope, runtime->lookupNameInModule(thread, ID(_builtins),
compare_method));
if (compare_func.isError()) return *compare_func;
MutableTuple input(&scope, list.items());
Object compare_result(&scope, NoneType::object());
for (word left = 0; left < num_items; left += kListInsertionSortSize) {
word right = Utils::minimum(left + kListInsertionSortSize, num_items);
compare_result =
listInsertionSort(thread, input, compare_func, left, right);
if (compare_result.isError()) {
return *compare_result;
}
}
if (num_items <= kListInsertionSortSize) {
// The input list is small enough to be sorted by insertion sort.
return NoneType::object();
}
MutableTuple output(&scope, runtime->newMutableTuple(input.length()));
Object tmp(&scope, NoneType::object());
for (word width = kListInsertionSortSize; width < num_items; width *= 2) {
for (word left = 0; left < num_items; left += width * 2) {
word right = Utils::minimum(num_items, left + width);
word end = Utils::minimum(num_items, left + width * 2);
compare_result =
listMerge(thread, input, output, compare_func, left, right, end);
if (compare_result.isError()) {
return *compare_result;
}
}
tmp = *output;
output = *input;
input = MutableTuple::cast(*tmp);
}
list.setItems(*input);
return NoneType::object();
}
RawObject listIteratorNext(Thread* thread, const ListIterator& iter) {
HandleScope scope(thread);
word idx = iter.index();
List underlying(&scope, iter.iterable());
if (idx >= underlying.numItems()) {
return Error::outOfBounds();
}
RawObject item = underlying.at(idx);
iter.setIndex(idx + 1);
return item;
}
static const BuiltinAttribute kListAttributes[] = {
{ID(_list__items), RawList::kItemsOffset, AttributeFlags::kHidden},
{ID(_list__num_items), RawList::kNumItemsOffset, AttributeFlags::kHidden},
};
static const BuiltinAttribute kListIteratorAttributes[] = {
{ID(_list_iterator__iterable), RawListIterator::kIterableOffset,
AttributeFlags::kHidden},
{ID(_list_iterator__index), RawListIterator::kIndexOffset,
AttributeFlags::kHidden},
};
void initializeListTypes(Thread* thread) {
addBuiltinType(thread, ID(list), LayoutId::kList,
/*superclass_id=*/LayoutId::kObject, kListAttributes,
List::kSize, /*basetype=*/true);
addBuiltinType(thread, ID(list_iterator), LayoutId::kListIterator,
/*superclass_id=*/LayoutId::kObject, kListIteratorAttributes,
ListIterator::kSize, /*basetype=*/false);
}
RawObject METH(list, __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::kList) {
return thread->raiseWithFmt(LayoutId::kTypeError, "not a subtype of list");
}
Layout layout(&scope, type.instanceLayout());
List result(&scope, runtime->newInstance(layout));
result.setNumItems(0);
result.setItems(runtime->emptyTuple());
return *result;
}
RawObject METH(list, __add__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self_obj(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfList(*self_obj)) {
return thread->raiseRequiresType(self_obj, ID(list));
}
Object other_obj(&scope, args.get(1));
if (!runtime->isInstanceOfList(*other_obj)) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"can only concatenate list to list");
}
List self(&scope, *self_obj);
List other(&scope, *other_obj);
List new_list(&scope, runtime->newList());
word self_len = self.numItems();
word other_len = other.numItems();
runtime->listEnsureCapacity(thread, new_list, self_len + other_len);
Tuple self_items(&scope, self.items());
Tuple other_items(&scope, other.items());
listExtend(thread, new_list, self_items, self_len);
listExtend(thread, new_list, other_items, other_len);
return *new_list;
}
RawObject listContains(Thread* thread, const List& list, const Object& key) {
for (word i = 0, num_items = list.numItems(); i < num_items; ++i) {
RawObject result = Runtime::objectEquals(thread, *key, list.at(i));
if (result == Bool::trueObj()) {
return Bool::trueObj();
}
if (result.isErrorException()) return result;
}
return Bool::falseObj();
}
RawObject METH(list, __contains__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self_obj(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfList(*self_obj)) {
return thread->raiseRequiresType(self_obj, ID(list));
}
List self(&scope, *self_obj);
Object key(&scope, args.get(1));
return listContains(thread, self, key);
}
RawObject METH(list, __eq__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Object self_obj(&scope, args.get(0));
if (!runtime->isInstanceOfList(*self_obj)) {
return thread->raiseRequiresType(self_obj, ID(list));
}
Object other_obj(&scope, args.get(1));
if (!runtime->isInstanceOfList(*other_obj)) {
return NotImplementedType::object();
}
if (self_obj == other_obj) {
return Bool::trueObj();
}
List self(&scope, *self_obj);
List other(&scope, *other_obj);
word num_items = self.numItems();
if (num_items != other.numItems()) {
return Bool::falseObj();
}
Tuple self_items(&scope, self.items());
Tuple other_items(&scope, other.items());
for (word i = 0; i < num_items; i++) {
RawObject self_item = self_items.at(i);
RawObject other_item = other_items.at(i);
if (self_item != other_item) {
RawObject equals = Runtime::objectEquals(thread, self_item, other_item);
if (equals != Bool::trueObj()) {
return equals;
}
}
}
return Bool::trueObj();
}
RawObject METH(list, clear)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfList(*self)) {
return thread->raiseRequiresType(self, ID(list));
}
List list(&scope, *self);
list.clearFrom(0);
return NoneType::object();
}
RawObject METH(list, __len__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfList(*self)) {
return thread->raiseRequiresType(self, ID(list));
}
List list(&scope, *self);
return SmallInt::fromWord(list.numItems());
}
RawObject METH(list, insert)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfList(*self)) {
return thread->raiseRequiresType(self, ID(list));
}
List list(&scope, *self);
Object index_obj(&scope, args.get(1));
index_obj = intFromIndex(thread, index_obj);
if (index_obj.isError()) return *index_obj;
Int index_int(&scope, intUnderlying(*index_obj));
if (index_int.isLargeInt()) {
return thread->raiseWithFmt(LayoutId::kOverflowError,
"Python int too large to convert to C ssize_t");
}
word index = index_int.asWord();
Object value(&scope, args.get(2));
listInsert(thread, list, value, index);
return NoneType::object();
}
RawObject METH(list, __mul__)(Thread* thread, Arguments args) {
RawObject other = args.get(1);
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfList(*self)) {
return thread->raiseRequiresType(self, ID(list));
}
if (other.isSmallInt()) {
word ntimes = SmallInt::cast(other).value();
if (ntimes <= 0) return runtime->newList();
List list(&scope, *self);
return listReplicate(thread, list, ntimes);
}
return thread->raiseWithFmt(LayoutId::kTypeError,
"can't multiply list by non-int");
}
RawObject METH(list, pop)(Thread* thread, Arguments args) {
if (!args.get(1).isUnbound() && !args.get(1).isSmallInt()) {
return thread->raiseWithFmt(
LayoutId::kTypeError,
"index object cannot be interpreted as an integer");
}
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfList(*self)) {
return thread->raiseRequiresType(self, ID(list));
}
List list(&scope, *self);
word length = list.numItems();
if (length == 0) {
return thread->raiseWithFmt(LayoutId::kIndexError, "pop from empty list");
}
word index = length - 1;
if (!args.get(1).isUnbound()) {
index = SmallInt::cast(args.get(1)).value();
if (index < 0) index += length;
}
if (index < 0 || index >= length) {
return thread->raiseWithFmt(LayoutId::kIndexError,
"pop index out of range");
}
return listPop(thread, list, index);
}
RawObject METH(list, remove)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self_obj(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfList(*self_obj)) {
return thread->raiseRequiresType(self_obj, ID(list));
}
Object value(&scope, args.get(1));
List self(&scope, *self_obj);
Object item(&scope, NoneType::object());
Object comp_result(&scope, NoneType::object());
Object found(&scope, NoneType::object());
for (word i = 0, num_items = self.numItems(); i < num_items; ++i) {
item = self.at(i);
if (*value == *item) {
listPop(thread, self, i);
return NoneType::object();
}
comp_result =
Interpreter::compareOperation(thread, CompareOp::EQ, item, value);
if (comp_result.isError()) return *comp_result;
found = Interpreter::isTrue(thread, *comp_result);
if (found.isError()) return *found;
if (found == Bool::trueObj()) {
listPop(thread, self, i);
return NoneType::object();
}
}
return thread->raiseWithFmt(LayoutId::kValueError,
"list.remove(x) x not in list");
}
RawObject METH(list, __imul__)(Thread* thread, Arguments args) {
RawObject other = args.get(1);
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfList(*self)) {
return thread->raiseRequiresType(self, ID(list));
}
Object count_index(&scope, args.get(1));
Object count_obj(&scope, intFromIndex(thread, count_index));
if (count_obj.isError()) return *count_obj;
word count = intUnderlying(*count_obj).asWordSaturated();
if (!SmallInt::isValid(count)) {
return thread->raiseWithFmt(LayoutId::kOverflowError,
"cannot fit '%T' into an index-sized integer",
&count_index);
}
word ntimes = SmallInt::cast(other).value();
if (ntimes == 1) {
return *self;
}
List list(&scope, *self);
if (ntimes <= 0) {
list.clearFrom(0);
return *list;
}
word new_length;
word len = list.numItems();
if (__builtin_mul_overflow(len, ntimes, &new_length) ||
!SmallInt::isValid(new_length)) {
return thread->raiseMemoryError();
}
if (new_length == len) {
return *list;
}
runtime->listEnsureCapacity(thread, list, new_length);
list.setNumItems(new_length);
for (word i = 0; i < ntimes; i++) {
list.replaceFromWith(i * len, *list, len);
}
return *list;
}
RawObject METH(list, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfList(*self)) {
return thread->raiseRequiresType(self, ID(list));
}
return thread->runtime()->newListIterator(self);
}
RawObject METH(list_iterator, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isListIterator()) {
return thread->raiseRequiresType(self, ID(list_iterator));
}
return *self;
}
RawObject METH(list_iterator, __next__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self_obj(&scope, args.get(0));
if (!self_obj.isListIterator()) {
return thread->raiseRequiresType(self_obj, ID(list_iterator));
}
ListIterator self(&scope, *self_obj);
Object value(&scope, listIteratorNext(thread, self));
if (value.isError()) {
return thread->raise(LayoutId::kStopIteration, NoneType::object());
}
return *value;
}
RawObject METH(list_iterator, __length_hint__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isListIterator()) {
return thread->raiseRequiresType(self, ID(list_iterator));
}
ListIterator list_iterator(&scope, *self);
List list(&scope, list_iterator.iterable());
return SmallInt::fromWord(list.numItems() - list_iterator.index());
}
} // namespace py