diff --git a/tests/jit/test_array.py b/tests/jit/test_array.py index abb207c40..74efe67af 100644 --- a/tests/jit/test_array.py +++ b/tests/jit/test_array.py @@ -8,39 +8,44 @@ def test_subscript_assign_simple(self, topaz, tmpdir): 10000.times { arr[0] = true } """) self.assert_matches(traces[0].loop, """ - label(p0, p1, p3, p4, p5, p6, p7, p8, p10, p13, i69, p21, p24, p26, p28, i40, p37, p51, p66, descr=TargetToken(4310782200)) + label(p0, p1, p3, p4, p5, p6, p7, p8, p10, p13, i74, p21, p24, p26, p28, i40, p37, p67, p56, p43, p42, p46, p53, p47, p45, p51, descr=TargetToken(4311183872)) debug_merge_point(0, 0, 'times at LOAD_DEREF') debug_merge_point(0, 0, 'times at LOAD_SELF') debug_merge_point(0, 0, 'times at SEND') setfield_gc(p28, 42, descr=) - guard_not_invalidated(descr=) - p72 = force_token() - i73 = int_lt(i69, i40) - guard_true(i73, descr=) + guard_not_invalidated(descr=) + p76 = force_token() + i77 = int_lt(i74, i40) + guard_true(i77, descr=) debug_merge_point(0, 0, 'times at JUMP_IF_FALSE') debug_merge_point(0, 0, 'times at LOAD_DEREF') debug_merge_point(0, 0, 'times at YIELD') - p74 = force_token() + p78 = force_token() debug_merge_point(1, 1, 'block in
at LOAD_DEREF') debug_merge_point(1, 1, 'block in
at LOAD_CONST') debug_merge_point(1, 1, 'block in
at BUILD_ARRAY') debug_merge_point(1, 1, 'block in
at LOAD_CONST') debug_merge_point(1, 1, 'block in
at BUILD_ARRAY') debug_merge_point(1, 1, 'block in
at SEND_SPLAT') - p75 = force_token() + p79 = force_token() + i80 = getfield_gc(p67, descr=) + i81 = int_ge(0, i80) + setfield_gc(p28, 20, descr=) + guard_false(i81, descr=) + p82 = getfield_gc(p67, descr=) debug_merge_point(1, 1, 'block in
at RETURN') debug_merge_point(0, 0, 'times at DISCARD_TOP') debug_merge_point(0, 0, 'times at LOAD_DEREF') debug_merge_point(0, 0, 'times at LOAD_CONST') debug_merge_point(0, 0, 'times at SEND') - p76 = force_token() - i77 = int_add(i69, 1) + p83 = force_token() + i84 = int_add(i74, 1) debug_merge_point(0, 0, 'times at STORE_DEREF') debug_merge_point(0, 0, 'times at DISCARD_TOP') debug_merge_point(0, 0, 'times at JUMP') debug_merge_point(0, 0, 'times at LOAD_DEREF') setfield_gc(p28, 63, descr=) - i78 = arraylen_gc(p51, descr=) - i79 = arraylen_gc(p66, descr=) - jump(p0, p1, p3, p4, p5, p6, p7, p8, p10, p13, i77, p21, p24, p26, p28, i40, p37, p51, p66, descr=TargetToken(4310782200)) + setarrayitem_gc(p82, 0, ConstPtr(ptr58), descr=) + i85 = arraylen_gc(p51, descr=) + jump(p0, p1, p3, p4, p5, p6, p7, p8, p10, p13, i84, p21, p24, p26, p28, i40, p37, p67, p56, p43, p42, p46, p53, p47, p45, p51, descr=TargetToken(4311183872)) """) diff --git a/tests/objects/test_arrayobject.py b/tests/objects/test_arrayobject.py index 6ddbca1db..dffe446c0 100644 --- a/tests/objects/test_arrayobject.py +++ b/tests/objects/test_arrayobject.py @@ -203,6 +203,17 @@ def test_dup(self, space): assert x == [1, 2, 3, 4] assert y == [1, 2, 3] + def test_dupf(self, space): + w_res = space.execute(""" + x = [1.0, 2.0, 3.0] + y = x.dup + x << 4 + return [x, y] + """) + x, y = self.unwrap(space, w_res) + assert x == [1.0, 2.0, 3.0, 4] + assert y == [1.0, 2.0, 3.0] + def test_compact(self, space): w_res = space.execute("return ['a', nil, 'b', nil, 'c'].compact") assert self.unwrap(space, w_res) == ['a', 'b', 'c'] diff --git a/tests/objects/test_rangeobject.py b/tests/objects/test_rangeobject.py index 8bd4ac225..5e59e5a83 100644 --- a/tests/objects/test_rangeobject.py +++ b/tests/objects/test_rangeobject.py @@ -84,3 +84,9 @@ def test_each_returns_self(self, space): return r.each {}.equal?(r) """) assert w_res is space.w_true + + def test_array_conversion(self, space): + w_res = space.execute(""" + return (1..4).to_a + """) + assert self.unwrap(space, w_res) == [1, 2, 3, 4] diff --git a/topaz/frame.py b/topaz/frame.py index 190fa1d43..03ea245da 100644 --- a/topaz/frame.py +++ b/topaz/frame.py @@ -14,7 +14,7 @@ def __init__(self): class Frame(BaseFrame): - _virtualizable2_ = [ + _virtualizable_ = [ "bytecode", "localsstack_w[*]", "stackpos", "w_self", "block", "cells[*]", "lastblock", "lexical_scope", "last_instr", "parent_interp", "top_parent_interp", diff --git a/topaz/objects/arrayobject.py b/topaz/objects/arrayobject.py index 4a0997157..817eb7138 100644 --- a/topaz/objects/arrayobject.py +++ b/topaz/objects/arrayobject.py @@ -2,6 +2,8 @@ from rpython.rlib import jit from rpython.rlib.listsort import make_timsort_class +from rpython.rlib.objectmodel import specialize +from rpython.rlib.rerased import new_static_erasing_pair from rpython.rlib.rbigint import rbigint from topaz.coerce import Coerce @@ -15,6 +17,11 @@ BaseRubySortBy = make_timsort_class() +@specialize.call_location() +def unroll_heuristic(items_w): + return jit.isconstant(len(items_w)) and len(items_w) <= 20 + + class RubySorter(BaseRubySorter): def __init__(self, space, list, listlength=None, sortblock=None): BaseRubySorter.__init__(self, list, listlength=listlength) @@ -43,35 +50,288 @@ def lt(self, w_a, w_b): return self.space.int_w(w_cmp_res) < 0 +class BaseArrayStrategy(object): + def __init__(self, space): + pass + + def switch_to_object_strategy(self, space, w_ary): + obj_strategy = space.fromcache(ObjectArrayStrategy) + w_ary.array_storage = obj_strategy.erase(self.listview(space, w_ary)) + w_ary.strategy = obj_strategy + + +class TypedArrayStrategyMixin(object): + _mixin_ = True + + def get_empty_storage(self): + return self.erase([]) + + def length(self, w_ary): + return len(self.unerase(w_ary.array_storage)) + + def getitem(self, space, w_ary, idx): + return self.wrap(space, self.unerase(w_ary.array_storage)[idx]) + + def getslice(self, space, w_ary, start, end): + return self.erase(self.unerase(w_ary.array_storage)[start:end]) + + def setitem(self, space, w_ary, idx, w_obj): + if self.is_correct_type(space, w_obj): + self.unerase(w_ary.array_storage)[idx] = self.unwrap(space, w_obj) + else: + self.switch_to_object_strategy(space, w_ary) + w_ary.strategy.setitem(space, w_ary, idx, w_obj) + + def delslice(self, space, w_ary, start, end): + del self.unerase(w_ary.array_storage)[start:end] + + def append(self, space, w_ary, w_obj): + if self.is_correct_type(space, w_obj): + self.unerase(w_ary.array_storage).append(self.unwrap(space, w_obj)) + else: + self.switch_to_object_strategy(space, w_ary) + w_ary.append(space, w_obj) + + def append_empty(self, w_ary): + self.unerase(w_ary.array_storage).append(self.empty_value) + + @jit.look_inside_iff(lambda self, space, w_ary, other_w: unroll_heuristic(other_w)) + def extend(self, space, w_ary, other_w): + for w_o in other_w: + w_ary.append(space, w_o) + + def extend_from_storage(self, space, w_ary, data): + self.unerase(w_ary.array_storage).extend(self.unerase(data)) + + def insert(self, space, w_ary, idx, w_obj): + if self.is_correct_type(space, w_obj): + self.unerase(w_ary.array_storage).insert(idx, self.unwrap(space, w_obj)) + else: + self.switch_to_object_strategy(space, w_ary) + w_ary.strategy.insert(space, w_ary, idx, w_obj) + + @jit.look_inside_iff(lambda self, space, w_ary: unroll_heuristic(self.unerase(w_ary.array_storage))) + def listview(self, space, w_ary): + return [self.wrap(space, item) for item in self.unerase(w_ary.array_storage)] + + def pop(self, space, w_ary, idx): + storage = self.unerase(w_ary.array_storage) + return self.wrap(space, storage.pop(idx)) + + def reverse(self, space, w_ary): + storage = self.unerase(w_ary.array_storage) + storage.reverse() + + def clear(self, space, w_ary): + del self.unerase(w_ary.array_storage)[:] + + def mul(self, w_ary, n): + return self.erase(self.unerase(w_ary.array_storage) * n) + + def sort(self, space, w_ary, block): + # TODO: this should use an unboxed sorter if <=> has not been + # overwritten on the appropriate type + self.switch_to_object_strategy(space, w_ary) + w_ary.strategy.sort(space, w_ary, block) + + def sort_by(self, space, w_ary, block): + # TODO: this should use an unboxed sorter if <=> has not been + # overwritten on the appropriate type + self.switch_to_object_strategy(space, w_ary) + w_ary.strategy.sort_by(space, w_ary, block) + + def wrap(self, space, w_obj): + raise NotImplementedError + + def unwrap(self, space, w_obj): + raise NotImplementedError + + def erase(self, items): + raise NotImplementedError + + def unerase(self, items): + raise NotImplementedError + + +class ObjectArrayStrategy(BaseArrayStrategy, TypedArrayStrategyMixin): + erase, unerase = new_static_erasing_pair("object") + empty_value = None + + def wrap(self, space, w_obj): + return w_obj + + def unwrap(self, space, w_obj): + return w_obj + + def is_correct_type(self, space, w_obj): + return True + + def listview(self, space, w_ary): + return self.unerase(w_ary.array_storage) + + def extend(self, space, w_ary, other_w): + self.unerase(w_ary.array_storage).extend(other_w) + + def sort(self, space, w_ary, block): + RubySorter(space, self.unerase(w_ary.array_storage), sortblock=block).sort() + + def sort_by(self, space, w_ary, block): + RubySortBy(space, self.unerase(w_ary.array_storage), sortblock=block).sort() + + +class FloatArrayStrategy(BaseArrayStrategy, TypedArrayStrategyMixin): + erase, unerase = new_static_erasing_pair("FloatArrayStrategy") + empty_value = 0.0 + + def wrap(self, space, f): + return space.newfloat(f) + + def unwrap(self, space, w_f): + return space.float_w(w_f) + + def is_correct_type(self, space, w_obj): + return space.is_kind_of(w_obj, space.w_float) + + +class FixnumArrayStrategy(BaseArrayStrategy, TypedArrayStrategyMixin): + erase, unerase = new_static_erasing_pair("FixnumArrayStrategy") + empty_value = 0 + + def wrap(self, space, i): + return space.newint(i) + + def unwrap(self, space, w_i): + return space.int_w(w_i) + + def is_correct_type(self, space, w_obj): + return space.is_kind_of(w_obj, space.w_fixnum) + + +class EmptyArrayStrategy(BaseArrayStrategy): + erase, unerase = new_static_erasing_pair("EmptyArrayStrategy") + + def switch_to_correct_strategy(self, space, w_ary, w_obj): + if space.is_kind_of(w_obj, space.w_fixnum): + strategy = space.fromcache(FixnumArrayStrategy) + elif space.is_kind_of(w_obj, space.w_float): + strategy = space.fromcache(FloatArrayStrategy) + else: + strategy = space.fromcache(ObjectArrayStrategy) + w_ary.strategy = strategy + w_ary.array_storage = strategy.get_empty_storage() + + def get_empty_storage(self): + return self.erase(None) + + def length(self, w_ary): + return 0 + + def listview(self, space, w_ary): + return [] + + def is_correct_type(self, w_obj): + return False + + def getslice(self, space, w_ary, start, end): + return w_ary.array_storage + + def delslice(self, space, w_ary, start, end): + pass + + def append(self, space, w_ary, w_obj): + self.switch_to_correct_strategy(space, w_ary, w_obj) + w_ary.strategy.append(space, w_ary, w_obj) + + def insert(self, space, w_ary, idx, w_obj): + self.append(space, w_ary, w_obj) + + def extend(self, space, w_ary, other_w): + if not other_w: + return + strategy = W_ArrayObject.strategy_for_list(space, other_w) + w_ary.strategy = strategy + w_ary.array_storage = strategy.get_empty_storage() + w_ary.strategy.extend(space, w_ary, other_w) + + def clear(self, space, w_ary): + pass + + def reverse(self, space, w_ary): + pass + + def sort(self, space, w_ary, block): + pass + + def mul(self, w_ary, n): + return w_ary.array_storage + + class W_ArrayObject(W_Object): classdef = ClassDef("Array", W_Object.classdef) classdef.include_module(Enumerable) - def __init__(self, space, items_w, klass=None): + def __init__(self, space, strategy, storage, klass=None): W_Object.__init__(self, space, klass) - self.items_w = items_w + self.strategy = strategy + self.array_storage = storage def __deepcopy__(self, memo): obj = super(W_ArrayObject, self).__deepcopy__(memo) - obj.items_w = copy.deepcopy(self.items_w, memo) + obj.strategy = copy.deepcopy(self.strategy, memo) + obj.array_storage = copy.deepcopy(self.array_storage, memo) return obj def listview(self, space): - return self.items_w + return self.strategy.listview(space, self) def length(self): - return len(self.items_w) + return self.strategy.length(self) + + def append(self, space, w_obj): + self.strategy.append(space, self, w_obj) + + @staticmethod + def newarray(space, items_w): + strategy = space.fromcache(EmptyArrayStrategy) + w_ary = W_ArrayObject(space, strategy, strategy.get_empty_storage()) + w_ary.strategy.extend(space, w_ary, items_w) + return w_ary + + @staticmethod + @jit.look_inside_iff(lambda space, items_w: unroll_heuristic(items_w)) + def strategy_for_list(space, items_w): + if not items_w: + return space.fromcache(EmptyArrayStrategy) + + for w_item in items_w: + if not space.is_kind_of(w_item, space.w_fixnum): + break + else: + return space.fromcache(FixnumArrayStrategy) + + for w_item in items_w: + if not space.is_kind_of(w_item, space.w_float): + break + else: + return space.fromcache(FloatArrayStrategy) + + return space.fromcache(ObjectArrayStrategy) @classdef.singleton_method("allocate") def singleton_method_allocate(self, space): - return W_ArrayObject(space, [], self) + strategy = space.fromcache(EmptyArrayStrategy) + return W_ArrayObject(space, strategy, strategy.get_empty_storage(), self) + + def replace(self, space, other_w): + self.strategy = W_ArrayObject.strategy_for_list(space, other_w) + self.array_storage = self.strategy.get_empty_storage() + self.strategy.extend(space, self, other_w) @classdef.method("initialize_copy", other_w="array") @classdef.method("replace", other_w="array") @check_frozen() def method_replace(self, space, other_w): - del self.items_w[:] - self.items_w.extend(other_w) + self.replace(space, other_w) return self @classdef.method("[]") @@ -83,9 +343,10 @@ def method_subscript(self, space, w_idx, w_count=None): elif as_range: assert start >= 0 assert end >= 0 - return W_ArrayObject(space, self.items_w[start:end], space.getnonsingletonclass(self)) + data = self.strategy.getslice(space, self, start, end) + return W_ArrayObject(space, self.strategy, data, space.getnonsingletonclass(self)) else: - return self.items_w[start] + return self.strategy.getitem(space, self, start) @classdef.method("[]=") @check_frozen() @@ -109,8 +370,8 @@ def method_subscript_assign(self, space, w_idx, w_count_or_obj, w_obj=None): ) ) elif start >= self.length(): - self.items_w += [space.w_nil] * (start - self.length() + 1) - self.items_w[start] = w_obj + self.strategy.extend(space, self, [space.w_nil] * (start - self.length() + 1)) + self.strategy.setitem(space, self, start, w_obj) elif as_range: w_converted = space.convert_type(w_obj, space.w_array, "to_ary", raise_error=False) if w_converted is space.w_nil: @@ -119,22 +380,24 @@ def method_subscript_assign(self, space, w_idx, w_count_or_obj, w_obj=None): rep_w = space.listview(w_converted) self._subscript_assign_range(space, start, end, rep_w) else: - self.items_w[start] = w_obj + self.strategy.setitem(space, self, start, w_obj) return w_obj def _subscript_assign_range(self, space, start, end, rep_w): assert end >= 0 delta = (end - start) - len(rep_w) if delta < 0: - self.items_w += [None] * -delta + for i in xrange(-delta): + self.strategy.append_empty(self) lim = start + len(rep_w) i = self.length() - 1 while i >= lim: - self.items_w[i] = self.items_w[i + delta] + self.strategy.setitem(space, self, i, self.strategy.getitem(space, self, i + delta)) i -= 1 elif delta > 0: - del self.items_w[start:start + delta] - self.items_w[start:start + len(rep_w)] = rep_w + self.strategy.delslice(space, self, start, start + delta) + for i, w_obj in enumerate(rep_w): + self.strategy.setitem(space, self, i + start, w_obj) @classdef.method("slice!") @check_frozen() @@ -148,13 +411,11 @@ def method_slice_i(self, space, w_idx, w_count=None): end = min(max(end, 0), self.length()) delta = (end - start) assert delta >= 0 - w_items = self.items_w[start:start + delta] - del self.items_w[start:start + delta] - return space.newarray(w_items) + data = self.strategy.getslice(space, self, start, start + delta) + self.strategy.delslice(space, self, start, start + delta) + return W_ArrayObject(space, self.strategy, data) else: - w_item = self.items_w[start] - del self.items_w[start] - return w_item + return self.strategy.pop(space, self, start) @classdef.method("size") @classdef.method("length") @@ -167,18 +428,18 @@ def method_emptyp(self, space): @classdef.method("+", other="array") def method_add(self, space, other): - return space.newarray(self.items_w + other) + return space.newarray(self.listview(space) + other) @classdef.method("<<") @check_frozen() def method_lshift(self, space, w_obj): - self.items_w.append(w_obj) + self.append(space, w_obj) return self - @classdef.method("concat", other="array") + @classdef.method("concat", other_w="array") @check_frozen() - def method_concat(self, space, other): - self.items_w += other + def method_concat(self, space, other_w): + self.strategy.extend(space, self, other_w) return self @classdef.method("*") @@ -188,41 +449,41 @@ def method_times(self, space, w_other): n = space.int_w(space.convert_type(w_other, space.w_fixnum, "to_int")) if n < 0: raise space.error(space.w_ArgumentError, "Count cannot be negative") - w_res = W_ArrayObject(space, self.items_w * n, space.getnonsingletonclass(self)) + w_res = W_ArrayObject(space, self.strategy, self.strategy.mul(self, n), space.getnonsingletonclass(self)) space.infect(w_res, self, freeze=False) return w_res @classdef.method("push") @check_frozen() def method_push(self, space, args_w): - self.items_w.extend(args_w) + self.strategy.extend(space, self, args_w) return self @classdef.method("shift") @check_frozen() def method_shift(self, space, w_n=None): if w_n is None: - if self.items_w: - return self.items_w.pop(0) + if self.length(): + return self.strategy.pop(space, self, 0) else: return space.w_nil n = space.int_w(space.convert_type(w_n, space.w_fixnum, "to_int")) if n < 0: raise space.error(space.w_ArgumentError, "negative array size") - items_w = self.items_w[:n] - del self.items_w[:n] - return space.newarray(items_w) + data = self.strategy.getslice(space, self, 0, n) + self.strategy.delslice(space, self, 0, n) + return W_ArrayObject(space, self.strategy, data) @classdef.method("unshift") @check_frozen() def method_unshift(self, space, args_w): for w_obj in reversed(args_w): - self.items_w.insert(0, w_obj) + self.strategy.insert(space, self, 0, w_obj) return self @classdef.method("join") def method_join(self, space, w_sep=None): - if not self.items_w: + if not self.length(): return space.newstr_fromstr("") if w_sep is None: separator = "" @@ -234,15 +495,15 @@ def method_join(self, space, w_sep=None): ) return space.newstr_fromstr(separator.join([ space.str_w(space.send(w_o, "to_s")) - for w_o in self.items_w + for w_o in self.listview(space) ])) @classdef.method("pop") @check_frozen() def method_pop(self, space, w_num=None): if w_num is None: - if self.items_w: - return self.items_w.pop() + if self.length(): + return self.strategy.pop(space, self, -1) else: return space.w_nil else: @@ -253,9 +514,9 @@ def method_pop(self, space, w_num=None): raise space.error(space.w_ArgumentError, "negative array size") else: pop_size = max(0, self.length() - num) - res_w = self.items_w[pop_size:] - del self.items_w[pop_size:] - return space.newarray(res_w) + data = self.strategy.getslice(space, self, pop_size, self.length()) + self.strategy.delslice(space, self, pop_size, self.length()) + return W_ArrayObject(space, self.strategy, data) @classdef.method("delete_at", idx="int") @check_frozen() @@ -265,7 +526,7 @@ def method_delete_at(self, space, idx): if idx < 0 or idx >= self.length(): return space.w_nil else: - return self.items_w.pop(idx) + return self.strategy.pop(space, self, idx) @classdef.method("last") def method_last(self, space, w_count=None): @@ -276,12 +537,12 @@ def method_last(self, space, w_count=None): start = self.length() - count if start < 0: start = 0 - return space.newarray(self.items_w[start:]) + return space.newarray(self.listview(space)[start:]) if self.length() == 0: return space.w_nil else: - return self.items_w[self.length() - 1] + return self.strategy.getitem(space, self, self.length() - 1) @classdef.method("pack") def method_pack(self, space, w_template): @@ -298,13 +559,13 @@ def method_to_ary(self, space): @classdef.method("clear") @check_frozen() def method_clear(self, space): - del self.items_w[:] + self.strategy.clear(space, self) return self @classdef.method("sort!") @check_frozen() def method_sort_i(self, space, block): - RubySorter(space, self.items_w, sortblock=block).sort() + self.strategy.sort(space, self, block) return self @classdef.method("sort_by!") @@ -312,13 +573,13 @@ def method_sort_i(self, space, block): def method_sort_by_i(self, space, block): if block is None: return space.send(self, "enum_for", [space.newsymbol("sort_by!")]) - RubySortBy(space, self.items_w, sortblock=block).sort() + self.strategy.sort_by(space, self, block) return self @classdef.method("reverse!") @check_frozen() def method_reverse_i(self, space): - self.items_w.reverse() + self.strategy.reverse(space, self) return self @classdef.method("rotate!", n="int") @@ -334,8 +595,8 @@ def method_rotate_i(self, space, n=1): if n == 0: return self assert n >= 0 - self.items_w.extend(self.items_w[:n]) - del self.items_w[:n] + self.strategy.extend_from_storage(space, self, self.strategy.getslice(space, self, 0, n)) + self.strategy.delslice(space, self, 0, n) return self @classdef.method("insert", i="int") @@ -347,7 +608,7 @@ def method_insert(self, space, i, args_w): length = self.length() if i > length: self._append_nils(space, i - length) - self.items_w.extend(args_w) + self.strategy.extend(space, self, args_w) return self if i < 0: if i < -length - 1: @@ -357,10 +618,10 @@ def method_insert(self, space, i, args_w): i += length + 1 assert i >= 0 for w_e in args_w: - self.items_w.insert(i, w_e) + self.strategy.insert(space, self, i, w_e) i += 1 return self def _append_nils(self, space, num): for _ in xrange(num): - self.items_w.append(space.w_nil) + self.append(space, space.w_nil) diff --git a/topaz/objspace.py b/topaz/objspace.py index ba0d7a624..600a652ac 100644 --- a/topaz/objspace.py +++ b/topaz/objspace.py @@ -393,7 +393,7 @@ def newstr_fromstrs(self, strs_w): return W_StringObject.newstr_fromstrs(self, strs_w) def newarray(self, items_w): - return W_ArrayObject(self, items_w) + return W_ArrayObject.newarray(self, items_w) def newhash(self): return W_HashObject(self)