diff options
Diffstat (limited to 'rpython')
69 files changed, 2491 insertions, 1188 deletions
diff --git a/rpython/annotator/policy.py b/rpython/annotator/policy.py index 5a33b79083..93c3dccdd1 100644 --- a/rpython/annotator/policy.py +++ b/rpython/annotator/policy.py @@ -3,6 +3,9 @@ from rpython.annotator.specialize import default_specialize as default from rpython.annotator.specialize import ( specialize_argvalue, specialize_argtype, specialize_arglistitemtype, specialize_arg_or_var, memo, specialize_call_location) +from rpython.flowspace.operation import op +from rpython.flowspace.model import Constant +from rpython.annotator.model import SomeTuple class AnnotatorPolicy(object): @@ -64,7 +67,34 @@ class AnnotatorPolicy(object): return LowLevelAnnotatorPolicy.specialize__ll_and_arg(*args) def no_more_blocks_to_annotate(pol, annotator): + bk = annotator.bookkeeper # hint to all pending specializers that we are done - for callback in annotator.bookkeeper.pending_specializations: + for callback in bk.pending_specializations: callback() - del annotator.bookkeeper.pending_specializations[:] + del bk.pending_specializations[:] + if annotator.added_blocks is not None: + all_blocks = annotator.added_blocks + else: + all_blocks = annotator.annotated + for block in list(all_blocks): + for i, instr in enumerate(block.operations): + if not isinstance(instr, (op.simple_call, op.call_args)): + continue + v_func = instr.args[0] + s_func = annotator.annotation(v_func) + if not hasattr(s_func, 'needs_sandboxing'): + continue + key = ('sandboxing', s_func.const) + if key not in bk.emulated_pbc_calls: + params_s = s_func.args_s + s_result = s_func.s_result + from rpython.translator.sandbox.rsandbox import make_sandbox_trampoline + sandbox_trampoline = make_sandbox_trampoline( + s_func.name, params_s, s_result) + sandbox_trampoline._signature_ = [SomeTuple(items=params_s)], s_result + bk.emulate_pbc_call(key, bk.immutablevalue(sandbox_trampoline), params_s) + else: + s_trampoline = bk.emulated_pbc_calls[key][0] + sandbox_trampoline = s_trampoline.const + new = instr.replace({instr.args[0]: Constant(sandbox_trampoline)}) + block.operations[i] = new diff --git a/rpython/annotator/specialize.py b/rpython/annotator/specialize.py index 14bd0d6e85..436d8ec635 100644 --- a/rpython/annotator/specialize.py +++ b/rpython/annotator/specialize.py @@ -317,18 +317,6 @@ def cartesian_product(lstlst): yield (value,) + tuple_tail -def make_constgraphbuilder(n, v=None, factory=None, srcmodule=None): - def constgraphbuilder(translator, ignore): - args = ','.join(["arg%d" % i for i in range(n)]) - if factory is not None: - computed_v = factory() - else: - computed_v = v - miniglobals = {'v': computed_v, '__name__': srcmodule} - exec py.code.Source("constf = lambda %s: v" % args).compile() in miniglobals - return translator.buildflowgraph(miniglobals['constf']) - return constgraphbuilder - def maybe_star_args(funcdesc, key, args_s): args_s, key1, builder = flatten_star_args(funcdesc, args_s) if key1 is not None: diff --git a/rpython/annotator/unaryop.py b/rpython/annotator/unaryop.py index 50ea9b7769..27629f5639 100644 --- a/rpython/annotator/unaryop.py +++ b/rpython/annotator/unaryop.py @@ -113,8 +113,9 @@ def contains_number(annotator, number, element): @op.simple_call.register(SomeObject) def simple_call_SomeObject(annotator, func, *args): - return annotator.annotation(func).call( - simple_args([annotator.annotation(arg) for arg in args])) + s_func = annotator.annotation(func) + argspec = simple_args([annotator.annotation(arg) for arg in args]) + return s_func.call(argspec) @op.call_args.register_transform(SomeObject) def transform_varargs(annotator, v_func, v_shape, *data_v): diff --git a/rpython/jit/backend/ppc/test/test_runner.py b/rpython/jit/backend/ppc/test/test_runner.py index 3d2c0ef959..9137407b84 100644 --- a/rpython/jit/backend/ppc/test/test_runner.py +++ b/rpython/jit/backend/ppc/test/test_runner.py @@ -134,7 +134,7 @@ class TestPPC(LLtypeBackendTest): def test_debugger_on(self): py.test.skip("XXX") - from pypy.rlib import debug + from rpython.rlib import debug targettoken, preambletoken = TargetToken(), TargetToken() loop = """ diff --git a/rpython/jit/codewriter/jtransform.py b/rpython/jit/codewriter/jtransform.py index 2e8e683dd9..12357425a9 100644 --- a/rpython/jit/codewriter/jtransform.py +++ b/rpython/jit/codewriter/jtransform.py @@ -2042,6 +2042,11 @@ class Transformer(object): self.vable_flags[op.args[0]] = op.args[2].value return [] + def rewrite_op_jit_enter_portal_frame(self, op): + return [op] + def rewrite_op_jit_leave_portal_frame(self, op): + return [op] + # --------- # ll_math.sqrt_nonneg() diff --git a/rpython/jit/codewriter/support.py b/rpython/jit/codewriter/support.py index 177a507e69..e319a87883 100644 --- a/rpython/jit/codewriter/support.py +++ b/rpython/jit/codewriter/support.py @@ -246,12 +246,12 @@ def _ll_1_jit_force_virtual(inst): def _ll_2_int_floordiv_ovf_zer(x, y): if y == 0: raise ZeroDivisionError - if x == -sys.maxint - 1 and y == -1: - raise OverflowError - return llop.int_floordiv(lltype.Signed, x, y) + return _ll_2_int_floordiv_ovf(x, y) def _ll_2_int_floordiv_ovf(x, y): - if x == -sys.maxint - 1 and y == -1: + # intentionally not short-circuited to produce only one guard + # and to remove the check fully if one of the arguments is known + if (x == -sys.maxint - 1) & (y == -1): raise OverflowError return llop.int_floordiv(lltype.Signed, x, y) @@ -263,12 +263,11 @@ def _ll_2_int_floordiv_zer(x, y): def _ll_2_int_mod_ovf_zer(x, y): if y == 0: raise ZeroDivisionError - if x == -sys.maxint - 1 and y == -1: - raise OverflowError - return llop.int_mod(lltype.Signed, x, y) + return _ll_2_int_mod_ovf(x, y) def _ll_2_int_mod_ovf(x, y): - if x == -sys.maxint - 1 and y == -1: + #see comment in _ll_2_int_floordiv_ovf + if (x == -sys.maxint - 1) & (y == -1): raise OverflowError return llop.int_mod(lltype.Signed, x, y) diff --git a/rpython/jit/metainterp/blackhole.py b/rpython/jit/metainterp/blackhole.py index 47e5ff0950..20a69597a5 100644 --- a/rpython/jit/metainterp/blackhole.py +++ b/rpython/jit/metainterp/blackhole.py @@ -944,6 +944,14 @@ class BlackholeInterpreter(object): pass @arguments("i") + def bhimpl_jit_enter_portal_frame(x): + pass + + @arguments() + def bhimpl_jit_leave_portal_frame(): + pass + + @arguments("i") def bhimpl_int_assert_green(x): pass @arguments("r") diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py index 2081651bbc..b196fe341e 100644 --- a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py @@ -28,7 +28,7 @@ def test_store_final_boxes_in_guard(): # setup rd data fi0 = resume.FrameInfo(None, FakeJitCode(), 11) snapshot0 = resume.Snapshot(None, [b0]) - op.rd_snapshot = resume.Snapshot(snapshot0, [b1]) + op.rd_snapshot = resume.TopSnapshot(snapshot0, [], [b1]) op.rd_frame_info_list = resume.FrameInfo(fi0, FakeJitCode(), 33) # opt.store_final_boxes_in_guard(op, []) diff --git a/rpython/jit/metainterp/optimizeopt/test/test_util.py b/rpython/jit/metainterp/optimizeopt/test/test_util.py index 8c190a2b9b..2ac0e98d1f 100644 --- a/rpython/jit/metainterp/optimizeopt/test/test_util.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_util.py @@ -506,14 +506,15 @@ class BaseTest(object): index = 0 if op.is_guard(): - op.rd_snapshot = resume.Snapshot(None, op.getfailargs()) + op.rd_snapshot = resume.TopSnapshot( + resume.Snapshot(None, op.getfailargs()), [], []) op.rd_frame_info_list = resume.FrameInfo(None, FakeJitCode(), 11) def add_guard_future_condition(self, res): # invent a GUARD_FUTURE_CONDITION to not have to change all tests if res.operations[-1].getopnum() == rop.JUMP: guard = ResOperation(rop.GUARD_FUTURE_CONDITION, [], None) - guard.rd_snapshot = resume.Snapshot(None, []) + guard.rd_snapshot = resume.TopSnapshot(None, [], []) res.operations.insert(-1, guard) def assert_equal(self, optimized, expected, text_right=None): diff --git a/rpython/jit/metainterp/optimizeopt/util.py b/rpython/jit/metainterp/optimizeopt/util.py index c434fb63d5..78bccaf865 100644 --- a/rpython/jit/metainterp/optimizeopt/util.py +++ b/rpython/jit/metainterp/optimizeopt/util.py @@ -8,7 +8,7 @@ from rpython.rlib.objectmodel import we_are_translated from rpython.jit.metainterp import resoperation from rpython.rlib.debug import make_sure_not_resized from rpython.jit.metainterp.resoperation import rop -from rpython.jit.metainterp.resume import Snapshot, AccumInfo +from rpython.jit.metainterp.resume import AccumInfo # ____________________________________________________________ # Misc. utilities diff --git a/rpython/jit/metainterp/pyjitpl.py b/rpython/jit/metainterp/pyjitpl.py index 8bfb259e18..fe3521e995 100644 --- a/rpython/jit/metainterp/pyjitpl.py +++ b/rpython/jit/metainterp/pyjitpl.py @@ -1358,6 +1358,17 @@ class MIFrame(object): self.metainterp.attach_debug_info(op) @arguments("box") + def opimpl_jit_enter_portal_frame(self, uniqueidbox): + unique_id = uniqueidbox.getint() + jd_no = self.metainterp.jitdriver_sd.mainjitcode.index # fish + self.metainterp.enter_portal_frame(jd_no, unique_id) + + @arguments() + def opimpl_jit_leave_portal_frame(self): + jd_no = self.metainterp.jitdriver_sd.mainjitcode.index # fish + self.metainterp.leave_portal_frame(jd_no) + + @arguments("box") def _opimpl_assert_green(self, box): if not isinstance(box, Const): msg = "assert_green failed at %s:%d" % ( diff --git a/rpython/jit/metainterp/resume.py b/rpython/jit/metainterp/resume.py index e563896a91..21b8b55f7a 100644 --- a/rpython/jit/metainterp/resume.py +++ b/rpython/jit/metainterp/resume.py @@ -27,6 +27,13 @@ class Snapshot(object): self.prev = prev self.boxes = boxes +class TopSnapshot(Snapshot): + __slots__ = ('vable_boxes',) + + def __init__(self, prev, boxes, vable_boxes): + Snapshot.__init__(self, prev, boxes) + self.vable_boxes = vable_boxes + def combine_uint(index1, index2): assert 0 <= index1 < 65536 assert 0 <= index2 < 65536 @@ -127,9 +134,11 @@ def capture_resumedata(framestack, virtualizable_boxes, virtualref_boxes, snapshot_storage): n = len(framestack) - 1 if virtualizable_boxes is not None: - boxes = virtualref_boxes + virtualizable_boxes + virtualizable_boxes = ([virtualizable_boxes[-1]] + + virtualizable_boxes[:-1]) else: - boxes = virtualref_boxes[:] + virtualizable_boxes = [] + virtualref_boxes = virtualref_boxes[:] if n >= 0: top = framestack[n] _ensure_parent_resumedata(framestack, n) @@ -138,11 +147,12 @@ def capture_resumedata(framestack, virtualizable_boxes, virtualref_boxes, snapshot_storage.rd_frame_info_list = frame_info_list snapshot = Snapshot(top.parent_resumedata_snapshot, top.get_list_of_active_boxes(False)) - snapshot = Snapshot(snapshot, boxes) + snapshot = TopSnapshot(snapshot, virtualref_boxes, virtualizable_boxes) snapshot_storage.rd_snapshot = snapshot else: snapshot_storage.rd_frame_info_list = None - snapshot_storage.rd_snapshot = Snapshot(None, boxes) + snapshot_storage.rd_snapshot = TopSnapshot(None, virtualref_boxes, + virtualizable_boxes) PENDINGFIELDSTRUCT = lltype.Struct('PendingField', ('lldescr', OBJECTPTR), @@ -200,10 +210,12 @@ class NumberingState(object): self.v = 0 def count_boxes(self, lst): - c = 0 + snapshot = lst[0] + assert isinstance(snapshot, TopSnapshot) + c = len(snapshot.vable_boxes) for snapshot in lst: c += len(snapshot.boxes) - c += 2 * (len(lst) - 1) + c += 2 * (len(lst) - 1) + 1 + 1 return c def append(self, item): @@ -294,13 +306,11 @@ class ResumeDataLoopMemo(object): state.append(tagged) state.n = n state.v = v - state.position -= length + 2 - def number(self, optimizer, snapshot, frameinfo): + def number(self, optimizer, topsnapshot, frameinfo): # flatten the list - vref_snapshot = snapshot - cur = snapshot.prev - snapshot_list = [vref_snapshot] + cur = topsnapshot.prev + snapshot_list = [topsnapshot] framestack_list = [] while cur: framestack_list.append(frameinfo) @@ -311,19 +321,30 @@ class ResumeDataLoopMemo(object): # we want to number snapshots starting from the back, but ending # with a forward list - for i in range(len(snapshot_list) - 1, -1, -1): - state.position -= len(snapshot_list[i].boxes) - if i != 0: - frameinfo = framestack_list[i - 1] - jitcode_pos, pc = unpack_uint(frameinfo.packed_jitcode_pc) - state.position -= 2 - state.append(rffi.cast(rffi.SHORT, jitcode_pos)) - state.append(rffi.cast(rffi.SHORT, pc)) + for i in range(len(snapshot_list) - 1, 0, -1): + state.position -= len(snapshot_list[i].boxes) + 2 + frameinfo = framestack_list[i - 1] + jitcode_pos, pc = unpack_uint(frameinfo.packed_jitcode_pc) + state.append(rffi.cast(rffi.SHORT, jitcode_pos)) + state.append(rffi.cast(rffi.SHORT, pc)) self._number_boxes(snapshot_list[i].boxes, optimizer, state) - - numb = resumecode.create_numbering(state.current, - len(vref_snapshot.boxes)) - + state.position -= len(snapshot_list[i].boxes) + 2 + + assert isinstance(topsnapshot, TopSnapshot) + special_boxes_size = (1 + len(topsnapshot.vable_boxes) + + 1 + len(topsnapshot.boxes)) + assert state.position == special_boxes_size + + state.position = 0 + state.append(rffi.cast(rffi.SHORT, len(topsnapshot.vable_boxes))) + self._number_boxes(topsnapshot.vable_boxes, optimizer, state) + n = len(topsnapshot.boxes) + assert not (n & 1) + state.append(rffi.cast(rffi.SHORT, n >> 1)) + self._number_boxes(topsnapshot.boxes, optimizer, state) + assert state.position == special_boxes_size + + numb = resumecode.create_numbering(state.current) return numb, state.liveboxes, state.v def forget_numberings(self): @@ -1113,48 +1134,42 @@ class ResumeDataBoxReader(AbstractResumeDataReader): self.boxes_f = boxes_f self._prepare_next_section(info) - def consume_virtualizable_boxes(self, vinfo): + def consume_virtualizable_boxes(self, vinfo, index): # we have to ignore the initial part of 'nums' (containing vrefs), # find the virtualizable from nums[-1], and use it to know how many # boxes of which type we have to return. This does not write # anything into the virtualizable. numb = self.numb - first_snapshot_size = rffi.cast(lltype.Signed, numb.first_snapshot_size) - item, _ = resumecode.numb_next_item(numb, first_snapshot_size - 1) + item, index = resumecode.numb_next_item(numb, index) virtualizablebox = self.decode_ref(item) - index = first_snapshot_size - vinfo.get_total_size(virtualizablebox.getref_base()) - 1 virtualizable = vinfo.unwrap_virtualizable_box(virtualizablebox) return vinfo.load_list_of_boxes(virtualizable, self, virtualizablebox, numb, index) - def consume_virtualref_boxes(self, end): + def consume_virtualref_boxes(self, index): # Returns a list of boxes, assumed to be all BoxPtrs. # We leave up to the caller to call vrefinfo.continue_tracing(). - assert (end & 1) == 0 + size, index = resumecode.numb_next_item(self.numb, index) + if size == 0: + return [], index lst = [] - self.cur_index = 0 - for i in range(end): - item, self.cur_index = resumecode.numb_next_item(self.numb, - self.cur_index) + for i in range(size * 2): + item, index = resumecode.numb_next_item(self.numb, index) lst.append(self.decode_ref(item)) - return lst + return lst, index def consume_vref_and_vable_boxes(self, vinfo, ginfo): - first_snapshot_size = rffi.cast(lltype.Signed, - self.numb.first_snapshot_size) + vable_size, index = resumecode.numb_next_item(self.numb, 0) if vinfo is not None: - virtualizable_boxes = self.consume_virtualizable_boxes(vinfo) - end = first_snapshot_size - len(virtualizable_boxes) + virtualizable_boxes, index = self.consume_virtualizable_boxes(vinfo, + index) elif ginfo is not None: - item, self.cur_index = resumecode.numb_next_item(self.numb, - first_snapshot_size - 1) + item, index = resumecode.numb_next_item(self.numb, index) virtualizable_boxes = [self.decode_ref(item)] - end = first_snapshot_size - 1 else: - end = first_snapshot_size virtualizable_boxes = None - virtualref_boxes = self.consume_virtualref_boxes(end) - self.cur_index = rffi.cast(lltype.Signed, self.numb.first_snapshot_size) + virtualref_boxes, index = self.consume_virtualref_boxes(index) + self.cur_index = index return virtualizable_boxes, virtualref_boxes def allocate_with_vtable(self, descr=None): @@ -1429,39 +1444,36 @@ class ResumeDataDirectReader(AbstractResumeDataReader): info = blackholeinterp.get_current_position_info() self._prepare_next_section(info) - def consume_virtualref_info(self, vrefinfo, end): + def consume_virtualref_info(self, vrefinfo, index): # we have to decode a list of references containing pairs - # [..., virtual, vref, ...] stopping at 'end' - if vrefinfo is None: - assert end == 0 - return - assert (end & 1) == 0 - self.cur_index = 0 - for i in range(0, end, 2): - virtual_item, self.cur_index = resumecode.numb_next_item( - self.numb, self.cur_index) - vref_item, self.cur_index = resumecode.numb_next_item( - self.numb, self.cur_index) + # [..., virtual, vref, ...] and returns the index at the end + size, index = resumecode.numb_next_item(self.numb, index) + if vrefinfo is None or size == 0: + assert size == 0 + return index + for i in range(size): + virtual_item, index = resumecode.numb_next_item( + self.numb, index) + vref_item, index = resumecode.numb_next_item( + self.numb, index) virtual = self.decode_ref(virtual_item) vref = self.decode_ref(vref_item) # For each pair, we store the virtual inside the vref. vrefinfo.continue_tracing(vref, virtual) + return index - def consume_vable_info(self, vinfo): + def consume_vable_info(self, vinfo, index): # we have to ignore the initial part of 'nums' (containing vrefs), # find the virtualizable from nums[-1], load all other values # from the CPU stack, and copy them into the virtualizable numb = self.numb - first_snapshot_size = rffi.cast(lltype.Signed, numb.first_snapshot_size) - item, _ = resumecode.numb_next_item(self.numb, - first_snapshot_size - 1) + item, index = resumecode.numb_next_item(self.numb, index) virtualizable = self.decode_ref(item) - start_index = first_snapshot_size - 1 - vinfo.get_total_size(virtualizable) # just reset the token, we'll force it later vinfo.reset_token_gcref(virtualizable) - vinfo.write_from_resume_data_partial(virtualizable, self, start_index, - numb) - return start_index + index = vinfo.write_from_resume_data_partial(virtualizable, self, + index, numb) + return index def load_value_of_type(self, TYPE, tagged): from rpython.jit.metainterp.warmstate import specialize_value @@ -1478,14 +1490,18 @@ class ResumeDataDirectReader(AbstractResumeDataReader): load_value_of_type._annspecialcase_ = 'specialize:arg(1)' def consume_vref_and_vable(self, vrefinfo, vinfo, ginfo): + vable_size, index = resumecode.numb_next_item(self.numb, 0) if self.resume_after_guard_not_forced != 2: - end_vref = rffi.cast(lltype.Signed, self.numb.first_snapshot_size) if vinfo is not None: - end_vref = self.consume_vable_info(vinfo) + index = self.consume_vable_info(vinfo, index) if ginfo is not None: - end_vref -= 1 - self.consume_virtualref_info(vrefinfo, end_vref) - self.cur_index = rffi.cast(lltype.Signed, self.numb.first_snapshot_size) + _, index = resumecode.numb_next_item(self.numb, index) + index = self.consume_virtualref_info(vrefinfo, index) + else: + index = resumecode.numb_next_n_items(self.numb, vable_size, index) + vref_size, index = resumecode.numb_next_item(self.numb, index) + index = resumecode.numb_next_n_items(self.numb, vref_size * 2, index) + self.cur_index = index def allocate_with_vtable(self, descr=None): from rpython.jit.metainterp.executor import exec_new_with_vtable diff --git a/rpython/jit/metainterp/resumecode.py b/rpython/jit/metainterp/resumecode.py index 43f65a92a3..8c2104da83 100644 --- a/rpython/jit/metainterp/resumecode.py +++ b/rpython/jit/metainterp/resumecode.py @@ -1,96 +1,74 @@ """ Resume bytecode. It goes as following: -<numb> <numb> <pc> <jitcode> <numb> <numb> <numb> <pc> <jitcode> + [<length> <virtualizable object> <numb> <numb> <numb>] if vinfo is not None + -OR- + [1 <ginfo object>] if ginfo is not None + -OR- + [0] if both are None -until the length of the array. + [<length> <virtual> <vref> <virtual> <vref>] for virtualrefs -The interface is only create_numbering/numb_next_item, but! there is a trick -that uses first_snapshot_size + some knowledge about inside to decode -virtualref/virtualizable_fields/virtualizable in that order in resume.py. + [<pc> <jitcode> <numb> <numb> <numb>] the frames + [<pc> <jitcode> <numb> <numb>] + ... -If the algorithm changes, the part about how to find where virtualizable -and virtualrefs are to be found + until the length of the array. """ from rpython.rtyper.lltypesystem import rffi, lltype NUMBERINGP = lltype.Ptr(lltype.GcForwardReference()) NUMBERING = lltype.GcStruct('Numbering', -# ('prev', NUMBERINGP), -# ('prev_index', rffi.USHORT), - ('first_snapshot_size', rffi.USHORT), # ugh, ugly - ('code', lltype.Array(rffi.SHORT))) + ('code', lltype.Array(rffi.UCHAR))) NUMBERINGP.TO.become(NUMBERING) NULL_NUMBER = lltype.nullptr(NUMBERING) -# this is the actually used version - -def create_numbering(lst, first_snapshot_size): - numb = lltype.malloc(NUMBERING, len(lst)) - for i in range(len(lst)): - numb.code[i] = rffi.cast(rffi.SHORT, lst[i]) - numb.first_snapshot_size = rffi.cast(rffi.USHORT, first_snapshot_size) - return numb - -def numb_next_item(numb, index): - return rffi.cast(lltype.Signed, numb.code[index]), index + 1 - -# this is the version that can be potentially used - -def _create_numbering(lst, prev, prev_index, first_snapshot_size): - count = 0 +def create_numbering(lst): + result = [] for item in lst: + item = rffi.cast(lltype.Signed, item) + item *= 2 if item < 0: - if item < -63: - count += 1 - if item > 127: - count += 1 - count += 1 - numb = lltype.malloc(NUMBERING, count) - numb.prev = prev - numb.prev_index = rffi.cast(rffi.USHORT, prev_index) - numb.first_snapshot_size = rffi.cast(rffi.USHORT, first_snapshot_size) - index = 0 - for item in lst: - if 0 <= item <= 128: - numb.code[index] = rffi.cast(rffi.UCHAR, item) - index += 1 + item = -1 - item + + assert item >= 0 + if item < 2**7: + result.append(rffi.cast(rffi.UCHAR, item)) + elif item < 2**14: + result.append(rffi.cast(rffi.UCHAR, item | 0x80)) + result.append(rffi.cast(rffi.UCHAR, item >> 7)) else: - assert (item >> 8) <= 63 - if item < 0: - item = -item - if item <= 63: - numb.code[index] = rffi.cast(rffi.UCHAR, item | 0x40) - index += 1 - else: - numb.code[index] = rffi.cast(rffi.UCHAR, (item >> 8) | 0x80 | 0x40) - numb.code[index + 1] = rffi.cast(rffi.UCHAR, item & 0xff) - index += 2 - else: - numb.code[index] = rffi.cast(rffi.UCHAR, (item >> 8) | 0x80) - numb.code[index + 1] = rffi.cast(rffi.UCHAR, item & 0xff) - index += 2 + assert item < 2**16 + result.append(rffi.cast(rffi.UCHAR, item | 0x80)) + result.append(rffi.cast(rffi.UCHAR, (item >> 7) | 0x80)) + result.append(rffi.cast(rffi.UCHAR, item >> 14)) + + numb = lltype.malloc(NUMBERING, len(result)) + for i in range(len(result)): + numb.code[i] = result[i] return numb -def copy_from_list_to_numb(lst, numb, index): - i = 0 - while i < len(lst): - numb.code[i + index] = lst[i] - i += 1 +def numb_next_item(numb, index): + value = rffi.cast(lltype.Signed, numb.code[index]) + index += 1 + if value & (2**7): + value &= 2**7 - 1 + value |= rffi.cast(lltype.Signed, numb.code[index]) << 7 + index += 1 + if value & (2**14): + value &= 2**14 - 1 + value |= rffi.cast(lltype.Signed, numb.code[index]) << 14 + index += 1 + if value & 1: + value = -1 - value + value >>= 1 + return value, index -def _numb_next_item(numb, index): - one = rffi.cast(lltype.Signed, numb.code[index]) - if one & 0x40: - if one & 0x80: - two = rffi.cast(lltype.Signed, numb.code[index + 1]) - return -(((one & ~(0x80 | 0x40)) << 8) | two), index + 2 - else: - return -(one & (~0x40)), index + 1 - if one & 0x80: - two = rffi.cast(lltype.Signed, numb.code[index + 1]) - return ((one & 0x7f) << 8) | two, index + 2 - return one, index + 1 +def numb_next_n_items(numb, size, index): + for i in range(size): + _, index = numb_next_item(numb, index) + return index def unpack_numbering(numb): l = [] diff --git a/rpython/jit/metainterp/test/strategies.py b/rpython/jit/metainterp/test/strategies.py new file mode 100644 index 0000000000..e7cd79ec20 --- /dev/null +++ b/rpython/jit/metainterp/test/strategies.py @@ -0,0 +1,13 @@ + +import sys +from hypothesis import strategies +from rpython.jit.metainterp.resoperation import InputArgInt +from rpython.jit.metainterp.history import ConstInt + +machine_ints = strategies.integers(min_value=-sys.maxint - 1, + max_value=sys.maxint) +intboxes = strategies.builds(InputArgInt) +intconsts = strategies.builds(ConstInt, machine_ints) +boxes = intboxes | intconsts +boxlists = strategies.lists(boxes, min_size=1).flatmap( + lambda cis: strategies.lists(strategies.sampled_from(cis)))
\ No newline at end of file diff --git a/rpython/jit/metainterp/test/test_ajit.py b/rpython/jit/metainterp/test/test_ajit.py index fc4a231aca..ecf82bcbf6 100644 --- a/rpython/jit/metainterp/test/test_ajit.py +++ b/rpython/jit/metainterp/test/test_ajit.py @@ -1199,6 +1199,31 @@ class BasicTests: (-sys.maxint-1) // (-6) + 100 * 8) + def test_overflow_fold_if_divisor_constant(self): + import sys + from rpython.rtyper.lltypesystem.lloperation import llop + myjitdriver = JitDriver(greens = [], reds = ['x', 'y', 'res']) + def f(x, y): + res = 0 + while y > 0: + myjitdriver.can_enter_jit(x=x, y=y, res=res) + myjitdriver.jit_merge_point(x=x, y=y, res=res) + try: + res += llop.int_floordiv_ovf(lltype.Signed, + x, 2) + res += llop.int_mod_ovf(lltype.Signed, + x, 2) + x += 5 + except OverflowError: + res += 100 + y -= 1 + return res + res = self.meta_interp(f, [-41, 8]) + # the guard_true are for the loop condition + # the guard_false needed to check whether an overflow can occur have + # been folded away + self.check_resops(guard_true=2, guard_false=0) + def test_isinstance(self): class A: pass diff --git a/rpython/jit/metainterp/test/test_jitdriver.py b/rpython/jit/metainterp/test/test_jitdriver.py index 56f26ffd85..cba873a956 100644 --- a/rpython/jit/metainterp/test/test_jitdriver.py +++ b/rpython/jit/metainterp/test/test_jitdriver.py @@ -213,6 +213,21 @@ class MultipleJitDriversTests(object): if op.getopname() == 'enter_portal_frame': assert op.getarg(0).getint() == 0 assert op.getarg(1).getint() == 1 - + + def test_manual_leave_enter_portal_frame(self): + from rpython.rlib import jit + driver = JitDriver(greens=[], reds='auto', is_recursive=True) + + def f(arg): + i = 0 + while i < 100: + driver.jit_merge_point() + jit.enter_portal_frame(42) + jit.leave_portal_frame() + i += 1 + + self.meta_interp(f, [0]) + self.check_simple_loop(enter_portal_frame=1, leave_portal_frame=1) + class TestLLtype(MultipleJitDriversTests, LLJitMixin): pass diff --git a/rpython/jit/metainterp/test/test_resume.py b/rpython/jit/metainterp/test/test_resume.py index ebbd602860..dd2a9f501d 100644 --- a/rpython/jit/metainterp/test/test_resume.py +++ b/rpython/jit/metainterp/test/test_resume.py @@ -10,7 +10,7 @@ from rpython.jit.metainterp.resume import ResumeDataVirtualAdder,\ VArrayInfoNotClear, VStrPlainInfo, VStrConcatInfo, VStrSliceInfo,\ VUniPlainInfo, VUniConcatInfo, VUniSliceInfo, Snapshot, FrameInfo,\ capture_resumedata, ResumeDataLoopMemo, UNASSIGNEDVIRTUAL, INT,\ - annlowlevel, PENDINGFIELDSP, unpack_uint, TAG_CONST_OFFSET + annlowlevel, PENDINGFIELDSP, unpack_uint, TAG_CONST_OFFSET, TopSnapshot from rpython.jit.metainterp.resumecode import unpack_numbering,\ create_numbering, NULL_NUMBER @@ -22,8 +22,12 @@ from rpython.jit.metainterp import executor from rpython.jit.codewriter import heaptracker, longlong from rpython.jit.metainterp.resoperation import ResOperation, InputArgInt,\ InputArgRef, rop +from rpython.jit.metainterp.test.strategies import boxlists from rpython.rlib.debug import debug_start, debug_stop, debug_print,\ have_debug_prints +from rpython.jit.metainterp import resumecode + +from hypothesis import given class Storage: rd_frame_info_list = None @@ -278,9 +282,7 @@ def _next_section(reader, *expected): assert bh.written_f == expected_f -def Numbering(nums): - numb = create_numbering(nums, 0) - return numb +Numbering = create_numbering def tagconst(i): return tag(i + TAG_CONST_OFFSET, TAGCONST) @@ -610,7 +612,8 @@ def test_capture_resumedata(): assert unpack_uint(frame_info_list.packed_jitcode_pc) == (2, 15) snapshot = storage.rd_snapshot - assert snapshot.boxes == vrs + vbs # in the same list + assert snapshot.boxes == vrs + assert snapshot.vable_boxes == [b2, b1] snapshot = snapshot.prev assert snapshot.prev is fs[2].parent_resumedata_snapshot @@ -904,9 +907,9 @@ def test_ResumeDataLoopMemo_number(): env = [b1, c1, b2, b1, c2] snap = Snapshot(None, env) env1 = [c3, b3, b1, c1] - snap1 = Snapshot(snap, env1) + snap1 = TopSnapshot(snap, env1, []) env2 = [c3, b3, b1, c3] - snap2 = Snapshot(snap, env2) + snap2 = TopSnapshot(snap, env2, []) memo = ResumeDataLoopMemo(FakeMetaInterpStaticData()) frameinfo = FrameInfo(None, FakeJitCode("jitcode", 0), 0) @@ -916,10 +919,11 @@ def test_ResumeDataLoopMemo_number(): assert liveboxes == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX), b3: tag(2, TAGBOX)} - base = [tag(0, TAGBOX), tag(1, TAGINT), tag(1, TAGBOX), tag(0, TAGBOX), tag(2, TAGINT)] + base = [0, 0, tag(0, TAGBOX), tag(1, TAGINT), + tag(1, TAGBOX), tag(0, TAGBOX), tag(2, TAGINT)] - assert unpack_numbering(numb) == [ - tag(3, TAGINT), tag(2, TAGBOX), tag(0, TAGBOX), tag(1, TAGINT), 0, 0] + base + assert unpack_numbering(numb) == [0, 2, tag(3, TAGINT), tag(2, TAGBOX), + tag(0, TAGBOX), tag(1, TAGINT)] + base numb2, liveboxes2, v = memo.number(FakeOptimizer(), snap2, frameinfo) assert v == 0 @@ -927,11 +931,11 @@ def test_ResumeDataLoopMemo_number(): assert liveboxes2 == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX), b3: tag(2, TAGBOX)} assert liveboxes2 is not liveboxes - assert unpack_numbering(numb2) == [ - tag(3, TAGINT), tag(2, TAGBOX), tag(0, TAGBOX), tag(3, TAGINT), 0, 0] + base + assert unpack_numbering(numb2) == [0, 2, tag(3, TAGINT), tag(2, TAGBOX), + tag(0, TAGBOX), tag(3, TAGINT)] + base env3 = [c3, b3, b1, c3] - snap3 = Snapshot(snap, env3) + snap3 = TopSnapshot(snap, env3, []) class FakeVirtualInfo(info.AbstractInfo): def __init__(self, virt): @@ -946,13 +950,12 @@ def test_ResumeDataLoopMemo_number(): assert v == 0 assert liveboxes3 == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX)} - assert unpack_numbering(numb3) == [tag(3, TAGINT), tag(4, TAGINT), - tag(0, TAGBOX), - tag(3, TAGINT), 0, 0] + base + assert unpack_numbering(numb3) == [0, 2, tag(3, TAGINT), tag(4, TAGINT), + tag(0, TAGBOX), tag(3, TAGINT)] + base # virtual env4 = [c3, b4, b1, c3] - snap4 = Snapshot(snap, env4) + snap4 = TopSnapshot(snap, env4, []) b4.set_forwarded(FakeVirtualInfo(True)) numb4, liveboxes4, v = memo.number(FakeOptimizer(), snap4, frameinfo) @@ -960,11 +963,11 @@ def test_ResumeDataLoopMemo_number(): assert liveboxes4 == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX), b4: tag(0, TAGVIRTUAL)} - assert unpack_numbering(numb4) == [tag(3, TAGINT), tag(0, TAGVIRTUAL), - tag(0, TAGBOX), tag(3, TAGINT), 0, 0] + base + assert unpack_numbering(numb4) == [0, 2, tag(3, TAGINT), tag(0, TAGVIRTUAL), + tag(0, TAGBOX), tag(3, TAGINT)] + base env5 = [b1, b4, b5] - snap5 = Snapshot(snap4, env5) + snap5 = TopSnapshot(snap4, [], env5) b4.set_forwarded(FakeVirtualInfo(True)) b5.set_forwarded(FakeVirtualInfo(True)) @@ -974,9 +977,30 @@ def test_ResumeDataLoopMemo_number(): assert liveboxes5 == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX), b4: tag(0, TAGVIRTUAL), b5: tag(1, TAGVIRTUAL)} - assert unpack_numbering(numb5) == [tag(0, TAGBOX), tag(0, TAGVIRTUAL), - tag(1, TAGVIRTUAL), 2, 1] + unpack_numbering(numb4) - + assert unpack_numbering(numb5) == [ + 3, tag(0, TAGBOX), tag(0, TAGVIRTUAL), tag(1, TAGVIRTUAL), + 0, + 2, 1, tag(3, TAGINT), tag(0, TAGVIRTUAL), tag(0, TAGBOX), tag(3, TAGINT) + ] + base + +@given(boxlists) +def test_ResumeDataLoopMemo_random(lst): + s = TopSnapshot(None, [], lst) + frameinfo = FrameInfo(None, FakeJitCode("foo", 0), 0) + memo = ResumeDataLoopMemo(FakeMetaInterpStaticData()) + num, liveboxes, v = memo.number(FakeOptimizer(), s, frameinfo) + l = unpack_numbering(num) + assert l[-1] == 0 + assert l[0] == len(lst) + for i, item in enumerate(lst): + v, tag = untag(l[i + 1]) + if tag == TAGBOX: + assert l[i + 1] == liveboxes[item] + elif tag == TAGCONST: + assert memo.consts[v].getint() == item.getint() + elif tag == TAGINT: + assert v == item.getint() + def test_ResumeDataLoopMemo_number_boxes(): memo = ResumeDataLoopMemo(FakeMetaInterpStaticData()) b1, b2 = [InputArgInt(), InputArgInt()] @@ -1060,10 +1084,11 @@ def make_storage(b1, b2, b3): storage = Storage() snapshot = Snapshot(None, [b1, ConstInt(1), b1, b2]) snapshot = Snapshot(snapshot, [ConstInt(2), ConstInt(3)]) - snapshot = Snapshot(snapshot, [b1, b2, b3]) - frameinfo = FrameInfo(FrameInfo(None, FakeJitCode("code1", 21), 22), - FakeJitCode("code2", 31), 32) - storage.rd_snapshot = snapshot + snapshot = Snapshot(snapshot, [b1, b2, b3]) + top_snapshot = TopSnapshot(snapshot, [], []) + frameinfo = FrameInfo(FrameInfo(FrameInfo(None, FakeJitCode("code1", 21), 22), + FakeJitCode("code2", 31), 32), FakeJitCode("code3", 41), 42) + storage.rd_snapshot = top_snapshot storage.rd_frame_info_list = frameinfo return storage @@ -1076,6 +1101,8 @@ def test_virtual_adder_int_constants(): assert storage.rd_snapshot is None cpu = MyCPU([]) reader = ResumeDataDirectReader(MyMetaInterp(cpu), storage, "deadframe") + reader.consume_vref_and_vable(None, None, None) + reader.cur_index += 2 # framestack _next_section(reader, sys.maxint, 2**16, -65) reader.cur_index += 2 # framestack _next_section(reader, 2, 3) @@ -1116,7 +1143,8 @@ class ResumeDataFakeReader(ResumeDataBoxReader): class MyInfo: @staticmethod def enumerate_vars(callback_i, callback_r, callback_f, _, index): - for tagged in self.numb.code: + while index < len(self.numb.code): + tagged, _ = resumecode.numb_next_item(self.numb, index) _, tag = untag(tagged) if tag == TAGVIRTUAL: kind = REF @@ -1131,6 +1159,13 @@ class ResumeDataFakeReader(ResumeDataBoxReader): index = callback_f(index, index) else: assert 0 + size, self.cur_index = resumecode.numb_next_item(self.numb, 0) + assert size == 0 + size, self.cur_index = resumecode.numb_next_item(self.numb, self.cur_index) + assert size == 0 + pc, self.cur_index = resumecode.numb_next_item(self.numb, self.cur_index) + jitcode_pos, self.cur_index = resumecode.numb_next_item(self.numb, self.cur_index) + self._prepare_next_section(MyInfo()) return self.lst diff --git a/rpython/jit/metainterp/test/test_resumecode.py b/rpython/jit/metainterp/test/test_resumecode.py index 848a4f1009..f65fae7d07 100644 --- a/rpython/jit/metainterp/test/test_resumecode.py +++ b/rpython/jit/metainterp/test/test_resumecode.py @@ -1,9 +1,12 @@ from rpython.jit.metainterp.resumecode import NUMBERING, NULL_NUMBER from rpython.jit.metainterp.resumecode import create_numbering,\ - unpack_numbering, copy_from_list_to_numb + unpack_numbering from rpython.rtyper.lltypesystem import lltype +from hypothesis import strategies, given + + def test_pack_unpack(): examples = [ [1, 2, 3, 4, 257, 10000, 13, 15], @@ -12,5 +15,15 @@ def test_pack_unpack(): [13000, 12000, 10000, 256, 255, 254, 257, -3, -1000] ] for l in examples: - n = create_numbering(l, 0) + n = create_numbering(l) assert unpack_numbering(n) == l + +@given(strategies.lists(strategies.integers(-2**15, 2**15-1))) +def test_roundtrip(l): + n = create_numbering(l) + assert unpack_numbering(n) == l + +@given(strategies.lists(strategies.integers(-2**15, 2**15-1))) +def test_compressing(l): + n = create_numbering(l) + assert len(n.code) <= len(l) * 3 diff --git a/rpython/jit/metainterp/test/test_tlc.py b/rpython/jit/metainterp/test/test_tlc.py index ee88035bf2..5c1396d533 100644 --- a/rpython/jit/metainterp/test/test_tlc.py +++ b/rpython/jit/metainterp/test/test_tlc.py @@ -1,5 +1,4 @@ import py -from rpython.rtyper.module.support import LLSupport from rpython.jit.tl import tlc diff --git a/rpython/jit/metainterp/virtualizable.py b/rpython/jit/metainterp/virtualizable.py index 08f2ec2059..3278ff0e84 100644 --- a/rpython/jit/metainterp/virtualizable.py +++ b/rpython/jit/metainterp/virtualizable.py @@ -142,6 +142,7 @@ class VirtualizableInfo(object): item, index = numb_next_item(numb, index) x = reader.load_value_of_type(ARRAYITEMTYPE, item) setarrayitem(lst, j, x) + return index def load_list_of_boxes(virtualizable, reader, vable_box, numb, index): virtualizable = cast_gcref_to_vtype(virtualizable) @@ -161,7 +162,7 @@ class VirtualizableInfo(object): box = reader.decode_box_of_type(ARRAYITEMTYPE, item) boxes.append(box) boxes.append(vable_box) - return boxes + return boxes, index def check_boxes(virtualizable, boxes): virtualizable = cast_gcref_to_vtype(virtualizable) diff --git a/rpython/memory/gc/incminimark.py b/rpython/memory/gc/incminimark.py index 6c04f077c6..7e9999fb6d 100644 --- a/rpython/memory/gc/incminimark.py +++ b/rpython/memory/gc/incminimark.py @@ -706,6 +706,7 @@ class IncrementalMiniMarkGC(MovingGCBase): self.major_collection_step() else: self.minor_and_major_collection() + self.rrc_invoke_callback() def collect_and_reserve(self, totalsize): @@ -783,12 +784,15 @@ class IncrementalMiniMarkGC(MovingGCBase): self.threshold_reached()): # ^^but only if still self.minor_collection() # the same collection self.major_collection_step() - # - # The nursery might not be empty now, because of - # execute_finalizers(). If it is almost full again, - # we need to fix it with another call to minor_collection(). - if self.nursery_free + totalsize > self.nursery_top: - self.minor_collection() + # + self.rrc_invoke_callback() + # + # The nursery might not be empty now, because of + # execute_finalizers() or rrc_invoke_callback(). + # If it is almost full again, + # we need to fix it with another call to minor_collection(). + if self.nursery_free + totalsize > self.nursery_top: + self.minor_collection() # else: ll_assert(minor_collection_count == 2, @@ -861,6 +865,7 @@ class IncrementalMiniMarkGC(MovingGCBase): if self.threshold_reached(raw_malloc_usage(totalsize) + self.nursery_size // 2): self.major_collection_step(raw_malloc_usage(totalsize)) + self.rrc_invoke_callback() # note that this loop should not be infinite: when the # last step of a major collection is done but # threshold_reached(totalsize) is still true, then @@ -1080,35 +1085,19 @@ class IncrementalMiniMarkGC(MovingGCBase): "odd-valued (i.e. tagged) pointer unexpected here") return self.nursery <= addr < self.nursery + self.nursery_size - def appears_to_be_young(self, addr): - # "is a valid addr to a young object?" - # but it's ok to occasionally return True accidentally. - # Maybe the best implementation would be a bloom filter - # of some kind instead of the dictionary lookup that is - # sometimes done below. But the expected common answer - # is "Yes" because addr points to the nursery, so it may - # not be useful to optimize the other case too much. - # - # First, if 'addr' appears to be a pointer to some place within - # the nursery, return True - if not self.translated_to_c: - # When non-translated, filter out tagged pointers explicitly. - # When translated, it may occasionally give a wrong answer - # of True if 'addr' is a tagged pointer with just the wrong value. - if not self.is_valid_gc_object(addr): - return False - + def is_young_object(self, addr): + # Check if the object at 'addr' is young. + if not self.is_valid_gc_object(addr): + return False # filter out tagged pointers explicitly. if self.nursery <= addr < self.nursery_top: return True # addr is in the nursery - # # Else, it may be in the set 'young_rawmalloced_objects' return (bool(self.young_rawmalloced_objects) and self.young_rawmalloced_objects.contains(addr)) - appears_to_be_young._always_inline_ = True def debug_is_old_object(self, addr): return (self.is_valid_gc_object(addr) - and not self.appears_to_be_young(addr)) + and not self.is_young_object(addr)) def is_forwarded(self, obj): """Returns True if the nursery obj is marked as forwarded. @@ -1618,6 +1607,10 @@ class IncrementalMiniMarkGC(MovingGCBase): self._visit_old_objects_pointing_to_pinned, None) current_old_objects_pointing_to_pinned.delete() # + # visit the P list from rawrefcount, if enabled. + if self.rrc_enabled: + self.rrc_minor_collection_trace() + # while True: # If we are using card marking, do a partial trace of the arrays # that are flagged with GCFLAG_CARDS_SET. @@ -1666,6 +1659,10 @@ class IncrementalMiniMarkGC(MovingGCBase): if self.young_rawmalloced_objects: self.free_young_rawmalloced_objects() # + # visit the P and O lists from rawrefcount, if enabled. + if self.rrc_enabled: + self.rrc_minor_collection_free() + # # All live nursery objects are out of the nursery or pinned inside # the nursery. Create nursery barriers to protect the pinned objects, # fill the rest of the nursery with zeros and reset the current nursery @@ -2178,9 +2175,13 @@ class IncrementalMiniMarkGC(MovingGCBase): # finalizers/weak references are rare and short which means that # they do not need a separate state and do not need to be # made incremental. + # For now, the same applies to rawrefcount'ed objects. if (not self.objects_to_trace.non_empty() and not self.more_objects_to_trace.non_empty()): # + if self.rrc_enabled: + self.rrc_major_collection_trace() + # if self.objects_with_finalizers.non_empty(): self.deal_with_objects_with_finalizers() elif self.old_objects_with_weakrefs.non_empty(): @@ -2215,6 +2216,10 @@ class IncrementalMiniMarkGC(MovingGCBase): self.old_objects_pointing_to_pinned = \ new_old_objects_pointing_to_pinned self.updated_old_objects_pointing_to_pinned = True + # + if self.rrc_enabled: + self.rrc_major_collection_free() + # self.gc_state = STATE_SWEEPING #END MARKING elif self.gc_state == STATE_SWEEPING: @@ -2745,3 +2750,238 @@ class IncrementalMiniMarkGC(MovingGCBase): (obj + offset).address[0] = llmemory.NULL self.old_objects_with_weakrefs.delete() self.old_objects_with_weakrefs = new_with_weakref + + + # ---------- + # RawRefCount + + rrc_enabled = False + + _ADDRARRAY = lltype.Array(llmemory.Address, hints={'nolength': True}) + PYOBJ_HDR = lltype.Struct('GCHdr_PyObject', + ('ob_refcnt', lltype.Signed), + ('ob_pypy_link', lltype.Signed)) + PYOBJ_HDR_PTR = lltype.Ptr(PYOBJ_HDR) + RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void)) + + def _pyobj(self, pyobjaddr): + return llmemory.cast_adr_to_ptr(pyobjaddr, self.PYOBJ_HDR_PTR) + + def rawrefcount_init(self, dealloc_trigger_callback): + # see pypy/doc/discussion/rawrefcount.rst + if not self.rrc_enabled: + self.rrc_p_list_young = self.AddressStack() + self.rrc_p_list_old = self.AddressStack() + self.rrc_o_list_young = self.AddressStack() + self.rrc_o_list_old = self.AddressStack() + self.rrc_p_dict = self.AddressDict() # non-nursery keys only + self.rrc_p_dict_nurs = self.AddressDict() # nursery keys only + p = lltype.malloc(self._ADDRARRAY, 1, flavor='raw', + track_allocation=False) + self.rrc_singleaddr = llmemory.cast_ptr_to_adr(p) + self.rrc_dealloc_trigger_callback = dealloc_trigger_callback + self.rrc_dealloc_pending = self.AddressStack() + self.rrc_enabled = True + + def check_no_more_rawrefcount_state(self): + "NOT_RPYTHON: for tests" + assert self.rrc_p_list_young.length() == 0 + assert self.rrc_p_list_old .length() == 0 + assert self.rrc_o_list_young.length() == 0 + assert self.rrc_o_list_old .length() == 0 + def check_value_is_null(key, value, ignore): + assert value == llmemory.NULL + self.rrc_p_dict.foreach(check_value_is_null, None) + self.rrc_p_dict_nurs.foreach(check_value_is_null, None) + + def rawrefcount_create_link_pypy(self, gcobj, pyobject): + ll_assert(self.rrc_enabled, "rawrefcount.init not called") + obj = llmemory.cast_ptr_to_adr(gcobj) + objint = llmemory.cast_adr_to_int(obj, "symbolic") + self._pyobj(pyobject).ob_pypy_link = objint + # + lst = self.rrc_p_list_young + if self.is_in_nursery(obj): + dct = self.rrc_p_dict_nurs + else: + dct = self.rrc_p_dict + if not self.is_young_object(obj): + lst = self.rrc_p_list_old + lst.append(pyobject) + dct.setitem(obj, pyobject) + + def rawrefcount_create_link_pyobj(self, gcobj, pyobject): + ll_assert(self.rrc_enabled, "rawrefcount.init not called") + obj = llmemory.cast_ptr_to_adr(gcobj) + if self.is_young_object(obj): + self.rrc_o_list_young.append(pyobject) + else: + self.rrc_o_list_old.append(pyobject) + objint = llmemory.cast_adr_to_int(obj, "symbolic") + self._pyobj(pyobject).ob_pypy_link = objint + # there is no rrc_o_dict + + def rawrefcount_from_obj(self, gcobj): + obj = llmemory.cast_ptr_to_adr(gcobj) + if self.is_in_nursery(obj): + dct = self.rrc_p_dict_nurs + else: + dct = self.rrc_p_dict + return dct.get(obj) + + def rawrefcount_to_obj(self, pyobject): + obj = llmemory.cast_int_to_adr(self._pyobj(pyobject).ob_pypy_link) + return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF) + + def rawrefcount_next_dead(self): + if self.rrc_dealloc_pending.non_empty(): + return self.rrc_dealloc_pending.pop() + return llmemory.NULL + + + def rrc_invoke_callback(self): + if self.rrc_enabled and self.rrc_dealloc_pending.non_empty(): + self.rrc_dealloc_trigger_callback() + + def rrc_minor_collection_trace(self): + length_estimate = self.rrc_p_dict_nurs.length() + self.rrc_p_dict_nurs.delete() + self.rrc_p_dict_nurs = self.AddressDict(length_estimate) + self.rrc_p_list_young.foreach(self._rrc_minor_trace, + self.rrc_singleaddr) + + def _rrc_minor_trace(self, pyobject, singleaddr): + from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY + from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT + # + rc = self._pyobj(pyobject).ob_refcnt + if rc == REFCNT_FROM_PYPY or rc == REFCNT_FROM_PYPY_LIGHT: + pass # the corresponding object may die + else: + # force the corresponding object to be alive + intobj = self._pyobj(pyobject).ob_pypy_link + singleaddr.address[0] = llmemory.cast_int_to_adr(intobj) + self._trace_drag_out(singleaddr, llmemory.NULL) + + def rrc_minor_collection_free(self): + ll_assert(self.rrc_p_dict_nurs.length() == 0, "p_dict_nurs not empty 1") + lst = self.rrc_p_list_young + while lst.non_empty(): + self._rrc_minor_free(lst.pop(), self.rrc_p_list_old, + self.rrc_p_dict) + lst = self.rrc_o_list_young + no_o_dict = self.null_address_dict() + while lst.non_empty(): + self._rrc_minor_free(lst.pop(), self.rrc_o_list_old, + no_o_dict) + + def _rrc_minor_free(self, pyobject, surviving_list, surviving_dict): + intobj = self._pyobj(pyobject).ob_pypy_link + obj = llmemory.cast_int_to_adr(intobj) + if self.is_in_nursery(obj): + if self.is_forwarded(obj): + # Common case: survives and moves + obj = self.get_forwarding_address(obj) + intobj = llmemory.cast_adr_to_int(obj, "symbolic") + self._pyobj(pyobject).ob_pypy_link = intobj + surviving = True + if surviving_dict: + # Surviving nursery object: was originally in + # rrc_p_dict_nurs and now must be put into rrc_p_dict + surviving_dict.setitem(obj, pyobject) + else: + surviving = False + elif (bool(self.young_rawmalloced_objects) and + self.young_rawmalloced_objects.contains(obj)): + # young weakref to a young raw-malloced object + if self.header(obj).tid & GCFLAG_VISITED_RMY: + surviving = True # survives, but does not move + else: + surviving = False + if surviving_dict: + # Dying young large object: was in rrc_p_dict, + # must be deleted + surviving_dict.setitem(obj, llmemory.NULL) + else: + ll_assert(False, "rrc_X_list_young contains non-young obj") + return + # + if surviving: + surviving_list.append(pyobject) + else: + self._rrc_free(pyobject) + + def _rrc_free(self, pyobject): + from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY + from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT + # + rc = self._pyobj(pyobject).ob_refcnt + if rc >= REFCNT_FROM_PYPY_LIGHT: + rc -= REFCNT_FROM_PYPY_LIGHT + if rc == 0: + lltype.free(self._pyobj(pyobject), flavor='raw') + else: + # can only occur if LIGHT is used in create_link_pyobj() + self._pyobj(pyobject).ob_refcnt = rc + self._pyobj(pyobject).ob_pypy_link = 0 + else: + ll_assert(rc >= REFCNT_FROM_PYPY, "refcount underflow?") + ll_assert(rc < int(REFCNT_FROM_PYPY_LIGHT * 0.99), + "refcount underflow from REFCNT_FROM_PYPY_LIGHT?") + rc -= REFCNT_FROM_PYPY + self._pyobj(pyobject).ob_refcnt = rc + self._pyobj(pyobject).ob_pypy_link = 0 + if rc == 0: + self.rrc_dealloc_pending.append(pyobject) + _rrc_free._always_inline_ = True + + def rrc_major_collection_trace(self): + self.rrc_p_list_old.foreach(self._rrc_major_trace, None) + + def _rrc_major_trace(self, pyobject, ignore): + from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY + from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT + # + rc = self._pyobj(pyobject).ob_refcnt + if rc == REFCNT_FROM_PYPY or rc == REFCNT_FROM_PYPY_LIGHT: + pass # the corresponding object may die + else: + # force the corresponding object to be alive + intobj = self._pyobj(pyobject).ob_pypy_link + obj = llmemory.cast_int_to_adr(intobj) + self.objects_to_trace.append(obj) + self.visit_all_objects() + + def rrc_major_collection_free(self): + ll_assert(self.rrc_p_dict_nurs.length() == 0, "p_dict_nurs not empty 2") + length_estimate = self.rrc_p_dict.length() + self.rrc_p_dict.delete() + self.rrc_p_dict = new_p_dict = self.AddressDict(length_estimate) + new_p_list = self.AddressStack() + while self.rrc_p_list_old.non_empty(): + self._rrc_major_free(self.rrc_p_list_old.pop(), new_p_list, + new_p_dict) + self.rrc_p_list_old.delete() + self.rrc_p_list_old = new_p_list + # + new_o_list = self.AddressStack() + no_o_dict = self.null_address_dict() + while self.rrc_o_list_old.non_empty(): + self._rrc_major_free(self.rrc_o_list_old.pop(), new_o_list, + no_o_dict) + self.rrc_o_list_old.delete() + self.rrc_o_list_old = new_o_list + + def _rrc_major_free(self, pyobject, surviving_list, surviving_dict): + # The pyobject survives if the corresponding obj survives. + # This is true if the obj has one of the following two flags: + # * GCFLAG_VISITED: was seen during tracing + # * GCFLAG_NO_HEAP_PTRS: immortal object never traced (so far) + intobj = self._pyobj(pyobject).ob_pypy_link + obj = llmemory.cast_int_to_adr(intobj) + if self.header(obj).tid & (GCFLAG_VISITED | GCFLAG_NO_HEAP_PTRS): + surviving_list.append(pyobject) + if surviving_dict: + surviving_dict.insertclean(obj, pyobject) + else: + self._rrc_free(pyobject) diff --git a/rpython/memory/gc/test/test_rawrefcount.py b/rpython/memory/gc/test/test_rawrefcount.py new file mode 100644 index 0000000000..aca61015b8 --- /dev/null +++ b/rpython/memory/gc/test/test_rawrefcount.py @@ -0,0 +1,282 @@ +import py +from rpython.rtyper.lltypesystem import lltype, llmemory +from rpython.memory.gc.incminimark import IncrementalMiniMarkGC +from rpython.memory.gc.test.test_direct import BaseDirectGCTest +from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY +from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT + +PYOBJ_HDR = IncrementalMiniMarkGC.PYOBJ_HDR +PYOBJ_HDR_PTR = IncrementalMiniMarkGC.PYOBJ_HDR_PTR + +S = lltype.GcForwardReference() +S.become(lltype.GcStruct('S', + ('x', lltype.Signed), + ('prev', lltype.Ptr(S)), + ('next', lltype.Ptr(S)))) + + +class TestRawRefCount(BaseDirectGCTest): + GCClass = IncrementalMiniMarkGC + + def _collect(self, major, expected_trigger=0): + if major: + self.gc.collect() + else: + self.gc.minor_collection() + count1 = len(self.trigger) + self.gc.rrc_invoke_callback() + count2 = len(self.trigger) + assert count2 - count1 == expected_trigger + + def _rawrefcount_pair(self, intval, is_light=False, is_pyobj=False, + create_old=False, create_immortal=False): + if is_light: + rc = REFCNT_FROM_PYPY_LIGHT + else: + rc = REFCNT_FROM_PYPY + self.trigger = [] + self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # + if create_immortal: + p1 = lltype.malloc(S, immortal=True) + else: + p1 = self.malloc(S) + p1.x = intval + if create_immortal: + self.consider_constant(p1) + elif create_old: + self.stackroots.append(p1) + self._collect(major=False) + p1 = self.stackroots.pop() + p1ref = lltype.cast_opaque_ptr(llmemory.GCREF, p1) + r1 = lltype.malloc(PYOBJ_HDR, flavor='raw', immortal=create_immortal) + r1.ob_refcnt = rc + r1.ob_pypy_link = 0 + r1addr = llmemory.cast_ptr_to_adr(r1) + if is_pyobj: + assert not is_light + self.gc.rawrefcount_create_link_pyobj(p1ref, r1addr) + else: + self.gc.rawrefcount_create_link_pypy(p1ref, r1addr) + assert r1.ob_refcnt == rc + assert r1.ob_pypy_link != 0 + + def check_alive(extra_refcount): + assert r1.ob_refcnt == rc + extra_refcount + assert r1.ob_pypy_link != 0 + p1ref = self.gc.rawrefcount_to_obj(r1addr) + p1 = lltype.cast_opaque_ptr(lltype.Ptr(S), p1ref) + assert p1.x == intval + if not is_pyobj: + assert self.gc.rawrefcount_from_obj(p1ref) == r1addr + else: + assert self.gc.rawrefcount_from_obj(p1ref) == llmemory.NULL + return p1 + return p1, p1ref, r1, r1addr, check_alive + + def test_rawrefcount_objects_basic(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_light=True, create_old=old)) + p2 = self.malloc(S) + p2.x = 84 + p2ref = lltype.cast_opaque_ptr(llmemory.GCREF, p2) + r2 = lltype.malloc(PYOBJ_HDR, flavor='raw') + r2.ob_refcnt = 1 + r2.ob_pypy_link = 0 + r2addr = llmemory.cast_ptr_to_adr(r2) + # p2 and r2 are not linked + assert r1.ob_pypy_link != 0 + assert r2.ob_pypy_link == 0 + assert self.gc.rawrefcount_from_obj(p1ref) == r1addr + assert self.gc.rawrefcount_from_obj(p2ref) == llmemory.NULL + assert self.gc.rawrefcount_to_obj(r1addr) == p1ref + assert self.gc.rawrefcount_to_obj(r2addr) == lltype.nullptr( + llmemory.GCREF.TO) + lltype.free(r1, flavor='raw') + lltype.free(r2, flavor='raw') + + def test_rawrefcount_objects_collection_survives_from_raw(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_light=True, create_old=old)) + check_alive(0) + r1.ob_refcnt += 1 + self._collect(major=False) + check_alive(+1) + self._collect(major=True) + check_alive(+1) + r1.ob_refcnt -= 1 + self._collect(major=False) + p1 = check_alive(0) + self._collect(major=True) + py.test.raises(RuntimeError, "r1.ob_refcnt") # dead + py.test.raises(RuntimeError, "p1.x") # dead + self.gc.check_no_more_rawrefcount_state() + assert self.trigger == [] + assert self.gc.rawrefcount_next_dead() == llmemory.NULL + + def test_rawrefcount_dies_quickly(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_light=True, create_old=old)) + check_alive(0) + self._collect(major=False) + if old: + check_alive(0) + self._collect(major=True) + py.test.raises(RuntimeError, "r1.ob_refcnt") # dead + py.test.raises(RuntimeError, "p1.x") # dead + self.gc.check_no_more_rawrefcount_state() + + def test_rawrefcount_objects_collection_survives_from_obj(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_light=True, create_old=old)) + check_alive(0) + self.stackroots.append(p1) + self._collect(major=False) + check_alive(0) + self._collect(major=True) + check_alive(0) + p1 = self.stackroots.pop() + self._collect(major=False) + check_alive(0) + assert p1.x == 42 + self._collect(major=True) + py.test.raises(RuntimeError, "r1.ob_refcnt") # dead + py.test.raises(RuntimeError, "p1.x") # dead + self.gc.check_no_more_rawrefcount_state() + + def test_rawrefcount_objects_basic_old(self): + self.test_rawrefcount_objects_basic(old=True) + def test_rawrefcount_objects_collection_survives_from_raw_old(self): + self.test_rawrefcount_objects_collection_survives_from_raw(old=True) + def test_rawrefcount_dies_quickly_old(self): + self.test_rawrefcount_dies_quickly(old=True) + def test_rawrefcount_objects_collection_survives_from_obj_old(self): + self.test_rawrefcount_objects_collection_survives_from_obj(old=True) + + def test_pypy_nonlight_survives_from_raw(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_light=False, create_old=old)) + check_alive(0) + r1.ob_refcnt += 1 + self._collect(major=False) + check_alive(+1) + self._collect(major=True) + check_alive(+1) + r1.ob_refcnt -= 1 + self._collect(major=False) + p1 = check_alive(0) + self._collect(major=True, expected_trigger=1) + py.test.raises(RuntimeError, "p1.x") # dead + assert r1.ob_refcnt == 0 + assert r1.ob_pypy_link == 0 + assert self.gc.rawrefcount_next_dead() == r1addr + assert self.gc.rawrefcount_next_dead() == llmemory.NULL + assert self.gc.rawrefcount_next_dead() == llmemory.NULL + self.gc.check_no_more_rawrefcount_state() + lltype.free(r1, flavor='raw') + + def test_pypy_nonlight_survives_from_obj(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_light=False, create_old=old)) + check_alive(0) + self.stackroots.append(p1) + self._collect(major=False) + check_alive(0) + self._collect(major=True) + check_alive(0) + p1 = self.stackroots.pop() + self._collect(major=False) + check_alive(0) + assert p1.x == 42 + self._collect(major=True, expected_trigger=1) + py.test.raises(RuntimeError, "p1.x") # dead + assert r1.ob_refcnt == 0 + assert r1.ob_pypy_link == 0 + assert self.gc.rawrefcount_next_dead() == r1addr + self.gc.check_no_more_rawrefcount_state() + lltype.free(r1, flavor='raw') + + def test_pypy_nonlight_dies_quickly(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_light=False, create_old=old)) + check_alive(0) + if old: + self._collect(major=False) + check_alive(0) + self._collect(major=True, expected_trigger=1) + else: + self._collect(major=False, expected_trigger=1) + py.test.raises(RuntimeError, "p1.x") # dead + assert r1.ob_refcnt == 0 + assert r1.ob_pypy_link == 0 + assert self.gc.rawrefcount_next_dead() == r1addr + self.gc.check_no_more_rawrefcount_state() + lltype.free(r1, flavor='raw') + + def test_pypy_nonlight_survives_from_raw_old(self): + self.test_pypy_nonlight_survives_from_raw(old=True) + def test_pypy_nonlight_survives_from_obj_old(self): + self.test_pypy_nonlight_survives_from_obj(old=True) + def test_pypy_nonlight_dies_quickly_old(self): + self.test_pypy_nonlight_dies_quickly(old=True) + + def test_pyobject_pypy_link_dies_on_minor_collection(self): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_pyobj=True)) + check_alive(0) + r1.ob_refcnt += 1 # the pyobject is kept alive + self._collect(major=False) + assert r1.ob_refcnt == 1 # refcnt dropped to 1 + assert r1.ob_pypy_link == 0 # detached + self.gc.check_no_more_rawrefcount_state() + lltype.free(r1, flavor='raw') + + def test_pyobject_dies(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_pyobj=True, create_old=old)) + check_alive(0) + if old: + self._collect(major=False) + check_alive(0) + self._collect(major=True, expected_trigger=1) + else: + self._collect(major=False, expected_trigger=1) + assert r1.ob_refcnt == 0 # refcnt dropped to 0 + assert r1.ob_pypy_link == 0 # detached + assert self.gc.rawrefcount_next_dead() == r1addr + self.gc.check_no_more_rawrefcount_state() + lltype.free(r1, flavor='raw') + + def test_pyobject_survives_from_obj(self, old=False): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, is_pyobj=True, create_old=old)) + check_alive(0) + self.stackroots.append(p1) + self._collect(major=False) + check_alive(0) + self._collect(major=True) + check_alive(0) + p1 = self.stackroots.pop() + self._collect(major=False) + check_alive(0) + assert p1.x == 42 + assert self.trigger == [] + self._collect(major=True, expected_trigger=1) + py.test.raises(RuntimeError, "p1.x") # dead + assert r1.ob_refcnt == 0 + assert r1.ob_pypy_link == 0 + assert self.gc.rawrefcount_next_dead() == r1addr + self.gc.check_no_more_rawrefcount_state() + lltype.free(r1, flavor='raw') + + def test_pyobject_dies_old(self): + self.test_pyobject_dies(old=True) + def test_pyobject_survives_from_obj_old(self): + self.test_pyobject_survives_from_obj(old=True) + + def test_pyobject_attached_to_prebuilt_obj(self): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, create_immortal=True)) + check_alive(0) + self._collect(major=True) + check_alive(0) diff --git a/rpython/memory/gctransform/boehm.py b/rpython/memory/gctransform/boehm.py index 5144714329..25f72d32f6 100644 --- a/rpython/memory/gctransform/boehm.py +++ b/rpython/memory/gctransform/boehm.py @@ -156,9 +156,9 @@ class BoehmGCTransformer(GCTransformer): resulttype = lltype.Signed) hop.genop('int_invert', [v_int], resultvar=hop.spaceop.result) - def gcheader_initdata(self, defnode): + def gcheader_initdata(self, obj): hdr = lltype.malloc(self.HDR, immortal=True) - hdr.hash = lltype.identityhash_nocache(defnode.obj._as_ptr()) + hdr.hash = lltype.identityhash_nocache(obj._as_ptr()) return hdr._obj diff --git a/rpython/memory/gctransform/framework.py b/rpython/memory/gctransform/framework.py index 140e3cab11..8a7c0f06ad 100644 --- a/rpython/memory/gctransform/framework.py +++ b/rpython/memory/gctransform/framework.py @@ -153,6 +153,7 @@ class BaseFrameworkGCTransformer(GCTransformer): else: # for regular translation: pick the GC from the config GCClass, GC_PARAMS = choose_gc_from_config(translator.config) + self.GCClass = GCClass if hasattr(translator, '_jit2gc'): self.layoutbuilder = translator._jit2gc['layoutbuilder'] @@ -482,6 +483,29 @@ class BaseFrameworkGCTransformer(GCTransformer): annmodel.SomeInteger(nonneg=True)], annmodel.s_None) + if hasattr(GCClass, 'rawrefcount_init'): + self.rawrefcount_init_ptr = getfn( + GCClass.rawrefcount_init, + [s_gc, SomePtr(GCClass.RAWREFCOUNT_DEALLOC_TRIGGER)], + annmodel.s_None) + self.rawrefcount_create_link_pypy_ptr = getfn( + GCClass.rawrefcount_create_link_pypy, + [s_gc, s_gcref, SomeAddress()], + annmodel.s_None) + self.rawrefcount_create_link_pyobj_ptr = getfn( + GCClass.rawrefcount_create_link_pyobj, + [s_gc, s_gcref, SomeAddress()], + annmodel.s_None) + self.rawrefcount_from_obj_ptr = getfn( + GCClass.rawrefcount_from_obj, [s_gc, s_gcref], SomeAddress(), + inline = True) + self.rawrefcount_to_obj_ptr = getfn( + GCClass.rawrefcount_to_obj, [s_gc, SomeAddress()], s_gcref, + inline = True) + self.rawrefcount_next_dead_ptr = getfn( + GCClass.rawrefcount_next_dead, [s_gc], SomeAddress(), + inline = True) + if GCClass.can_usually_pin_objects: self.pin_ptr = getfn(GCClass.pin, [s_gc, SomeAddress()], @@ -1227,6 +1251,50 @@ class BaseFrameworkGCTransformer(GCTransformer): resultvar=hop.spaceop.result) self.pop_roots(hop, livevars) + def gct_gc_rawrefcount_init(self, hop): + [v_fnptr] = hop.spaceop.args + assert v_fnptr.concretetype == self.GCClass.RAWREFCOUNT_DEALLOC_TRIGGER + hop.genop("direct_call", + [self.rawrefcount_init_ptr, self.c_const_gc, v_fnptr]) + + def gct_gc_rawrefcount_create_link_pypy(self, hop): + [v_gcobj, v_pyobject] = hop.spaceop.args + assert v_gcobj.concretetype == llmemory.GCREF + assert v_pyobject.concretetype == llmemory.Address + hop.genop("direct_call", + [self.rawrefcount_create_link_pypy_ptr, self.c_const_gc, + v_gcobj, v_pyobject]) + + def gct_gc_rawrefcount_create_link_pyobj(self, hop): + [v_gcobj, v_pyobject] = hop.spaceop.args + assert v_gcobj.concretetype == llmemory.GCREF + assert v_pyobject.concretetype == llmemory.Address + hop.genop("direct_call", + [self.rawrefcount_create_link_pyobj_ptr, self.c_const_gc, + v_gcobj, v_pyobject]) + + def gct_gc_rawrefcount_from_obj(self, hop): + [v_gcobj] = hop.spaceop.args + assert v_gcobj.concretetype == llmemory.GCREF + assert hop.spaceop.result.concretetype == llmemory.Address + hop.genop("direct_call", + [self.rawrefcount_from_obj_ptr, self.c_const_gc, v_gcobj], + resultvar=hop.spaceop.result) + + def gct_gc_rawrefcount_to_obj(self, hop): + [v_pyobject] = hop.spaceop.args + assert v_pyobject.concretetype == llmemory.Address + assert hop.spaceop.result.concretetype == llmemory.GCREF + hop.genop("direct_call", + [self.rawrefcount_to_obj_ptr, self.c_const_gc, v_pyobject], + resultvar=hop.spaceop.result) + + def gct_gc_rawrefcount_next_dead(self, hop): + assert hop.spaceop.result.concretetype == llmemory.Address + hop.genop("direct_call", + [self.rawrefcount_next_dead_ptr, self.c_const_gc], + resultvar=hop.spaceop.result) + def _set_into_gc_array_part(self, op): if op.opname == 'setarrayitem': return op.args[1] @@ -1411,8 +1479,8 @@ class BaseFrameworkGCTransformer(GCTransformer): resulttype=llmemory.Address) llops.genop('raw_memclear', [v_adr, v_totalsize]) - def gcheader_initdata(self, defnode): - o = lltype.top_container(defnode.obj) + def gcheader_initdata(self, obj): + o = lltype.top_container(obj) needs_hash = self.get_prebuilt_hash(o) is not None hdr = self.gc_header_for(o, needs_hash) return hdr._obj diff --git a/rpython/memory/gctransform/refcounting.py b/rpython/memory/gctransform/refcounting.py index ae6f64b4ee..a496db590c 100644 --- a/rpython/memory/gctransform/refcounting.py +++ b/rpython/memory/gctransform/refcounting.py @@ -286,6 +286,6 @@ def ll_deallocator(addr): hop.genop("direct_call", [self.identityhash_ptr, v_adr], resultvar=hop.spaceop.result) - def gcheader_initdata(self, defnode): - top = lltype.top_container(defnode.obj) + def gcheader_initdata(self, obj): + top = lltype.top_container(obj) return self.gcheaderbuilder.header_of_object(top)._obj diff --git a/rpython/memory/gctransform/test/test_transform.py b/rpython/memory/gctransform/test/test_transform.py index 03343618db..9cd4382c5d 100644 --- a/rpython/memory/gctransform/test/test_transform.py +++ b/rpython/memory/gctransform/test/test_transform.py @@ -5,6 +5,7 @@ from rpython.translator.translator import TranslationContext, graphof from rpython.translator.exceptiontransform import ExceptionTransformer from rpython.rtyper.lltypesystem import lltype from rpython.conftest import option +from rpython.rtyper.rtyper import llinterp_backend class LLInterpedTranformerTests: @@ -131,8 +132,10 @@ def checkblock(block, is_borrowed, is_start_block): def rtype(func, inputtypes, specialize=True): t = TranslationContext() t.buildannotator().build_types(func, inputtypes) + rtyper = t.buildrtyper() + rtyper.backend = llinterp_backend if specialize: - t.buildrtyper().specialize() + rtyper.specialize() if option.view: t.view() return t diff --git a/rpython/memory/test/test_transformed_gc.py b/rpython/memory/test/test_transformed_gc.py index 81b77451a2..8feeb9198b 100644 --- a/rpython/memory/test/test_transformed_gc.py +++ b/rpython/memory/test/test_transformed_gc.py @@ -14,6 +14,7 @@ from rpython.rlib import rgc from rpython.conftest import option from rpython.rlib.rstring import StringBuilder from rpython.rlib.rarithmetic import LONG_BIT +from rpython.rtyper.rtyper import llinterp_backend WORD = LONG_BIT // 8 @@ -29,9 +30,11 @@ def rtype(func, inputtypes, specialize=True, gcname='ref', t.config.set(**extraconfigopts) ann = t.buildannotator() ann.build_types(func, inputtypes) + rtyper = t.buildrtyper() + rtyper.backend = llinterp_backend if specialize: - t.buildrtyper().specialize() + rtyper.specialize() if backendopt: from rpython.translator.backendopt.all import backend_optimizations backend_optimizations(t) diff --git a/rpython/rtyper/module/support.py b/rpython/rlib/_os_support.py index fac520788b..0365efdb08 100644 --- a/rpython/rtyper/module/support.py +++ b/rpython/rlib/_os_support.py @@ -1,53 +1,20 @@ import sys -from rpython.annotator import model as annmodel -from rpython.rtyper.lltypesystem import lltype, rffi -from rpython.rlib.objectmodel import specialize +from rpython.annotator.model import s_Str0, s_Unicode0 from rpython.rlib import rstring +from rpython.rlib.objectmodel import specialize +from rpython.rtyper.lltypesystem import rffi + +_CYGWIN = sys.platform == 'cygwin' _WIN32 = sys.platform.startswith('win') UNDERSCORE_ON_WIN32 = '_' if _WIN32 else '' - -# utility conversion functions -class LLSupport: - _mixin_ = True - - def to_rstr(s): - from rpython.rtyper.lltypesystem.rstr import STR, mallocstr - if s is None: - return lltype.nullptr(STR) - p = mallocstr(len(s)) - for i in range(len(s)): - p.chars[i] = s[i] - return p - to_rstr = staticmethod(to_rstr) - - def to_runicode(s): - from rpython.rtyper.lltypesystem.rstr import UNICODE, mallocunicode - if s is None: - return lltype.nullptr(UNICODE) - p = mallocunicode(len(s)) - for i in range(len(s)): - p.chars[i] = s[i] - return p - to_runicode = staticmethod(to_runicode) - - def from_rstr(rs): - if not rs: # null pointer - return None - else: - return ''.join([rs.chars[i] for i in range(len(rs.chars))]) - from_rstr = staticmethod(from_rstr) - - def from_rstr_nonnull(rs): - assert rs - return ''.join([rs.chars[i] for i in range(len(rs.chars))]) - from_rstr_nonnull = staticmethod(from_rstr_nonnull) +_MACRO_ON_POSIX = True if not _WIN32 else None -class StringTraits: +class StringTraits(object): str = str - str0 = annmodel.s_Str0 + str0 = s_Str0 CHAR = rffi.CHAR CCHARP = rffi.CCHARP charp2str = staticmethod(rffi.charp2str) @@ -58,14 +25,6 @@ class StringTraits: scoped_alloc_buffer = staticmethod(rffi.scoped_alloc_buffer) @staticmethod - def posix_function_name(name): - return UNDERSCORE_ON_WIN32 + name - - @staticmethod - def ll_os_name(name): - return 'll_os.ll_os_' + name - - @staticmethod @specialize.argtype(0) def as_str(path): assert path is not None @@ -77,7 +36,7 @@ class StringTraits: # We implement python2 behavior: silently convert to ascii. return path.encode('ascii') else: - return path.as_bytes() + return path.as_bytes() @staticmethod @specialize.argtype(0) @@ -86,9 +45,10 @@ class StringTraits: rstring.check_str0(res) return res -class UnicodeTraits: + +class UnicodeTraits(object): str = unicode - str0 = annmodel.s_Unicode0 + str0 = s_Unicode0 CHAR = rffi.WCHAR_T CCHARP = rffi.CWCHARP charp2str = staticmethod(rffi.wcharp2unicode) @@ -99,15 +59,6 @@ class UnicodeTraits: scoped_alloc_buffer = staticmethod(rffi.scoped_alloc_unicodebuffer) @staticmethod - def posix_function_name(name): - return UNDERSCORE_ON_WIN32 + 'w' + name - - @staticmethod - @specialize.argtype(0) - def ll_os_name(name): - return 'll_os.ll_os_w' + name - - @staticmethod @specialize.argtype(0) def as_str(path): assert path is not None @@ -115,7 +66,7 @@ class UnicodeTraits: return path else: return path.as_unicode() - + @staticmethod @specialize.argtype(0) def as_str0(path): @@ -123,17 +74,36 @@ class UnicodeTraits: rstring.check_str0(res) return res -def ll_strcpy(dst_s, src_s, n): - dstchars = dst_s.chars - srcchars = src_s.chars - i = 0 - while i < n: - dstchars[i] = srcchars[i] - i += 1 - -def _ll_strfill(dst_s, srcchars, n): - dstchars = dst_s.chars - i = 0 - while i < n: - dstchars[i] = srcchars[i] - i += 1 + +string_traits = StringTraits() +unicode_traits = UnicodeTraits() + + +# Returns True when the unicode function should be called: +# - on Windows +# - if the path is Unicode. +if _WIN32: + @specialize.argtype(0) + def _prefer_unicode(path): + assert path is not None + if isinstance(path, str): + return False + elif isinstance(path, unicode): + return True + else: + return path.is_unicode + + @specialize.argtype(0) + def _preferred_traits(path): + if _prefer_unicode(path): + return unicode_traits + else: + return string_traits +else: + @specialize.argtype(0) + def _prefer_unicode(path): + return False + + @specialize.argtype(0) + def _preferred_traits(path): + return string_traits diff --git a/rpython/rlib/exports.py b/rpython/rlib/exports.py index 1ae9ae4778..391678e1c4 100644 --- a/rpython/rlib/exports.py +++ b/rpython/rlib/exports.py @@ -1,5 +1,7 @@ from rpython.rtyper.lltypesystem.lltype import typeOf, ContainerType +# XXX kill me + def export_struct(name, struct): assert name not in EXPORTS_names, "Duplicate export " + name assert isinstance(typeOf(struct), ContainerType) diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py index d17e919e62..7017795baf 100644 --- a/rpython/rlib/jit.py +++ b/rpython/rlib/jit.py @@ -284,7 +284,7 @@ isvirtual._annspecialcase_ = "specialize:call_location" def loop_unrolling_heuristic(lst, size, cutoff=2): """ In which cases iterating over items of lst can be unrolled """ - return isvirtual(lst) or (isconstant(size) and size <= cutoff) + return size == 0 or isvirtual(lst) or (isconstant(size) and size <= cutoff) class Entry(ExtRegistryEntry): _about_ = hint @@ -1168,6 +1168,24 @@ class ConditionalCallEntry(ExtRegistryEntry): hop.exception_is_here() return hop.genop('jit_conditional_call', args_v) +def enter_portal_frame(unique_id): + """call this when starting to interpret a function. calling this is not + necessary for almost all interpreters. The only exception is stackless + interpreters where the portal never calls itself. + """ + from rpython.rtyper.lltypesystem import lltype + from rpython.rtyper.lltypesystem.lloperation import llop + llop.jit_enter_portal_frame(lltype.Void, unique_id) + +def leave_portal_frame(): + """call this after the end of executing a function. calling this is not + necessary for almost all interpreters. The only exception is stackless + interpreters where the portal never calls itself. + """ + from rpython.rtyper.lltypesystem import lltype + from rpython.rtyper.lltypesystem.lloperation import llop + llop.jit_leave_portal_frame(lltype.Void) + class Counters(object): counters=""" TRACING diff --git a/rpython/rlib/objectmodel.py b/rpython/rlib/objectmodel.py index 464afa1ee5..aa2a2d78c0 100644 --- a/rpython/rlib/objectmodel.py +++ b/rpython/rlib/objectmodel.py @@ -275,8 +275,6 @@ class CDefinedIntSymbolic(Symbolic): return lltype.Signed malloc_zero_filled = CDefinedIntSymbolic('MALLOC_ZERO_FILLED', default=0) -running_on_llinterp = CDefinedIntSymbolic('RUNNING_ON_LLINTERP', default=1) -# running_on_llinterp is meant to have the value 0 in all backends # ____________________________________________________________ diff --git a/rpython/rlib/rawrefcount.py b/rpython/rlib/rawrefcount.py new file mode 100644 index 0000000000..cb19b18c47 --- /dev/null +++ b/rpython/rlib/rawrefcount.py @@ -0,0 +1,265 @@ +# +# See documentation in pypy/doc/discussion/rawrefcount.rst +# +# This is meant for pypy's cpyext module, but is a generally +# useful interface over our GC. XXX "pypy" should be removed here +# +import sys, weakref +from rpython.rtyper.lltypesystem import lltype, llmemory +from rpython.rlib.objectmodel import we_are_translated, specialize +from rpython.rtyper.extregistry import ExtRegistryEntry +from rpython.rlib import rgc + + +REFCNT_FROM_PYPY = sys.maxint // 4 + 1 +REFCNT_FROM_PYPY_LIGHT = REFCNT_FROM_PYPY + (sys.maxint // 2 + 1) + +RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void)) + + +def _build_pypy_link(p): + res = len(_adr2pypy) + _adr2pypy.append(p) + return res + + +def init(dealloc_trigger_callback=None): + """NOT_RPYTHON: set up rawrefcount with the GC. This is only used + for tests; it should not be called at all during translation. + """ + global _p_list, _o_list, _adr2pypy, _pypy2ob + global _d_list, _dealloc_trigger_callback + _p_list = [] + _o_list = [] + _adr2pypy = [None] + _pypy2ob = {} + _d_list = [] + _dealloc_trigger_callback = dealloc_trigger_callback + +def create_link_pypy(p, ob): + "NOT_RPYTHON: a link where the PyPy object contains some or all the data" + #print 'create_link_pypy\n\t%s\n\t%s' % (p, ob) + assert p not in _pypy2ob + #assert not ob.c_ob_pypy_link + ob.c_ob_pypy_link = _build_pypy_link(p) + _pypy2ob[p] = ob + _p_list.append(ob) + +def create_link_pyobj(p, ob): + """NOT_RPYTHON: a link where the PyObject contains all the data. + from_obj() will not work on this 'p'.""" + #print 'create_link_pyobj\n\t%s\n\t%s' % (p, ob) + assert p not in _pypy2ob + #assert not ob.c_ob_pypy_link + ob.c_ob_pypy_link = _build_pypy_link(p) + _o_list.append(ob) + +def from_obj(OB_PTR_TYPE, p): + "NOT_RPYTHON" + ob = _pypy2ob.get(p) + if ob is None: + return lltype.nullptr(OB_PTR_TYPE.TO) + assert lltype.typeOf(ob) == OB_PTR_TYPE + return ob + +def to_obj(Class, ob): + "NOT_RPYTHON" + link = ob.c_ob_pypy_link + if link == 0: + return None + p = _adr2pypy[link] + assert isinstance(p, Class) + return p + +def next_dead(OB_PTR_TYPE): + if len(_d_list) == 0: + return lltype.nullptr(OB_PTR_TYPE.TO) + ob = _d_list.pop() + assert lltype.typeOf(ob) == OB_PTR_TYPE + return ob + +def _collect(track_allocation=True): + """NOT_RPYTHON: for tests only. Emulates a GC collection. + Will invoke dealloc_trigger_callback() once if there are objects + whose _Py_Dealloc() should be called. + """ + def detach(ob, wr_list): + assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY + assert ob.c_ob_pypy_link + p = _adr2pypy[ob.c_ob_pypy_link] + assert p is not None + _adr2pypy[ob.c_ob_pypy_link] = None + wr_list.append((ob, weakref.ref(p))) + return p + + global _p_list, _o_list + wr_p_list = [] + new_p_list = [] + for ob in reversed(_p_list): + if ob.c_ob_refcnt not in (REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT): + new_p_list.append(ob) + else: + p = detach(ob, wr_p_list) + del _pypy2ob[p] + del p + ob = None + _p_list = Ellipsis + + wr_o_list = [] + for ob in reversed(_o_list): + detach(ob, wr_o_list) + _o_list = Ellipsis + + rgc.collect() # forces the cycles to be resolved and the weakrefs to die + rgc.collect() + rgc.collect() + + def attach(ob, wr, final_list): + assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY + p = wr() + if p is not None: + assert ob.c_ob_pypy_link + _adr2pypy[ob.c_ob_pypy_link] = p + final_list.append(ob) + return p + else: + ob.c_ob_pypy_link = 0 + if ob.c_ob_refcnt >= REFCNT_FROM_PYPY_LIGHT: + ob.c_ob_refcnt -= REFCNT_FROM_PYPY_LIGHT + ob.c_ob_pypy_link = 0 + if ob.c_ob_refcnt == 0: + lltype.free(ob, flavor='raw', + track_allocation=track_allocation) + else: + assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY + assert ob.c_ob_refcnt < int(REFCNT_FROM_PYPY_LIGHT * 0.99) + ob.c_ob_refcnt -= REFCNT_FROM_PYPY + ob.c_ob_pypy_link = 0 + if ob.c_ob_refcnt == 0: + _d_list.append(ob) + return None + + _p_list = new_p_list + for ob, wr in wr_p_list: + p = attach(ob, wr, _p_list) + if p is not None: + _pypy2ob[p] = ob + _o_list = [] + for ob, wr in wr_o_list: + attach(ob, wr, _o_list) + + if _d_list: + res = _dealloc_trigger_callback() + if res == "RETRY": + _collect(track_allocation=track_allocation) + +_keepalive_forever = set() +def _dont_free_any_more(): + "Make sure that any object still referenced won't be freed any more." + for ob in _p_list + _o_list: + _keepalive_forever.add(to_obj(object, ob)) + del _d_list[:] + +# ____________________________________________________________ + + +def _unspec_p(hop, v_p): + assert isinstance(v_p.concretetype, lltype.Ptr) + assert v_p.concretetype.TO._gckind == 'gc' + return hop.genop('cast_opaque_ptr', [v_p], resulttype=llmemory.GCREF) + +def _unspec_ob(hop, v_ob): + assert isinstance(v_ob.concretetype, lltype.Ptr) + assert v_ob.concretetype.TO._gckind == 'raw' + return hop.genop('cast_ptr_to_adr', [v_ob], resulttype=llmemory.Address) + +def _spec_p(hop, v_p): + assert v_p.concretetype == llmemory.GCREF + return hop.genop('cast_opaque_ptr', [v_p], + resulttype=hop.r_result.lowleveltype) + +def _spec_ob(hop, v_ob): + assert v_ob.concretetype == llmemory.Address + return hop.genop('cast_adr_to_ptr', [v_ob], + resulttype=hop.r_result.lowleveltype) + + +class Entry(ExtRegistryEntry): + _about_ = init + + def compute_result_annotation(self, s_dealloc_callback): + from rpython.rtyper.llannotation import SomePtr + assert isinstance(s_dealloc_callback, SomePtr) # ll-ptr-to-function + + def specialize_call(self, hop): + hop.exception_cannot_occur() + [v_dealloc_callback] = hop.inputargs(hop.args_r[0]) + hop.genop('gc_rawrefcount_init', [v_dealloc_callback]) + + +class Entry(ExtRegistryEntry): + _about_ = (create_link_pypy, create_link_pyobj) + + def compute_result_annotation(self, s_p, s_ob): + pass + + def specialize_call(self, hop): + if self.instance is create_link_pypy: + name = 'gc_rawrefcount_create_link_pypy' + elif self.instance is create_link_pyobj: + name = 'gc_rawrefcount_create_link_pyobj' + v_p, v_ob = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() + hop.genop(name, [_unspec_p(hop, v_p), _unspec_ob(hop, v_ob)]) + + +class Entry(ExtRegistryEntry): + _about_ = from_obj + + def compute_result_annotation(self, s_OB_PTR_TYPE, s_p): + from rpython.annotator import model as annmodel + from rpython.rtyper.llannotation import lltype_to_annotation + assert (isinstance(s_p, annmodel.SomeInstance) or + annmodel.s_None.contains(s_p)) + assert s_OB_PTR_TYPE.is_constant() + return lltype_to_annotation(s_OB_PTR_TYPE.const) + + def specialize_call(self, hop): + hop.exception_cannot_occur() + v_p = hop.inputarg(hop.args_r[1], arg=1) + v_ob = hop.genop('gc_rawrefcount_from_obj', [_unspec_p(hop, v_p)], + resulttype = llmemory.Address) + return _spec_ob(hop, v_ob) + +class Entry(ExtRegistryEntry): + _about_ = to_obj + + def compute_result_annotation(self, s_Class, s_ob): + from rpython.annotator import model as annmodel + from rpython.rtyper.llannotation import SomePtr + assert isinstance(s_ob, SomePtr) + assert s_Class.is_constant() + classdef = self.bookkeeper.getuniqueclassdef(s_Class.const) + return annmodel.SomeInstance(classdef, can_be_None=True) + + def specialize_call(self, hop): + hop.exception_cannot_occur() + v_ob = hop.inputarg(hop.args_r[1], arg=1) + v_p = hop.genop('gc_rawrefcount_to_obj', [_unspec_ob(hop, v_ob)], + resulttype = llmemory.GCREF) + return _spec_p(hop, v_p) + +class Entry(ExtRegistryEntry): + _about_ = next_dead + + def compute_result_annotation(self, s_OB_PTR_TYPE): + from rpython.annotator import model as annmodel + from rpython.rtyper.llannotation import lltype_to_annotation + assert s_OB_PTR_TYPE.is_constant() + return lltype_to_annotation(s_OB_PTR_TYPE.const) + + def specialize_call(self, hop): + hop.exception_cannot_occur() + v_ob = hop.genop('gc_rawrefcount_next_dead', [], + resulttype = llmemory.Address) + return _spec_ob(hop, v_ob) diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py index e87849d393..99be096eef 100644 --- a/rpython/rlib/rgc.py +++ b/rpython/rlib/rgc.py @@ -487,6 +487,7 @@ NULL_GCREF = lltype.nullptr(llmemory.GCREF.TO) class _GcRef(object): # implementation-specific: there should not be any after translation __slots__ = ['_x', '_handle'] + _TYPE = llmemory.GCREF def __init__(self, x): self._x = x def __hash__(self): diff --git a/rpython/rlib/rposix.py b/rpython/rlib/rposix.py index 7ef8da67e9..b2d29d345f 100644 --- a/rpython/rlib/rposix.py +++ b/rpython/rlib/rposix.py @@ -1,27 +1,22 @@ import os import sys import errno +from rpython.annotator.model import s_Str0 from rpython.rtyper.lltypesystem.rffi import CConstant, CExternVariable, INT from rpython.rtyper.lltypesystem import lltype, ll2ctypes, rffi -from rpython.rtyper.module.support import StringTraits, UnicodeTraits from rpython.rtyper.tool import rffi_platform -from rpython.tool.sourcetools import func_renamer -from rpython.translator.tool.cbuild import ExternalCompilationInfo -from rpython.rlib.rarithmetic import intmask, widen +from rpython.rlib import debug, jit, rstring, rthread, types +from rpython.rlib._os_support import ( + _CYGWIN, _MACRO_ON_POSIX, UNDERSCORE_ON_WIN32, _WIN32, + _prefer_unicode, _preferred_traits) from rpython.rlib.objectmodel import ( specialize, enforceargs, register_replacement_for, NOT_CONSTANT) +from rpython.rlib.rarithmetic import intmask, widen from rpython.rlib.signature import signature -from rpython.rlib import types -from rpython.annotator.model import s_Str0 -from rpython.rlib import jit +from rpython.tool.sourcetools import func_renamer from rpython.translator.platform import platform -from rpython.rlib import rstring -from rpython.rlib import debug, rthread +from rpython.translator.tool.cbuild import ExternalCompilationInfo -_WIN32 = sys.platform.startswith('win') -_CYGWIN = sys.platform == 'cygwin' -UNDERSCORE_ON_WIN32 = '_' if _WIN32 else '' -_MACRO_ON_POSIX = True if not _WIN32 else None if _WIN32: from rpython.rlib import rwin32 @@ -119,7 +114,6 @@ def get_saved_errno(): with the flag llexternal(..., save_err=rffi.RFFI_SAVE_ERRNO). Functions without that flag don't change the saved errno. """ - from rpython.rlib import rthread return intmask(rthread.tlfield_rpy_errno.getraw()) def set_saved_errno(errno): @@ -130,7 +124,6 @@ def set_saved_errno(errno): zero; for that case, use llexternal(..., save_err=RFFI_ZERO_ERRNO_BEFORE) and then you don't need set_saved_errno(0). """ - from rpython.rlib import rthread rthread.tlfield_rpy_errno.setraw(rffi.cast(INT, errno)) def get_saved_alterrno(): @@ -139,7 +132,6 @@ def get_saved_alterrno(): with the flag llexternal(..., save_err=rffi.RFFI_SAVE_ERRNO | rffl.RFFI_ALT_ERRNO). Functions without that flag don't change the saved errno. """ - from rpython.rlib import rthread return intmask(rthread.tlfield_alt_errno.getraw()) def set_saved_alterrno(errno): @@ -150,7 +142,6 @@ def set_saved_alterrno(errno): zero; for that case, use llexternal(..., save_err=RFFI_ZERO_ERRNO_BEFORE) and then you don't need set_saved_errno(0). """ - from rpython.rlib import rthread rthread.tlfield_alt_errno.setraw(rffi.cast(INT, errno)) @@ -158,7 +149,6 @@ def set_saved_alterrno(errno): @specialize.call_location() def _errno_before(save_err): if save_err & rffi.RFFI_READSAVED_ERRNO: - from rpython.rlib import rthread if save_err & rffi.RFFI_ALT_ERRNO: _set_errno(rthread.tlfield_alt_errno.getraw()) else: @@ -166,7 +156,6 @@ def _errno_before(save_err): elif save_err & rffi.RFFI_ZERO_ERRNO_BEFORE: _set_errno(rffi.cast(rffi.INT, 0)) if _WIN32 and (save_err & rffi.RFFI_READSAVED_LASTERROR): - from rpython.rlib import rthread, rwin32 if save_err & rffi.RFFI_ALT_ERRNO: err = rthread.tlfield_alt_lasterror.getraw() else: @@ -180,7 +169,6 @@ def _errno_before(save_err): def _errno_after(save_err): if _WIN32: if save_err & rffi.RFFI_SAVE_LASTERROR: - from rpython.rlib import rthread, rwin32 err = rwin32._GetLastError() # careful, setraw() overwrites GetLastError. # We must read it first, before the errno handling. @@ -189,14 +177,13 @@ def _errno_after(save_err): else: rthread.tlfield_rpy_lasterror.setraw(err) elif save_err & rffi.RFFI_SAVE_WSALASTERROR: - from rpython.rlib import rthread, _rsocket_rffi + from rpython.rlib import _rsocket_rffi err = _rsocket_rffi._WSAGetLastError() if save_err & rffi.RFFI_ALT_ERRNO: rthread.tlfield_alt_lasterror.setraw(err) else: rthread.tlfield_rpy_lasterror.setraw(err) if save_err & rffi.RFFI_SAVE_ERRNO: - from rpython.rlib import rthread if save_err & rffi.RFFI_ALT_ERRNO: rthread.tlfield_alt_errno.setraw(_get_errno()) else: @@ -342,37 +329,6 @@ def _as_unicode0(path): rstring.check_str0(res) return res -# Returns True when the unicode function should be called: -# - on Windows -# - if the path is Unicode. -unicode_traits = UnicodeTraits() -string_traits = StringTraits() -if _WIN32: - @specialize.argtype(0) - def _prefer_unicode(path): - assert path is not None - if isinstance(path, str): - return False - elif isinstance(path, unicode): - return True - else: - return path.is_unicode - - @specialize.argtype(0) - def _preferred_traits(path): - if _prefer_unicode(path): - return unicode_traits - else: - return string_traits -else: - @specialize.argtype(0) - def _prefer_unicode(path): - return False - - @specialize.argtype(0) - def _preferred_traits(path): - return string_traits - @specialize.argtype(0, 1) def putenv(name, value): os.environ[_as_bytes(name)] = _as_bytes(value) diff --git a/rpython/rlib/rposix_environ.py b/rpython/rlib/rposix_environ.py index 9906ceb0d3..b419b6eaeb 100644 --- a/rpython/rlib/rposix_environ.py +++ b/rpython/rlib/rposix_environ.py @@ -1,11 +1,12 @@ import os import sys from rpython.annotator import model as annmodel +from rpython.rlib._os_support import _WIN32, StringTraits, UnicodeTraits from rpython.rlib.objectmodel import enforceargs +# importing rposix here creates a cycle on Windows from rpython.rtyper.controllerentry import Controller from rpython.rtyper.extfunc import register_external from rpython.rtyper.lltypesystem import rffi, lltype -from rpython.rtyper.module.support import _WIN32, StringTraits, UnicodeTraits from rpython.translator.tool.cbuild import ExternalCompilationInfo str0 = annmodel.s_Str0 diff --git a/rpython/rlib/rposix_stat.py b/rpython/rlib/rposix_stat.py index b6c20b591c..1b66547e50 100644 --- a/rpython/rlib/rposix_stat.py +++ b/rpython/rlib/rposix_stat.py @@ -16,13 +16,13 @@ from rpython.rtyper.rmodel import Repr from rpython.rtyper.rint import IntegerRepr from rpython.rtyper.error import TyperError +from rpython.rlib._os_support import _preferred_traits, string_traits from rpython.rlib.objectmodel import specialize from rpython.rtyper.lltypesystem import lltype, rffi from rpython.translator.tool.cbuild import ExternalCompilationInfo from rpython.rlib.rarithmetic import intmask from rpython.rlib.rposix import ( - replace_os_function, handle_posix_error, _as_bytes0, - _preferred_traits, string_traits) + replace_os_function, handle_posix_error, _as_bytes0) _WIN32 = sys.platform.startswith('win') _LINUX = sys.platform.startswith('linux') diff --git a/rpython/rlib/rsocket.py b/rpython/rlib/rsocket.py index 9f2247e05b..ff6b9b4257 100644 --- a/rpython/rlib/rsocket.py +++ b/rpython/rlib/rsocket.py @@ -516,6 +516,10 @@ class RSocket(object): """RPython-level socket object. """ fd = _c.INVALID_SOCKET + family = 0 + type = 0 + proto = 0 + timeout = -1.0 def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fd=_c.INVALID_SOCKET): @@ -531,6 +535,11 @@ class RSocket(object): self.proto = proto self.timeout = defaults.timeout + @staticmethod + def empty_rsocket(): + rsocket = instantiate(RSocket) + return rsocket + @rgc.must_be_light_finalizer def __del__(self): fd = self.fd diff --git a/rpython/rlib/rvmprof/cintf.py b/rpython/rlib/rvmprof/cintf.py index 154c06bcbd..80276a0975 100644 --- a/rpython/rlib/rvmprof/cintf.py +++ b/rpython/rlib/rvmprof/cintf.py @@ -7,8 +7,6 @@ from rpython.translator.tool.cbuild import ExternalCompilationInfo from rpython.rtyper.tool import rffi_platform as platform from rpython.rlib import rthread -from rpython.jit.backend import detect_cpu - class VMProfPlatformUnsupported(Exception): pass diff --git a/rpython/rlib/test/test_jit.py b/rpython/rlib/test/test_jit.py index 95b4c8987c..86abed36ba 100644 --- a/rpython/rlib/test/test_jit.py +++ b/rpython/rlib/test/test_jit.py @@ -4,7 +4,8 @@ from rpython.conftest import option from rpython.annotator.model import UnionError from rpython.rlib.jit import (hint, we_are_jitted, JitDriver, elidable_promote, JitHintError, oopspec, isconstant, conditional_call, - elidable, unroll_safe, dont_look_inside) + elidable, unroll_safe, dont_look_inside, + enter_portal_frame, leave_portal_frame) from rpython.rlib.rarithmetic import r_uint from rpython.rtyper.test.tool import BaseRtypingTest from rpython.rtyper.lltypesystem import lltype @@ -300,3 +301,11 @@ class TestJIT(BaseRtypingTest): mix = MixLevelHelperAnnotator(t.rtyper) mix.getgraph(later, [annmodel.s_Bool], annmodel.s_None) mix.finish() + + def test_enter_leave_portal_frame(self): + from rpython.translator.interactive import Translation + def g(): + enter_portal_frame(1) + leave_portal_frame() + t = Translation(g, []) + t.compile_c() # does not crash diff --git a/rpython/rlib/test/test_posix.py b/rpython/rlib/test/test_posix.py new file mode 100644 index 0000000000..6f4b2bce62 --- /dev/null +++ b/rpython/rlib/test/test_posix.py @@ -0,0 +1,304 @@ +import py.test +from rpython.rtyper.test.tool import BaseRtypingTest +from rpython.rtyper.annlowlevel import hlstr +from rpython.tool.udir import udir +from rpython.rlib.rarithmetic import is_valid_int + +import os +exec 'import %s as posix' % os.name + +def setup_module(module): + testf = udir.join('test.txt') + module.path = testf.strpath + +class TestPosix(BaseRtypingTest): + + def setup_method(self, meth): + # prepare/restore the file before each test + testfile = open(path, 'wb') + testfile.write('This is a test') + testfile.close() + + def test_open(self): + def f(): + ff = posix.open(path, posix.O_RDONLY, 0777) + return ff + func = self.interpret(f, []) + assert is_valid_int(func) + + def test_fstat(self): + def fo(fi): + g = posix.fstat(fi) + return g + fi = os.open(path,os.O_RDONLY,0777) + func = self.interpret(fo,[fi]) + stat = os.fstat(fi) + for i in range(len(stat)): + assert long(getattr(func, 'item%d' % i)) == stat[i] + + + def test_stat(self): + def fo(): + g = posix.stat(path) + return g + func = self.interpret(fo,[]) + stat = os.stat(path) + for i in range(len(stat)): + assert long(getattr(func, 'item%d' % i)) == stat[i] + + def test_stat_exception(self): + def fo(): + try: + posix.stat('I/do/not/exist') + except OSError: + return True + else: + return False + res = self.interpret(fo,[]) + assert res + + def test_times(self): + py.test.skip("llinterp does not like tuple returns") + from rpython.rtyper.test.test_llinterp import interpret + times = interpret(lambda: posix.times(), ()) + assert isinstance(times, tuple) + assert len(times) == 5 + for value in times: + assert is_valid_int(value) + + + def test_lseek(self): + def f(fi, pos): + posix.lseek(fi, pos, 0) + fi = os.open(path, os.O_RDONLY, 0777) + func = self.interpret(f, [fi, 5]) + res = os.read(fi, 2) + assert res =='is' + + def test_isatty(self): + def f(fi): + posix.isatty(fi) + fi = os.open(path, os.O_RDONLY, 0777) + func = self.interpret(f, [fi]) + assert not func + os.close(fi) + func = self.interpret(f, [fi]) + assert not func + + def test_getcwd(self): + def f(): + return posix.getcwd() + res = self.interpret(f,[]) + cwd = os.getcwd() + #print res.chars,cwd + assert self.ll_to_string(res) == cwd + + def test_write(self): + def f(fi): + if fi > 0: + text = 'This is a test' + else: + text = '333' + return posix.write(fi,text) + fi = os.open(path,os.O_WRONLY,0777) + text = 'This is a test' + func = self.interpret(f,[fi]) + os.close(fi) + fi = os.open(path,os.O_RDONLY,0777) + res = os.read(fi,20) + assert res == text + + def test_read(self): + def f(fi,len): + return posix.read(fi,len) + fi = os.open(path,os.O_WRONLY,0777) + text = 'This is a test' + os.write(fi,text) + os.close(fi) + fi = os.open(path,os.O_RDONLY,0777) + res = self.interpret(f,[fi,20]) + assert self.ll_to_string(res) == text + + @py.test.mark.skipif("not hasattr(os, 'chown')") + def test_chown(self): + f = open(path, "w") + f.write("xyz") + f.close() + def f(): + try: + posix.chown(path, os.getuid(), os.getgid()) + return 1 + except OSError: + return 2 + + assert self.interpret(f, []) == 1 + os.unlink(path) + assert self.interpret(f, []) == 2 + + def test_close(self): + def f(fi): + return posix.close(fi) + fi = os.open(path,os.O_WRONLY,0777) + text = 'This is a test' + os.write(fi,text) + res = self.interpret(f,[fi]) + py.test.raises( OSError, os.fstat, fi) + + @py.test.mark.skipif("not hasattr(os, 'ftruncate')") + def test_ftruncate(self): + def f(fi,len): + os.ftruncate(fi,len) + fi = os.open(path,os.O_RDWR,0777) + func = self.interpret(f,[fi,6]) + assert os.fstat(fi).st_size == 6 + + @py.test.mark.skipif("not hasattr(os, 'getuid')") + def test_getuid(self): + def f(): + return os.getuid() + assert self.interpret(f, []) == f() + + @py.test.mark.skipif("not hasattr(os, 'getgid')") + def test_getgid(self): + def f(): + return os.getgid() + assert self.interpret(f, []) == f() + + @py.test.mark.skipif("not hasattr(os, 'setuid')") + def test_os_setuid(self): + def f(): + os.setuid(os.getuid()) + return os.getuid() + assert self.interpret(f, []) == f() + + @py.test.mark.skipif("not hasattr(os, 'sysconf')") + def test_os_sysconf(self): + def f(i): + return os.sysconf(i) + assert self.interpret(f, [13]) == f(13) + + @py.test.mark.skipif("not hasattr(os, 'confstr')") + def test_os_confstr(self): + def f(i): + try: + return os.confstr(i) + except OSError: + return "oooops!!" + some_value = os.confstr_names.values()[-1] + res = self.interpret(f, [some_value]) + assert hlstr(res) == f(some_value) + res = self.interpret(f, [94781413]) + assert hlstr(res) == "oooops!!" + + @py.test.mark.skipif("not hasattr(os, 'pathconf')") + def test_os_pathconf(self): + def f(i): + return os.pathconf("/tmp", i) + i = os.pathconf_names["PC_NAME_MAX"] + some_value = self.interpret(f, [i]) + assert some_value >= 31 + + @py.test.mark.skipif("not hasattr(os, 'chroot')") + def test_os_chroot(self): + def f(): + try: + os.chroot('!@$#!#%$#^#@!#!$$#^') + except OSError: + return 1 + return 0 + + assert self.interpret(f, []) == 1 + + def test_os_wstar(self): + from rpython.rlib import rposix + for name in rposix.WAIT_MACROS: + if not hasattr(os, name): + continue + def fun(s): + return getattr(os, name)(s) + + for value in [0, 1, 127, 128, 255]: + res = self.interpret(fun, [value]) + assert res == fun(value) + + @py.test.mark.skipif("not hasattr(os, 'getgroups')") + def test_getgroups(self): + def f(): + return os.getgroups() + ll_a = self.interpret(f, []) + assert self.ll_to_list(ll_a) == f() + + @py.test.mark.skipif("not hasattr(os, 'setgroups')") + def test_setgroups(self): + def f(): + try: + os.setgroups(os.getgroups()) + except OSError: + pass + self.interpret(f, []) + + @py.test.mark.skipif("not hasattr(os, 'initgroups')") + def test_initgroups(self): + def f(): + try: + os.initgroups('sUJJeumz', 4321) + except OSError: + return 1 + return 0 + res = self.interpret(f, []) + assert res == 1 + + @py.test.mark.skipif("not hasattr(os, 'tcgetpgrp')") + def test_tcgetpgrp(self): + def f(fd): + try: + return os.tcgetpgrp(fd) + except OSError: + return 42 + res = self.interpret(f, [9999]) + assert res == 42 + + @py.test.mark.skipif("not hasattr(os, 'tcsetpgrp')") + def test_tcsetpgrp(self): + def f(fd, pgrp): + try: + os.tcsetpgrp(fd, pgrp) + except OSError: + return 1 + return 0 + res = self.interpret(f, [9999, 1]) + assert res == 1 + + @py.test.mark.skipif("not hasattr(os, 'getresuid')") + def test_getresuid(self): + def f(): + a, b, c = os.getresuid() + return a + b * 37 + c * 1291 + res = self.interpret(f, []) + a, b, c = os.getresuid() + assert res == a + b * 37 + c * 1291 + + @py.test.mark.skipif("not hasattr(os, 'getresgid')") + def test_getresgid(self): + def f(): + a, b, c = os.getresgid() + return a + b * 37 + c * 1291 + res = self.interpret(f, []) + a, b, c = os.getresgid() + assert res == a + b * 37 + c * 1291 + + @py.test.mark.skipif("not hasattr(os, 'setresuid')") + def test_setresuid(self): + def f(): + a, b, c = os.getresuid() + a = (a + 1) - 1 + os.setresuid(a, b, c) + self.interpret(f, []) + + @py.test.mark.skipif("not hasattr(os, 'setresgid')") + def test_setresgid(self): + def f(): + a, b, c = os.getresgid() + a = (a + 1) - 1 + os.setresgid(a, b, c) + self.interpret(f, []) diff --git a/rpython/rlib/test/test_rawrefcount.py b/rpython/rlib/test/test_rawrefcount.py new file mode 100644 index 0000000000..6f095b9ae7 --- /dev/null +++ b/rpython/rlib/test/test_rawrefcount.py @@ -0,0 +1,268 @@ +import weakref +from rpython.rlib import rawrefcount, objectmodel, rgc +from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT +from rpython.rtyper.lltypesystem import lltype, llmemory +from rpython.rtyper.annlowlevel import llhelper +from rpython.translator.c.test.test_standalone import StandaloneTests +from rpython.config.translationoption import get_combined_translation_config + + +class W_Root(object): + def __init__(self, intval=0): + self.intval = intval + def __nonzero__(self): + raise Exception("you cannot do that, you must use space.is_true()") + +PyObjectS = lltype.Struct('PyObjectS', + ('c_ob_refcnt', lltype.Signed), + ('c_ob_pypy_link', lltype.Signed)) +PyObject = lltype.Ptr(PyObjectS) + + +class TestRawRefCount: + + def setup_method(self, meth): + rawrefcount.init() + + def test_create_link_pypy(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS) + assert rawrefcount.to_obj(W_Root, ob) == None + rawrefcount.create_link_pypy(p, ob) + assert ob.c_ob_refcnt == 0 + ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT + assert rawrefcount.from_obj(PyObject, p) == ob + assert rawrefcount.to_obj(W_Root, ob) == p + lltype.free(ob, flavor='raw') + + def test_create_link_pyobj(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS) + assert rawrefcount.to_obj(W_Root, ob) == None + rawrefcount.create_link_pyobj(p, ob) + assert ob.c_ob_refcnt == 0 + ob.c_ob_refcnt += REFCNT_FROM_PYPY + assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS) + assert rawrefcount.to_obj(W_Root, ob) == p + lltype.free(ob, flavor='raw') + + def test_collect_p_dies(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + rawrefcount.create_link_pypy(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT + assert rawrefcount._p_list == [ob] + wr_ob = weakref.ref(ob) + wr_p = weakref.ref(p) + del ob, p + rawrefcount._collect() + assert rawrefcount._p_list == [] + assert wr_ob() is None + assert wr_p() is None + + def test_collect_p_keepalive_pyobject(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + rawrefcount.create_link_pypy(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT + assert rawrefcount._p_list == [ob] + wr_ob = weakref.ref(ob) + wr_p = weakref.ref(p) + ob.c_ob_refcnt += 1 # <= + del ob, p + rawrefcount._collect() + ob = wr_ob() + p = wr_p() + assert ob is not None and p is not None + assert rawrefcount._p_list == [ob] + assert rawrefcount.to_obj(W_Root, ob) == p + assert rawrefcount.from_obj(PyObject, p) == ob + lltype.free(ob, flavor='raw') + + def test_collect_p_keepalive_w_root(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + rawrefcount.create_link_pypy(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT + assert rawrefcount._p_list == [ob] + wr_ob = weakref.ref(ob) + del ob # p remains + rawrefcount._collect() + ob = wr_ob() + assert ob is not None + assert rawrefcount._p_list == [ob] + assert rawrefcount.to_obj(W_Root, ob) == p + assert rawrefcount.from_obj(PyObject, p) == ob + lltype.free(ob, flavor='raw') + + def test_collect_o_dies(self): + trigger = []; rawrefcount.init(lambda: trigger.append(1)) + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + rawrefcount.create_link_pyobj(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY + assert rawrefcount._o_list == [ob] + wr_ob = weakref.ref(ob) + wr_p = weakref.ref(p) + del ob, p + rawrefcount._collect() + ob = wr_ob() + assert ob is not None + assert trigger == [1] + assert rawrefcount.next_dead(PyObject) == ob + assert rawrefcount.next_dead(PyObject) == lltype.nullptr(PyObjectS) + assert rawrefcount.next_dead(PyObject) == lltype.nullptr(PyObjectS) + assert rawrefcount._o_list == [] + assert wr_p() is None + assert ob.c_ob_refcnt == 0 + assert ob.c_ob_pypy_link == 0 + lltype.free(ob, flavor='raw') + + def test_collect_o_keepalive_pyobject(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + p.pyobj = ob + rawrefcount.create_link_pyobj(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY + assert rawrefcount._o_list == [ob] + wr_ob = weakref.ref(ob) + wr_p = weakref.ref(p) + ob.c_ob_refcnt += 1 # <= + del p + rawrefcount._collect() + p = wr_p() + assert p is None # was unlinked + assert ob.c_ob_refcnt == 1 # != REFCNT_FROM_PYPY_OBJECT + 1 + assert rawrefcount._o_list == [] + assert rawrefcount.to_obj(W_Root, ob) == None + lltype.free(ob, flavor='raw') + + def test_collect_o_keepalive_w_root(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + p.pyobj = ob + rawrefcount.create_link_pyobj(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY + assert rawrefcount._o_list == [ob] + wr_ob = weakref.ref(ob) + del ob # p remains + rawrefcount._collect() + ob = wr_ob() + assert ob is not None + assert rawrefcount._o_list == [ob] + assert rawrefcount.to_obj(W_Root, ob) == p + assert p.pyobj == ob + lltype.free(ob, flavor='raw') + + def test_collect_s_dies(self): + trigger = []; rawrefcount.init(lambda: trigger.append(1)) + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + rawrefcount.create_link_pypy(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY + assert rawrefcount._p_list == [ob] + wr_ob = weakref.ref(ob) + wr_p = weakref.ref(p) + del ob, p + rawrefcount._collect() + ob = wr_ob() + assert ob is not None + assert trigger == [1] + assert rawrefcount._d_list == [ob] + assert rawrefcount._p_list == [] + assert wr_p() is None + assert ob.c_ob_refcnt == 0 + assert ob.c_ob_pypy_link == 0 + lltype.free(ob, flavor='raw') + + def test_collect_s_keepalive_pyobject(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + p.pyobj = ob + rawrefcount.create_link_pypy(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY + assert rawrefcount._p_list == [ob] + wr_ob = weakref.ref(ob) + wr_p = weakref.ref(p) + ob.c_ob_refcnt += 1 # <= + del ob, p + rawrefcount._collect() + ob = wr_ob() + p = wr_p() + assert ob is not None and p is not None + assert rawrefcount._p_list == [ob] + assert rawrefcount.to_obj(W_Root, ob) == p + lltype.free(ob, flavor='raw') + + def test_collect_s_keepalive_w_root(self): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + p.pyobj = ob + rawrefcount.create_link_pypy(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY + assert rawrefcount._p_list == [ob] + wr_ob = weakref.ref(ob) + del ob # p remains + rawrefcount._collect() + ob = wr_ob() + assert ob is not None + assert rawrefcount._p_list == [ob] + assert rawrefcount.to_obj(W_Root, ob) == p + lltype.free(ob, flavor='raw') + + +class TestTranslated(StandaloneTests): + + def test_full_translation(self): + class State: + pass + state = State() + state.seen = [] + def dealloc_trigger(): + state.seen.append(1) + + def make_p(): + p = W_Root(42) + ob = lltype.malloc(PyObjectS, flavor='raw', zero=True) + rawrefcount.create_link_pypy(p, ob) + ob.c_ob_refcnt += REFCNT_FROM_PYPY + assert rawrefcount.from_obj(PyObject, p) == ob + assert rawrefcount.to_obj(W_Root, ob) == p + return ob, p + + FTYPE = rawrefcount.RAWREFCOUNT_DEALLOC_TRIGGER + + def entry_point(argv): + ll_dealloc_trigger_callback = llhelper(FTYPE, dealloc_trigger) + rawrefcount.init(ll_dealloc_trigger_callback) + ob, p = make_p() + if state.seen != []: + print "OB COLLECTED REALLY TOO SOON" + return 1 + rgc.collect() + if state.seen != []: + print "OB COLLECTED TOO SOON" + return 1 + objectmodel.keepalive_until_here(p) + p = None + rgc.collect() + if state.seen != [1]: + print "OB NOT COLLECTED" + return 1 + if rawrefcount.next_dead(PyObject) != ob: + print "NEXT_DEAD != OB" + return 1 + if rawrefcount.next_dead(PyObject) != lltype.nullptr(PyObjectS): + print "NEXT_DEAD second time != NULL" + return 1 + print "OK!" + lltype.free(ob, flavor='raw') + return 0 + + self.config = get_combined_translation_config(translating=True) + self.config.translation.gc = "incminimark" + t, cbuilder = self.compile(entry_point) + data = cbuilder.cmdexec('hi there') + assert data.startswith('OK!\n') diff --git a/rpython/rlib/test/test_rerased.py b/rpython/rlib/test/test_rerased.py index 9cb4924cfc..989a62bede 100644 --- a/rpython/rlib/test/test_rerased.py +++ b/rpython/rlib/test/test_rerased.py @@ -192,7 +192,7 @@ class TestRErased(BaseRtypingTest): def interpret(self, *args, **kwargs): kwargs["taggedpointers"] = True - return BaseRtypingTest.interpret(self, *args, **kwargs) + return BaseRtypingTest.interpret(*args, **kwargs) def test_rtype_1(self): def f(): return eraseX(X()) diff --git a/rpython/rtyper/extfunc.py b/rpython/rtyper/extfunc.py index 30d61ec4ee..0b926c9ce1 100644 --- a/rpython/rtyper/extfunc.py +++ b/rpython/rtyper/extfunc.py @@ -1,99 +1,105 @@ -from rpython.rtyper.extregistry import ExtRegistryEntry -from rpython.rtyper.lltypesystem.lltype import typeOf, FuncType, functionptr -from rpython.annotator.model import unionof +from rpython.annotator.model import unionof, SomeObject from rpython.annotator.signature import annotation, SignatureError +from rpython.rtyper.extregistry import ExtRegistryEntry, lookup +from rpython.rtyper.lltypesystem.lltype import ( + typeOf, FuncType, functionptr, _ptr, Void) +from rpython.rtyper.error import TyperError +from rpython.rtyper.rmodel import Repr -import py +class SomeExternalFunction(SomeObject): + def __init__(self, name, args_s, s_result): + self.name = name + self.args_s = args_s + self.s_result = s_result -class ExtFuncEntry(ExtRegistryEntry): - safe_not_sandboxed = False + def check_args(self, callspec): + params_s = self.args_s + args_s, kwargs = callspec.unpack() + if kwargs: + raise SignatureError( + "External functions cannot be called with keyword arguments") + if len(args_s) != len(params_s): + raise SignatureError("Argument number mismatch") + for i, s_param in enumerate(params_s): + arg = unionof(args_s[i], s_param) + if not s_param.contains(arg): + raise SignatureError( + "In call to external function %r:\n" + "arg %d must be %s,\n" + " got %s" % ( + self.name, i + 1, s_param, args_s[i])) + + def call(self, callspec): + self.check_args(callspec) + return self.s_result + + def rtyper_makerepr(self, rtyper): + if not self.is_constant(): + raise TyperError("Non-constant external function!") + entry = lookup(self.const) + impl = getattr(entry, 'lltypeimpl', None) + fakeimpl = getattr(entry, 'lltypefakeimpl', None) + return ExternalFunctionRepr(self, impl, fakeimpl) + + def rtyper_makekey(self): + return self.__class__, self - # common case: args is a list of annotation or types - def normalize_args(self, *args_s): - args = self.signature_args - signature_args = [annotation(arg, None) for arg in args] - assert len(args_s) == len(signature_args),\ - "Argument number mismatch" - - for i, expected in enumerate(signature_args): - arg = unionof(args_s[i], expected) - if not expected.contains(arg): - name = getattr(self, 'name', None) - if not name: - try: - name = self.instance.__name__ - except AttributeError: - name = '?' - raise SignatureError("In call to external function %r:\n" - "arg %d must be %s,\n" - " got %s" % ( - name, i+1, expected, args_s[i])) - return signature_args - - def compute_result_annotation(self, *args_s): - self.normalize_args(*args_s) # check arguments - return self.signature_result - - def specialize_call(self, hop): +class ExternalFunctionRepr(Repr): + lowleveltype = Void + + def __init__(self, s_func, impl, fakeimpl): + self.s_func = s_func + self.impl = impl + self.fakeimpl = fakeimpl + + def rtype_simple_call(self, hop): rtyper = hop.rtyper - signature_args = self.normalize_args(*hop.args_s) - args_r = [rtyper.getrepr(s_arg) for s_arg in signature_args] + args_r = [rtyper.getrepr(s_arg) for s_arg in self.s_func.args_s] + r_result = rtyper.getrepr(self.s_func.s_result) + obj = self.get_funcptr(rtyper, args_r, r_result) + hop2 = hop.copy() + hop2.r_s_popfirstarg() + vlist = [hop2.inputconst(typeOf(obj), obj)] + hop2.inputargs(*args_r) + hop2.exception_is_here() + return hop2.genop('direct_call', vlist, r_result) + + def get_funcptr(self, rtyper, args_r, r_result): + from rpython.rtyper.rtyper import llinterp_backend args_ll = [r_arg.lowleveltype for r_arg in args_r] - s_result = hop.s_result - r_result = rtyper.getrepr(s_result) ll_result = r_result.lowleveltype - name = getattr(self, 'name', None) or self.instance.__name__ - impl = getattr(self, 'lltypeimpl', None) - fakeimpl = getattr(self, 'lltypefakeimpl', self.instance) - if impl: - if (rtyper.annotator.translator.config.translation.sandbox - and not self.safe_not_sandboxed): - from rpython.translator.sandbox.rsandbox import ( - make_sandbox_trampoline) - impl = make_sandbox_trampoline( - self.name, signature_args, s_result) - if hasattr(self, 'lltypefakeimpl'): - # If we have both an llimpl and an llfakeimpl, - # we need a wrapper that selects the proper one and calls it - from rpython.tool.sourcetools import func_with_new_name - # Using '*args' is delicate because this wrapper is also - # created for init-time functions like llarena.arena_malloc - # which are called before the GC is fully initialized - args = ', '.join(['arg%d' % i for i in range(len(args_ll))]) - d = {'original_impl': impl, - 's_result': s_result, - 'fakeimpl': fakeimpl, - '__name__': __name__, - } - exec py.code.compile(""" - from rpython.rlib.objectmodel import running_on_llinterp - from rpython.rlib.debug import llinterpcall - from rpython.rlib.jit import dont_look_inside - # note: we say 'dont_look_inside' mostly because the - # JIT does not support 'running_on_llinterp', but in - # theory it is probably right to stop jitting anyway. - @dont_look_inside - def ll_wrapper(%s): - if running_on_llinterp: - return llinterpcall(s_result, fakeimpl, %s) - else: - return original_impl(%s) - """ % (args, args, args)) in d - impl = func_with_new_name(d['ll_wrapper'], name + '_wrapper') - # store some attributes to the 'impl' function, where - # the eventual call to rtyper.getcallable() will find them - # and transfer them to the final lltype.functionptr(). - impl._llfnobjattrs_ = {'_name': self.name} - obj = rtyper.getannmixlevel().delayedfunction( - impl, signature_args, hop.s_result) + name = self.s_func.name + if self.fakeimpl and rtyper.backend is llinterp_backend: + FT = FuncType(args_ll, ll_result) + return functionptr( + FT, name, _external_name=name, _callable=self.fakeimpl) + elif self.impl: + if isinstance(self.impl, _ptr): + return self.impl + else: + # store some attributes to the 'impl' function, where + # the eventual call to rtyper.getcallable() will find them + # and transfer them to the final lltype.functionptr(). + self.impl._llfnobjattrs_ = {'_name': name} + return rtyper.getannmixlevel().delayedfunction( + self.impl, self.s_func.args_s, self.s_func.s_result) else: + fakeimpl = self.fakeimpl or self.s_func.const FT = FuncType(args_ll, ll_result) - obj = functionptr(FT, name, _external_name=self.name, - _callable=fakeimpl, - _safe_not_sandboxed=self.safe_not_sandboxed) - vlist = [hop.inputconst(typeOf(obj), obj)] + hop.inputargs(*args_r) - hop.exception_is_here() - return hop.genop('direct_call', vlist, r_result) + return functionptr( + FT, name, _external_name=name, _callable=fakeimpl) + + +class ExtFuncEntry(ExtRegistryEntry): + safe_not_sandboxed = False + + def compute_annotation(self): + s_result = SomeExternalFunction( + self.name, self.signature_args, self.signature_result) + if (self.bookkeeper.annotator.translator.config.translation.sandbox + and not self.safe_not_sandboxed): + s_result.needs_sandboxing = True + return s_result + def register_external(function, args, result=None, export_name=None, llimpl=None, llfakeimpl=None, sandboxsafe=False): @@ -109,32 +115,20 @@ def register_external(function, args, result=None, export_name=None, if export_name is None: export_name = function.__name__ + params_s = [annotation(arg) for arg in args] + s_result = annotation(result) class FunEntry(ExtFuncEntry): _about_ = function safe_not_sandboxed = sandboxsafe - - if args is None: - def normalize_args(self, *args_s): - return args_s # accept any argument unmodified - elif callable(args): - # custom annotation normalizer (see e.g. os.utime()) - normalize_args = staticmethod(args) - else: # use common case behavior - signature_args = args - - signature_result = annotation(result, None) + signature_args = params_s + signature_result = s_result name = export_name if llimpl: lltypeimpl = staticmethod(llimpl) if llfakeimpl: lltypefakeimpl = staticmethod(llfakeimpl) - if export_name: - FunEntry.__name__ = export_name - else: - FunEntry.__name__ = function.func_name - def is_external(func): if hasattr(func, 'value'): func = func.value diff --git a/rpython/rtyper/llinterp.py b/rpython/rtyper/llinterp.py index caeea15e6f..8f29a3208e 100644 --- a/rpython/rtyper/llinterp.py +++ b/rpython/rtyper/llinterp.py @@ -925,6 +925,21 @@ class LLFrame(object): def op_gc_gcflag_extra(self, subopnum, *args): return self.heap.gcflag_extra(subopnum, *args) + def op_gc_rawrefcount_init(self, *args): + raise NotImplementedError("gc_rawrefcount_init") + + def op_gc_rawrefcount_to_obj(self, *args): + raise NotImplementedError("gc_rawrefcount_to_obj") + + def op_gc_rawrefcount_from_obj(self, *args): + raise NotImplementedError("gc_rawrefcount_from_obj") + + def op_gc_rawrefcount_create_link_pyobj(self, *args): + raise NotImplementedError("gc_rawrefcount_create_link_pyobj") + + def op_gc_rawrefcount_create_link_pypy(self, *args): + raise NotImplementedError("gc_rawrefcount_create_link_pypy") + def op_do_malloc_fixedsize(self): raise NotImplementedError("do_malloc_fixedsize") def op_do_malloc_fixedsize_clear(self): diff --git a/rpython/rtyper/lltypesystem/ll2ctypes.py b/rpython/rtyper/lltypesystem/ll2ctypes.py index 43a59f82cf..664be37524 100644 --- a/rpython/rtyper/lltypesystem/ll2ctypes.py +++ b/rpython/rtyper/lltypesystem/ll2ctypes.py @@ -426,7 +426,12 @@ def convert_struct(container, cstruct=None, delayed_converters=None): else: n = None cstruct = cls._malloc(n) - add_storage(container, _struct_mixin, ctypes.pointer(cstruct)) + + if isinstance(container, lltype._fixedsizearray): + cls_mixin = _fixedsizedarray_mixin + else: + cls_mixin = _struct_mixin + add_storage(container, cls_mixin, ctypes.pointer(cstruct)) if delayed_converters is None: delayed_converters_was_None = True @@ -463,6 +468,9 @@ def convert_struct(container, cstruct=None, delayed_converters=None): def remove_regular_struct_content(container): STRUCT = container._TYPE + if isinstance(STRUCT, lltype.FixedSizeArray): + del container._items + return for field_name in STRUCT._names: FIELDTYPE = getattr(STRUCT, field_name) if not isinstance(FIELDTYPE, lltype.ContainerType): @@ -503,7 +511,11 @@ def remove_regular_array_content(container): def struct_use_ctypes_storage(container, ctypes_storage): STRUCT = container._TYPE assert isinstance(STRUCT, lltype.Struct) - add_storage(container, _struct_mixin, ctypes_storage) + if isinstance(container, lltype._fixedsizearray): + cls_mixin = _fixedsizedarray_mixin + else: + cls_mixin = _struct_mixin + add_storage(container, cls_mixin, ctypes_storage) remove_regular_struct_content(container) for field_name in STRUCT._names: FIELDTYPE = getattr(STRUCT, field_name) @@ -515,8 +527,10 @@ def struct_use_ctypes_storage(container, ctypes_storage): struct_use_ctypes_storage(struct_container, struct_storage) struct_container._setparentstructure(container, field_name) elif isinstance(FIELDTYPE, lltype.Array): - assert FIELDTYPE._hints.get('nolength', False) == False - arraycontainer = _array_of_known_length(FIELDTYPE) + if FIELDTYPE._hints.get('nolength', False): + arraycontainer = _array_of_unknown_length(FIELDTYPE) + else: + arraycontainer = _array_of_known_length(FIELDTYPE) arraycontainer._storage = ctypes.pointer( getattr(ctypes_storage.contents, field_name)) arraycontainer._setparentstructure(container, field_name) @@ -528,6 +542,7 @@ def struct_use_ctypes_storage(container, ctypes_storage): # Ctypes-aware subclasses of the _parentable classes ALLOCATED = {} # mapping {address: _container} +DEBUG_ALLOCATED = False def get_common_subclass(cls1, cls2, cache={}): """Return a unique subclass with (cls1, cls2) as bases.""" @@ -567,6 +582,8 @@ class _parentable_mixin(object): raise Exception("internal ll2ctypes error - " "double conversion from lltype to ctypes?") # XXX don't store here immortal structures + if DEBUG_ALLOCATED: + print >> sys.stderr, "LL2CTYPES:", hex(addr) ALLOCATED[addr] = self def _addressof_storage(self): @@ -579,6 +596,8 @@ class _parentable_mixin(object): self._check() # no double-frees # allow the ctypes object to go away now addr = ctypes.cast(self._storage, ctypes.c_void_p).value + if DEBUG_ALLOCATED: + print >> sys.stderr, "LL2C FREE:", hex(addr) try: del ALLOCATED[addr] except KeyError: @@ -613,11 +632,14 @@ class _parentable_mixin(object): return object.__hash__(self) def __repr__(self): + if '__str__' in self._TYPE._adtmeths: + r = self._TYPE._adtmeths['__str__'](self) + else: + r = 'C object %s' % (self._TYPE,) if self._storage is None: - return '<freed C object %s>' % (self._TYPE,) + return '<freed %s>' % (r,) else: - return '<C object %s at 0x%x>' % (self._TYPE, - fixid(self._addressof_storage())) + return '<%s at 0x%x>' % (r, fixid(self._addressof_storage())) def __str__(self): return repr(self) @@ -642,6 +664,45 @@ class _struct_mixin(_parentable_mixin): cobj = lltype2ctypes(value) setattr(self._storage.contents, field_name, cobj) +class _fixedsizedarray_mixin(_parentable_mixin): + """Mixin added to _fixedsizearray containers when they become ctypes-based.""" + __slots__ = () + + def __getattr__(self, field_name): + if hasattr(self, '_items'): + obj = lltype._fixedsizearray.__getattr__.im_func(self, field_name) + return obj + else: + cobj = getattr(self._storage.contents, field_name) + T = getattr(self._TYPE, field_name) + return ctypes2lltype(T, cobj) + + def __setattr__(self, field_name, value): + if field_name.startswith('_'): + object.__setattr__(self, field_name, value) # '_xxx' attributes + else: + cobj = lltype2ctypes(value) + if hasattr(self, '_items'): + lltype._fixedsizearray.__setattr__.im_func(self, field_name, cobj) + else: + setattr(self._storage.contents, field_name, cobj) + + + def getitem(self, index, uninitialized_ok=False): + if hasattr(self, '_items'): + obj = lltype._fixedsizearray.getitem.im_func(self, + index, uninitialized_ok=uninitialized_ok) + return obj + else: + return getattr(self, 'item%d' % index) + + def setitem(self, index, value): + cobj = lltype2ctypes(value) + if hasattr(self, '_items'): + lltype._fixedsizearray.setitem.im_func(self, index, value) + else: + setattr(self, 'item%d' % index, cobj) + class _array_mixin(_parentable_mixin): """Mixin added to _array containers when they become ctypes-based.""" __slots__ = () @@ -942,7 +1003,8 @@ def ctypes2lltype(T, cobj): REAL_TYPE = T.TO if T.TO._arrayfld is not None: carray = getattr(cobj.contents, T.TO._arrayfld) - container = lltype._struct(T.TO, carray.length) + length = getattr(carray, 'length', 9999) # XXX + container = lltype._struct(T.TO, length) else: # special treatment of 'OBJECT' subclasses if get_rtyper() and lltype._castdepth(REAL_TYPE, OBJECT) >= 0: diff --git a/rpython/rtyper/lltypesystem/lloperation.py b/rpython/rtyper/lltypesystem/lloperation.py index 87256ac849..9368164578 100644 --- a/rpython/rtyper/lltypesystem/lloperation.py +++ b/rpython/rtyper/lltypesystem/lloperation.py @@ -453,6 +453,8 @@ LL_OPERATIONS = { 'jit_record_exact_class' : LLOp(canrun=True), 'jit_ffi_save_result': LLOp(canrun=True), 'jit_conditional_call': LLOp(), + 'jit_enter_portal_frame': LLOp(canrun=True), + 'jit_leave_portal_frame': LLOp(canrun=True), 'get_exception_addr': LLOp(), 'get_exc_value_addr': LLOp(), 'do_malloc_fixedsize':LLOp(canmallocgc=True), @@ -503,6 +505,12 @@ LL_OPERATIONS = { 'gc_gcflag_extra' : LLOp(), 'gc_add_memory_pressure': LLOp(), + 'gc_rawrefcount_init': LLOp(), + 'gc_rawrefcount_create_link_pypy': LLOp(), + 'gc_rawrefcount_create_link_pyobj': LLOp(), + 'gc_rawrefcount_from_obj': LLOp(sideeffects=False), + 'gc_rawrefcount_to_obj': LLOp(sideeffects=False), + # ------- JIT & GC interaction, only for some GCs ---------- 'gc_adr_of_nursery_free' : LLOp(), diff --git a/rpython/rtyper/lltypesystem/lltype.py b/rpython/rtyper/lltypesystem/lltype.py index ac95f57e2d..677170a553 100644 --- a/rpython/rtyper/lltypesystem/lltype.py +++ b/rpython/rtyper/lltypesystem/lltype.py @@ -1761,7 +1761,10 @@ class _struct(_parentable): def __new__(self, TYPE, n=None, initialization=None, parent=None, parentindex=None): - my_variety = _struct_variety(TYPE._names) + if isinstance(TYPE, FixedSizeArray): + my_variety = _fixedsizearray + else: + my_variety = _struct_variety(TYPE._names) return object.__new__(my_variety) def __init__(self, TYPE, n=None, initialization=None, parent=None, @@ -1771,7 +1774,6 @@ class _struct(_parentable): raise TypeError("%r is not variable-sized" % (TYPE,)) if n is None and TYPE._arrayfld is not None: raise TypeError("%r is variable-sized" % (TYPE,)) - first, FIRSTTYPE = TYPE._first_struct() for fld, typ in TYPE._flds.items(): if fld == TYPE._arrayfld: value = _array(typ, n, initialization=initialization, @@ -1814,23 +1816,48 @@ class _struct(_parentable): raise UninitializedMemoryAccess("%r.%s"%(self, field_name)) return r - # for FixedSizeArray kind of structs: + +class _fixedsizearray(_struct): + def __init__(self, TYPE, n=None, initialization=None, parent=None, + parentindex=None): + _parentable.__init__(self, TYPE) + if n is not None: + raise TypeError("%r is not variable-sized" % (TYPE,)) + typ = TYPE.OF + storage = [] + for i, fld in enumerate(TYPE._names): + value = typ._allocate(initialization=initialization, + parent=self, parentindex=fld) + storage.append(value) + self._items = storage + if parent is not None: + self._setparentstructure(parent, parentindex) def getlength(self): - assert isinstance(self._TYPE, FixedSizeArray) return self._TYPE.length def getbounds(self): return 0, self.getlength() def getitem(self, index, uninitialized_ok=False): - assert isinstance(self._TYPE, FixedSizeArray) - return self._getattr('item%d' % index, uninitialized_ok) + assert 0 <= index < self.getlength() + return self._items[index] def setitem(self, index, value): - assert isinstance(self._TYPE, FixedSizeArray) - setattr(self, 'item%d' % index, value) + assert 0 <= index < self.getlength() + self._items[index] = value + def __getattr__(self, name): + # obscure + if name.startswith("item"): + return self.getitem(int(name[len('item'):])) + return _struct.__getattr__(self, name) + + def __setattr__(self, name, value): + if name.startswith("item"): + self.setitem(int(name[len('item'):]), value) + return + _struct.__setattr__(self, name, value) class _array(_parentable): _kind = "array" diff --git a/rpython/rtyper/lltypesystem/module/ll_math.py b/rpython/rtyper/lltypesystem/module/ll_math.py index 259b709d69..f3bbe4b033 100644 --- a/rpython/rtyper/lltypesystem/module/ll_math.py +++ b/rpython/rtyper/lltypesystem/module/ll_math.py @@ -6,8 +6,8 @@ import sys from rpython.translator import cdir from rpython.rlib import jit, rposix from rpython.rlib.rfloat import INFINITY, NAN, isfinite, isinf, isnan +from rpython.rlib.rposix import UNDERSCORE_ON_WIN32 from rpython.rtyper.lltypesystem import lltype, rffi -from rpython.rtyper.module.support import UNDERSCORE_ON_WIN32 from rpython.tool.sourcetools import func_with_new_name from rpython.translator.tool.cbuild import ExternalCompilationInfo from rpython.translator.platform import platform diff --git a/rpython/rtyper/lltypesystem/opimpl.py b/rpython/rtyper/lltypesystem/opimpl.py index dd702f73af..ae7db848a3 100644 --- a/rpython/rtyper/lltypesystem/opimpl.py +++ b/rpython/rtyper/lltypesystem/opimpl.py @@ -624,6 +624,12 @@ def op_jit_record_exact_class(x, y): def op_jit_ffi_save_result(*args): pass +def op_jit_enter_portal_frame(x): + pass + +def op_jit_leave_portal_frame(): + pass + def op_get_group_member(TYPE, grpptr, memberoffset): from rpython.rtyper.lltypesystem import llgroup assert isinstance(memberoffset, llgroup.GroupMemberOffset) diff --git a/rpython/rtyper/lltypesystem/rffi.py b/rpython/rtyper/lltypesystem/rffi.py index 0b5a124b06..ec0811d5a3 100644 --- a/rpython/rtyper/lltypesystem/rffi.py +++ b/rpython/rtyper/lltypesystem/rffi.py @@ -631,7 +631,8 @@ def COpaquePtr(*args, **kwds): def CExternVariable(TYPE, name, eci, _CConstantClass=CConstant, sandboxsafe=False, _nowrapper=False, - c_type=None, getter_only=False): + c_type=None, getter_only=False, + declare_as_extern=(sys.platform != 'win32')): """Return a pair of functions - a getter and a setter - to access the given global C variable. """ @@ -661,7 +662,7 @@ def CExternVariable(TYPE, name, eci, _CConstantClass=CConstant, c_setter = "void %(setter_name)s (%(c_type)s v) { %(name)s = v; }" % locals() lines = ["#include <%s>" % i for i in eci.includes] - if sys.platform != 'win32': + if declare_as_extern: lines.append('extern %s %s;' % (c_type, name)) lines.append(c_getter) if not getter_only: @@ -790,6 +791,12 @@ def make_string_mappings(strtype): return length str2chararray._annenforceargs_ = [strtype, None, int] + # s[start:start+length] -> already-existing char[], + # all characters including zeros + def str2rawmem(s, array, start, length): + ll_s = llstrtype(s) + copy_string_to_raw(ll_s, array, start, length) + # char* -> str # doesn't free char* def charp2str(cp): @@ -940,19 +947,19 @@ def make_string_mappings(strtype): return (str2charp, free_charp, charp2str, get_nonmovingbuffer, free_nonmovingbuffer, alloc_buffer, str_from_buffer, keep_buffer_alive_until_here, - charp2strn, charpsize2str, str2chararray, + charp2strn, charpsize2str, str2chararray, str2rawmem, ) (str2charp, free_charp, charp2str, get_nonmovingbuffer, free_nonmovingbuffer, alloc_buffer, str_from_buffer, keep_buffer_alive_until_here, - charp2strn, charpsize2str, str2chararray, + charp2strn, charpsize2str, str2chararray, str2rawmem, ) = make_string_mappings(str) (unicode2wcharp, free_wcharp, wcharp2unicode, get_nonmoving_unicodebuffer, free_nonmoving_unicodebuffer, alloc_unicodebuffer, unicode_from_buffer, keep_unicodebuffer_alive_until_here, - wcharp2unicoden, wcharpsize2unicode, unicode2wchararray, + wcharp2unicoden, wcharpsize2unicode, unicode2wchararray, unicode2rawmem, ) = make_string_mappings(unicode) # char** diff --git a/rpython/rtyper/lltypesystem/rstr.py b/rpython/rtyper/lltypesystem/rstr.py index b99c607e49..6e76ab4526 100644 --- a/rpython/rtyper/lltypesystem/rstr.py +++ b/rpython/rtyper/lltypesystem/rstr.py @@ -717,10 +717,7 @@ class LLHelpers(AbstractLLHelpers): return cls.ll_count_char(s1, s2.chars[0], start, end) res = cls.ll_search(s1, s2, start, end, FAST_COUNT) - # For a few cases ll_search can return -1 to indicate an "impossible" - # condition for a string match, count just returns 0 in these cases. - if res < 0: - res = 0 + assert res >= 0 return res @staticmethod @@ -741,6 +738,8 @@ class LLHelpers(AbstractLLHelpers): w = n - m if w < 0: + if mode == FAST_COUNT: + return 0 return -1 mlast = m - 1 diff --git a/rpython/rtyper/lltypesystem/test/test_ll2ctypes.py b/rpython/rtyper/lltypesystem/test/test_ll2ctypes.py index 4b1e59c884..3a9535925c 100644 --- a/rpython/rtyper/lltypesystem/test/test_ll2ctypes.py +++ b/rpython/rtyper/lltypesystem/test/test_ll2ctypes.py @@ -11,12 +11,12 @@ from rpython.rtyper.lltypesystem.ll2ctypes import cast_adr_to_int, get_ctypes_ty from rpython.rtyper.lltypesystem.ll2ctypes import _llgcopaque from rpython.rtyper.annlowlevel import llhelper from rpython.rlib import rposix +from rpython.rlib.rposix import UNDERSCORE_ON_WIN32 from rpython.translator.tool.cbuild import ExternalCompilationInfo from rpython.translator import cdir from rpython.tool.udir import udir from rpython.rtyper.test.test_llinterp import interpret from rpython.annotator.annrpython import RPythonAnnotator -from rpython.rtyper.module.support import UNDERSCORE_ON_WIN32 from rpython.rtyper.rtyper import RPythonTyper from rpython.rlib.rarithmetic import r_uint, get_long_pattern, is_emulated_long from rpython.rlib.rarithmetic import is_valid_int @@ -1466,3 +1466,20 @@ class TestPlatform(object): assert a[3].a == 17 #lltype.free(a, flavor='raw') py.test.skip("free() not working correctly here...") + + def test_fixedsizedarray_to_ctypes(self): + T = lltype.Ptr(rffi.CFixedArray(rffi.INT, 1)) + inst = lltype.malloc(T.TO, flavor='raw') + inst[0] = rffi.cast(rffi.INT, 42) + assert inst[0] == 42 + cinst = lltype2ctypes(inst) + assert rffi.cast(lltype.Signed, inst[0]) == 42 + assert cinst.contents.item0 == 42 + lltype.free(inst, flavor='raw') + + def test_fixedsizedarray_to_ctypes(self): + T = lltype.Ptr(rffi.CFixedArray(rffi.CHAR, 123)) + inst = lltype.malloc(T.TO, flavor='raw', zero=True) + cinst = lltype2ctypes(inst) + assert cinst.contents.item0 == 0 + lltype.free(inst, flavor='raw') diff --git a/rpython/rtyper/module/__init__.py b/rpython/rtyper/module/__init__.py deleted file mode 100644 index 792d600548..0000000000 --- a/rpython/rtyper/module/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/rpython/rtyper/module/test/__init__.py b/rpython/rtyper/module/test/__init__.py deleted file mode 100644 index 792d600548..0000000000 --- a/rpython/rtyper/module/test/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/rpython/rtyper/module/test/test_ll_strtod.py b/rpython/rtyper/module/test/test_ll_strtod.py deleted file mode 100644 index 4e1788eb69..0000000000 --- a/rpython/rtyper/module/test/test_ll_strtod.py +++ /dev/null @@ -1,13 +0,0 @@ -import py - -from rpython.rtyper.test.tool import BaseRtypingTest -from rpython.rlib import rfloat - -class TestStrtod(BaseRtypingTest): - def test_formatd(self): - for flags in [0, - rfloat.DTSF_ADD_DOT_0]: - def f(y): - return rfloat.formatd(y, 'g', 2, flags) - - assert self.ll_to_string(self.interpret(f, [3.0])) == f(3.0) diff --git a/rpython/rtyper/module/test/test_posix.py b/rpython/rtyper/module/test/test_posix.py deleted file mode 100644 index df091ed1c4..0000000000 --- a/rpython/rtyper/module/test/test_posix.py +++ /dev/null @@ -1,304 +0,0 @@ -import py -from rpython.rtyper.test.tool import BaseRtypingTest -from rpython.rtyper.annlowlevel import hlstr -from rpython.tool.udir import udir -from rpython.rlib.rarithmetic import is_valid_int - -import os -exec 'import %s as posix' % os.name - -def setup_module(module): - testf = udir.join('test.txt') - module.path = testf.strpath - -class TestPosix(BaseRtypingTest): - - def setup_method(self, meth): - # prepare/restore the file before each test - testfile = open(path, 'wb') - testfile.write('This is a test') - testfile.close() - - def test_open(self): - def f(): - ff = posix.open(path, posix.O_RDONLY, 0777) - return ff - func = self.interpret(f, []) - assert is_valid_int(func) - - def test_fstat(self): - def fo(fi): - g = posix.fstat(fi) - return g - fi = os.open(path,os.O_RDONLY,0777) - func = self.interpret(fo,[fi]) - stat = os.fstat(fi) - for i in range(len(stat)): - assert long(getattr(func, 'item%d' % i)) == stat[i] - - - def test_stat(self): - def fo(): - g = posix.stat(path) - return g - func = self.interpret(fo,[]) - stat = os.stat(path) - for i in range(len(stat)): - assert long(getattr(func, 'item%d' % i)) == stat[i] - - def test_stat_exception(self): - def fo(): - try: - posix.stat('I/do/not/exist') - except OSError: - return True - else: - return False - res = self.interpret(fo,[]) - assert res - - def test_times(self): - import py; py.test.skip("llinterp does not like tuple returns") - from rpython.rtyper.test.test_llinterp import interpret - times = interpret(lambda: posix.times(), ()) - assert isinstance(times, tuple) - assert len(times) == 5 - for value in times: - assert is_valid_int(value) - - - def test_lseek(self): - def f(fi, pos): - posix.lseek(fi, pos, 0) - fi = os.open(path, os.O_RDONLY, 0777) - func = self.interpret(f, [fi, 5]) - res = os.read(fi, 2) - assert res =='is' - - def test_isatty(self): - def f(fi): - posix.isatty(fi) - fi = os.open(path, os.O_RDONLY, 0777) - func = self.interpret(f, [fi]) - assert not func - os.close(fi) - func = self.interpret(f, [fi]) - assert not func - - def test_getcwd(self): - def f(): - return posix.getcwd() - res = self.interpret(f,[]) - cwd = os.getcwd() - #print res.chars,cwd - assert self.ll_to_string(res) == cwd - - def test_write(self): - def f(fi): - if fi > 0: - text = 'This is a test' - else: - text = '333' - return posix.write(fi,text) - fi = os.open(path,os.O_WRONLY,0777) - text = 'This is a test' - func = self.interpret(f,[fi]) - os.close(fi) - fi = os.open(path,os.O_RDONLY,0777) - res = os.read(fi,20) - assert res == text - - def test_read(self): - def f(fi,len): - return posix.read(fi,len) - fi = os.open(path,os.O_WRONLY,0777) - text = 'This is a test' - os.write(fi,text) - os.close(fi) - fi = os.open(path,os.O_RDONLY,0777) - res = self.interpret(f,[fi,20]) - assert self.ll_to_string(res) == text - - if hasattr(os, 'chown'): - def test_chown(self): - f = open(path, "w") - f.write("xyz") - f.close() - def f(): - try: - posix.chown(path, os.getuid(), os.getgid()) - return 1 - except OSError: - return 2 - - assert self.interpret(f, []) == 1 - os.unlink(path) - assert self.interpret(f, []) == 2 - - def test_close(self): - def f(fi): - return posix.close(fi) - fi = os.open(path,os.O_WRONLY,0777) - text = 'This is a test' - os.write(fi,text) - res = self.interpret(f,[fi]) - py.test.raises( OSError, os.fstat, fi) - - if hasattr(os, 'ftruncate'): - def test_ftruncate(self): - def f(fi,len): - os.ftruncate(fi,len) - fi = os.open(path,os.O_RDWR,0777) - func = self.interpret(f,[fi,6]) - assert os.fstat(fi).st_size == 6 - - if hasattr(os, 'getuid'): - def test_getuid(self): - def f(): - return os.getuid() - assert self.interpret(f, []) == f() - - if hasattr(os, 'getgid'): - def test_getgid(self): - def f(): - return os.getgid() - assert self.interpret(f, []) == f() - - if hasattr(os, 'setuid'): - def test_os_setuid(self): - def f(): - os.setuid(os.getuid()) - return os.getuid() - assert self.interpret(f, []) == f() - - if hasattr(os, 'sysconf'): - def test_os_sysconf(self): - def f(i): - return os.sysconf(i) - assert self.interpret(f, [13]) == f(13) - - if hasattr(os, 'confstr'): - def test_os_confstr(self): - def f(i): - try: - return os.confstr(i) - except OSError: - return "oooops!!" - some_value = os.confstr_names.values()[-1] - res = self.interpret(f, [some_value]) - assert hlstr(res) == f(some_value) - res = self.interpret(f, [94781413]) - assert hlstr(res) == "oooops!!" - - if hasattr(os, 'pathconf'): - def test_os_pathconf(self): - def f(i): - return os.pathconf("/tmp", i) - i = os.pathconf_names["PC_NAME_MAX"] - some_value = self.interpret(f, [i]) - assert some_value >= 31 - - if hasattr(os, 'chroot'): - def test_os_chroot(self): - def f(): - try: - os.chroot('!@$#!#%$#^#@!#!$$#^') - except OSError: - return 1 - return 0 - - assert self.interpret(f, []) == 1 - - def test_os_wstar(self): - from rpython.rlib import rposix - for name in rposix.WAIT_MACROS: - if not hasattr(os, name): - continue - def fun(s): - return getattr(os, name)(s) - - for value in [0, 1, 127, 128, 255]: - res = self.interpret(fun, [value]) - assert res == fun(value) - - if hasattr(os, 'getgroups'): - def test_getgroups(self): - def f(): - return os.getgroups() - ll_a = self.interpret(f, []) - assert self.ll_to_list(ll_a) == f() - - if hasattr(os, 'setgroups'): - def test_setgroups(self): - def f(): - try: - os.setgroups(os.getgroups()) - except OSError: - pass - self.interpret(f, []) - - if hasattr(os, 'initgroups'): - def test_initgroups(self): - def f(): - try: - os.initgroups('sUJJeumz', 4321) - except OSError: - return 1 - return 0 - res = self.interpret(f, []) - assert res == 1 - - if hasattr(os, 'tcgetpgrp'): - def test_tcgetpgrp(self): - def f(fd): - try: - return os.tcgetpgrp(fd) - except OSError: - return 42 - res = self.interpret(f, [9999]) - assert res == 42 - - if hasattr(os, 'tcsetpgrp'): - def test_tcsetpgrp(self): - def f(fd, pgrp): - try: - os.tcsetpgrp(fd, pgrp) - except OSError: - return 1 - return 0 - res = self.interpret(f, [9999, 1]) - assert res == 1 - - if hasattr(os, 'getresuid'): - def test_getresuid(self): - def f(): - a, b, c = os.getresuid() - return a + b * 37 + c * 1291 - res = self.interpret(f, []) - a, b, c = os.getresuid() - assert res == a + b * 37 + c * 1291 - - if hasattr(os, 'getresgid'): - def test_getresgid(self): - def f(): - a, b, c = os.getresgid() - return a + b * 37 + c * 1291 - res = self.interpret(f, []) - a, b, c = os.getresgid() - assert res == a + b * 37 + c * 1291 - - if hasattr(os, 'setresuid'): - def test_setresuid(self): - def f(): - a, b, c = os.getresuid() - a = (a + 1) - 1 - os.setresuid(a, b, c) - self.interpret(f, []) - - if hasattr(os, 'setresgid'): - def test_setresgid(self): - def f(): - a, b, c = os.getresgid() - a = (a + 1) - 1 - os.setresgid(a, b, c) - self.interpret(f, []) diff --git a/rpython/rtyper/rtyper.py b/rpython/rtyper/rtyper.py index 7734f74554..dfe867ad00 100644 --- a/rpython/rtyper/rtyper.py +++ b/rpython/rtyper/rtyper.py @@ -32,11 +32,24 @@ from rpython.translator.unsimplify import insert_empty_block from rpython.translator.sandbox.rsandbox import make_sandbox_trampoline +class RTyperBackend(object): + pass + +class GenCBackend(RTyperBackend): + pass +genc_backend = GenCBackend() + +class LLInterpBackend(RTyperBackend): + pass +llinterp_backend = LLInterpBackend() + + class RPythonTyper(object): from rpython.rtyper.rmodel import log - def __init__(self, annotator): + def __init__(self, annotator, backend=genc_backend): self.annotator = annotator + self.backend = backend self.lowlevel_ann_policy = LowLevelAnnotatorPolicy(self) self.reprs = {} self._reprs_must_call_setup = [] diff --git a/rpython/rtyper/test/test_extfunc.py b/rpython/rtyper/test/test_extfunc.py index c6b3948a9a..778be158f7 100644 --- a/rpython/rtyper/test/test_extfunc.py +++ b/rpython/rtyper/test/test_extfunc.py @@ -1,7 +1,6 @@ import py -from rpython.rtyper.extfunc import ExtFuncEntry, register_external,\ - is_external +from rpython.rtyper.extfunc import register_external from rpython.annotator.model import SomeInteger, SomeString, AnnotatorError from rpython.annotator.annrpython import RPythonAnnotator from rpython.annotator.policy import AnnotatorPolicy @@ -19,11 +18,7 @@ class TestExtFuncEntry: "NOT_RPYTHON" return eval("x+40") - class BTestFuncEntry(ExtFuncEntry): - _about_ = b - name = 'b' - signature_args = [SomeInteger()] - signature_result = SomeInteger() + register_external(b, [int], result=int) def f(): return b(2) @@ -43,15 +38,11 @@ class TestExtFuncEntry: def c(y, x): yyy - class CTestFuncEntry(ExtFuncEntry): - _about_ = c - name = 'ccc' - signature_args = [SomeInteger()] * 2 - signature_result = SomeInteger() + def llimpl(y, x): + return y + x - def lltypeimpl(y, x): - return y + x - lltypeimpl = staticmethod(lltypeimpl) + register_external(c, [int, int], result=int, llimpl=llimpl, + export_name='ccc') def f(): return c(3, 4) @@ -59,22 +50,6 @@ class TestExtFuncEntry: res = interpret(f, []) assert res == 7 - def test_register_external_signature(self): - """ - Test the standard interface for external functions. - """ - def dd(): - pass - register_external(dd, [int], int) - - def f(): - return dd(3) - - policy = AnnotatorPolicy() - a = RPythonAnnotator(policy=policy) - s = a.build_types(f, []) - assert isinstance(s, SomeInteger) - def test_register_external_tuple_args(self): """ Verify the annotation of a registered external function which takes a @@ -121,23 +96,6 @@ class TestExtFuncEntry: s = a.build_types(f, []) assert isinstance(s, SomeInteger) - def test_register_external_specialcase(self): - """ - When args=None, the external function accepts any arguments unmodified. - """ - def function_withspecialcase(arg): - return repr(arg) - register_external(function_withspecialcase, args=None, result=str) - - def f(): - x = function_withspecialcase - return x(33) + x("aaa") + x([]) + "\n" - - policy = AnnotatorPolicy() - a = RPythonAnnotator(policy=policy) - s = a.build_types(f, []) - assert isinstance(s, SomeString) - def test_str0(self): str0 = SomeString(no_nul=True) def os_open(s): @@ -182,3 +140,22 @@ class TestExtFuncEntry: # fails with TooLateForChange a.build_types(g, [[str]]) a.build_types(g, [[str0]]) # Does not raise + + def test_register_external_llfakeimpl(self): + def a(i): + return i + def a_llimpl(i): + return i * 2 + def a_llfakeimpl(i): + return i * 3 + register_external(a, [int], int, llimpl=a_llimpl, + llfakeimpl=a_llfakeimpl) + def f(i): + return a(i) + + res = interpret(f, [7]) + assert res == 21 + + from rpython.translator.c.test.test_genc import compile + fc = compile(f, [int]) + assert fc(7) == 14 diff --git a/rpython/rtyper/test/test_llinterp.py b/rpython/rtyper/test/test_llinterp.py index 9c8796ac6c..479a1de0a9 100644 --- a/rpython/rtyper/test/test_llinterp.py +++ b/rpython/rtyper/test/test_llinterp.py @@ -13,7 +13,7 @@ from rpython.rtyper.llannotation import lltype_to_annotation from rpython.rlib.rarithmetic import r_uint, ovfcheck from rpython.tool import leakfinder from rpython.conftest import option - +from rpython.rtyper.rtyper import llinterp_backend # switch on logging of interp to show more info on failing tests @@ -39,6 +39,7 @@ def gengraph(func, argtypes=[], viewbefore='auto', policy=None, t.view() global typer # we need it for find_exception typer = t.buildrtyper() + typer.backend = llinterp_backend typer.specialize() #t.view() t.checkgraphs() diff --git a/rpython/rtyper/test/test_rbuiltin.py b/rpython/rtyper/test/test_rbuiltin.py index 5531dcaedc..767fd1d7ed 100644 --- a/rpython/rtyper/test/test_rbuiltin.py +++ b/rpython/rtyper/test/test_rbuiltin.py @@ -3,8 +3,7 @@ import os import py -from rpython.rlib.debug import llinterpcall -from rpython.rlib.objectmodel import instantiate, running_on_llinterp, compute_unique_id, current_object_addr_as_int +from rpython.rlib.objectmodel import instantiate, compute_unique_id, current_object_addr_as_int from rpython.rlib.rarithmetic import (intmask, longlongmask, r_int64, is_valid_int, r_int, r_uint, r_longlong, r_ulonglong) from rpython.rlib.rstring import StringBuilder, UnicodeBuilder @@ -456,26 +455,6 @@ class TestRbuiltin(BaseRtypingTest): res = self.interpret(fn, [3.25]) assert res == 7.25 - def test_debug_llinterpcall(self): - S = lltype.Struct('S', ('m', lltype.Signed)) - SPTR = lltype.Ptr(S) - def foo(n): - "NOT_RPYTHON" - s = lltype.malloc(S, immortal=True) - s.m = eval("n*6", locals()) - return s - def fn(n): - if running_on_llinterp: - return llinterpcall(SPTR, foo, n).m - else: - return 321 - res = self.interpret(fn, [7]) - assert res == 42 - from rpython.translator.c.test.test_genc import compile - f = compile(fn, [int]) - res = f(7) - assert res == 321 - def test_id(self): class A: pass diff --git a/rpython/rtyper/test/test_rdict.py b/rpython/rtyper/test/test_rdict.py index f71aaf8174..e9a8b1c718 100644 --- a/rpython/rtyper/test/test_rdict.py +++ b/rpython/rtyper/test/test_rdict.py @@ -1,13 +1,63 @@ +import sys +from contextlib import contextmanager +import signal + from rpython.translator.translator import TranslationContext +from rpython.annotator.model import ( + SomeInteger, SomeString, SomeChar, SomeUnicodeString, SomeUnicodeCodePoint) +from rpython.annotator.dictdef import DictKey, DictValue from rpython.rtyper.lltypesystem import lltype, rffi -from rpython.rtyper import rint -from rpython.rtyper.lltypesystem import rdict, rstr +from rpython.rtyper.lltypesystem import rdict from rpython.rtyper.test.tool import BaseRtypingTest from rpython.rlib.objectmodel import r_dict from rpython.rlib.rarithmetic import r_int, r_uint, r_longlong, r_ulonglong import py -py.log.setconsumer("rtyper", py.log.STDOUT) +from hypothesis import settings +from hypothesis.strategies import ( + builds, sampled_from, binary, just, integers, text, characters, tuples) +from hypothesis.stateful import GenericStateMachine, run_state_machine_as_test + +def ann2strategy(s_value): + if isinstance(s_value, SomeChar): + return builds(chr, integers(min_value=0, max_value=255)) + elif isinstance(s_value, SomeString): + if s_value.can_be_None: + return binary() | just(None) + else: + return binary() + elif isinstance(s_value, SomeUnicodeCodePoint): + return characters() + elif isinstance(s_value, SomeUnicodeString): + if s_value.can_be_None: + return text() | just(None) + else: + return text() + elif isinstance(s_value, SomeInteger): + return integers(min_value=~sys.maxint, max_value=sys.maxint) + else: + raise TypeError("Cannot convert annotation %s to a strategy" % s_value) + + +if hasattr(signal, 'alarm'): + @contextmanager + def signal_timeout(n): + """A flaky context manager that throws an exception if the body of the + `with` block runs for longer than `n` seconds. + """ + def handler(signum, frame): + raise RuntimeError('timeout') + signal.signal(signal.SIGALRM, handler) + signal.alarm(n) + try: + yield + finally: + signal.alarm(0) +else: + @contextmanager + def signal_timeout(n): + yield + def not_really_random(): """A random-ish generator, which also generates nice patterns from time to time. @@ -199,9 +249,8 @@ class BaseTestRDict(BaseRtypingTest): def test_dict_copy(self): def func(): - # XXX this does not work if we use chars, only! dic = self.newdict() - dic['ab'] = 1 + dic['a'] = 1 dic['b'] = 2 d2 = dic.copy() ok = 1 @@ -1004,28 +1053,6 @@ class TestRDict(BaseTestRDict): assert r_AB_dic.lowleveltype == r_BA_dic.lowleveltype - def test_dict_resize(self): - py.test.skip("test written for non-ordered dicts, update or kill") - # XXX we no longer automatically resize on 'del'. We need to - # hack a bit in this test to trigger a resize by continuing to - # fill the dict's table while keeping the actual size very low - # in order to force a resize to shrink the table back - def func(want_empty): - d = self.newdict() - for i in range(rdict.DICT_INITSIZE << 1): - d[chr(ord('a') + i)] = i - if want_empty: - for i in range(rdict.DICT_INITSIZE << 1): - del d[chr(ord('a') + i)] - for i in range(rdict.DICT_INITSIZE << 3): - d[chr(ord('A') - i)] = i - del d[chr(ord('A') - i)] - return d - res = self.interpret(func, [0]) - assert len(res.entries) > rdict.DICT_INITSIZE - res = self.interpret(func, [1]) - assert len(res.entries) == rdict.DICT_INITSIZE - def test_opt_dummykeymarker(self): def f(): d = {"hello": None} @@ -1117,183 +1144,111 @@ class TestRDict(BaseTestRDict): DICT = lltype.typeOf(llres.item1) assert sorted(DICT.TO.entries.TO.OF._flds) == ['f_hash', 'key', 'value'] - def test_deleted_entry_reusage_with_colliding_hashes(self): - py.test.skip("test written for non-ordered dicts, update or kill") - def lowlevelhash(value): - p = rstr.mallocstr(len(value)) - for i in range(len(value)): - p.chars[i] = value[i] - return rstr.LLHelpers.ll_strhash(p) - - def func(c1, c2): - c1 = chr(c1) - c2 = chr(c2) - d = self.newdict() - d[c1] = 1 - d[c2] = 2 - del d[c1] - return d[c2] - - char_by_hash = {} - base = rdict.DICT_INITSIZE - for y in range(0, 256): - y = chr(y) - y_hash = lowlevelhash(y) % base - char_by_hash.setdefault(y_hash, []).append(y) - - x, y = char_by_hash[0][:2] # find a collision - - res = self.interpret(func, [ord(x), ord(y)]) - assert res == 2 - - def func2(c1, c2): - c1 = chr(c1) - c2 = chr(c2) - d = self.newdict() - d[c1] = 1 - d[c2] = 2 - del d[c1] - d[c1] = 3 - return d - - res = self.interpret(func2, [ord(x), ord(y)]) - for i in range(len(res.entries)): - assert not (res.entries.everused(i) and not res.entries.valid(i)) - def func3(c0, c1, c2, c3, c4, c5, c6, c7): - d = self.newdict() - c0 = chr(c0) ; d[c0] = 1; del d[c0] - c1 = chr(c1) ; d[c1] = 1; del d[c1] - c2 = chr(c2) ; d[c2] = 1; del d[c2] - c3 = chr(c3) ; d[c3] = 1; del d[c3] - c4 = chr(c4) ; d[c4] = 1; del d[c4] - c5 = chr(c5) ; d[c5] = 1; del d[c5] - c6 = chr(c6) ; d[c6] = 1; del d[c6] - c7 = chr(c7) ; d[c7] = 1; del d[c7] - return d - - if rdict.DICT_INITSIZE != 8: - py.test.skip("make dict tests more indepdent from initsize") - res = self.interpret(func3, [ord(char_by_hash[i][0]) - for i in range(rdict.DICT_INITSIZE)]) - count_frees = 0 - for i in range(len(res.entries)): - if not res.entries.everused(i): - count_frees += 1 - assert count_frees >= 3 - -class TestStress: - - def test_stress(self): - from rpython.annotator.dictdef import DictKey, DictValue - from rpython.annotator import model as annmodel - dictrepr = rdict.DictRepr(None, rint.signed_repr, rint.signed_repr, - DictKey(None, annmodel.SomeInteger()), - DictValue(None, annmodel.SomeInteger())) +class Action(object): + def __init__(self, method, args): + self.method = method + self.args = args + + def execute(self, space): + getattr(space, self.method)(*self.args) + + def __repr__(self): + return "space.%s(%s)" % (self.method, ', '.join(map(repr, self.args))) + +class PseudoRTyper: + cache_dummy_values = {} + +# XXX: None keys crash the test, but translation sort-of allows it +keytypes_s = [ + SomeString(), SomeInteger(), SomeChar(), + SomeUnicodeString(), SomeUnicodeCodePoint()] +st_keys = sampled_from(keytypes_s) +st_values = sampled_from(keytypes_s + [SomeString(can_be_None=True)]) + +class Space(object): + def __init__(self, s_key, s_value): + self.s_key = s_key + self.s_value = s_value + rtyper = PseudoRTyper() + r_key = s_key.rtyper_makerepr(rtyper) + r_value = s_value.rtyper_makerepr(rtyper) + dictrepr = rdict.DictRepr(rtyper, r_key, r_value, + DictKey(None, s_key), + DictValue(None, s_value)) dictrepr.setup() - l_dict = rdict.ll_newdict(dictrepr.DICT) - referencetable = [None] * 400 - referencelength = 0 - value = 0 - - def complete_check(): - for n, refvalue in zip(range(len(referencetable)), referencetable): - try: - gotvalue = rdict.ll_dict_getitem(l_dict, n) - except KeyError: - assert refvalue is None - else: - assert gotvalue == refvalue - - for x in not_really_random(): - n = int(x*100.0) # 0 <= x < 400 - op = repr(x)[-1] - if op <= '2' and referencetable[n] is not None: - rdict.ll_dict_delitem(l_dict, n) - referencetable[n] = None - referencelength -= 1 - elif op <= '6': - rdict.ll_dict_setitem(l_dict, n, value) - if referencetable[n] is None: - referencelength += 1 - referencetable[n] = value - value += 1 - else: - try: - gotvalue = rdict.ll_dict_getitem(l_dict, n) - except KeyError: - assert referencetable[n] is None - else: - assert gotvalue == referencetable[n] - if 1.38 <= x <= 1.39: - complete_check() - print 'current dict length:', referencelength - assert l_dict.num_items == referencelength - complete_check() - - def test_stress_2(self): - yield self.stress_combination, True, False - yield self.stress_combination, False, True - yield self.stress_combination, False, False - yield self.stress_combination, True, True - - def stress_combination(self, key_can_be_none, value_can_be_none): - from rpython.rtyper.lltypesystem.rstr import string_repr - from rpython.annotator.dictdef import DictKey, DictValue - from rpython.annotator import model as annmodel - - print - print "Testing combination with can_be_None: keys %s, values %s" % ( - key_can_be_none, value_can_be_none) - - class PseudoRTyper: - cache_dummy_values = {} - dictrepr = rdict.DictRepr(PseudoRTyper(), string_repr, string_repr, - DictKey(None, annmodel.SomeString(key_can_be_none)), - DictValue(None, annmodel.SomeString(value_can_be_none))) - dictrepr.setup() - print dictrepr.lowleveltype - for key, value in dictrepr.DICTENTRY._adtmeths.items(): - print ' %s = %s' % (key, value) - l_dict = rdict.ll_newdict(dictrepr.DICT) - referencetable = [None] * 400 - referencelength = 0 - values = not_really_random() - keytable = [string_repr.convert_const("foo%d" % n) - for n in range(len(referencetable))] - - def complete_check(): - for n, refvalue in zip(range(len(referencetable)), referencetable): - try: - gotvalue = rdict.ll_dict_getitem(l_dict, keytable[n]) - except KeyError: - assert refvalue is None - else: - assert gotvalue == refvalue - - for x in not_really_random(): - n = int(x*100.0) # 0 <= x < 400 - op = repr(x)[-1] - if op <= '2' and referencetable[n] is not None: - rdict.ll_dict_delitem(l_dict, keytable[n]) - referencetable[n] = None - referencelength -= 1 - elif op <= '6': - ll_value = string_repr.convert_const(str(values.next())) - rdict.ll_dict_setitem(l_dict, keytable[n], ll_value) - if referencetable[n] is None: - referencelength += 1 - referencetable[n] = ll_value - else: - try: - gotvalue = rdict.ll_dict_getitem(l_dict, keytable[n]) - except KeyError: - assert referencetable[n] is None - else: - assert gotvalue == referencetable[n] - if 1.38 <= x <= 1.39: - complete_check() - print 'current dict length:', referencelength - assert l_dict.num_items == referencelength - complete_check() - + self.l_dict = rdict.ll_newdict(dictrepr.DICT) + self.reference = {} + self.ll_key = r_key.convert_const + self.ll_value = r_value.convert_const + + def setitem(self, key, value): + ll_key = self.ll_key(key) + ll_value = self.ll_value(value) + rdict.ll_dict_setitem(self.l_dict, ll_key, ll_value) + self.reference[key] = value + assert rdict.ll_contains(self.l_dict, ll_key) + + def delitem(self, key): + ll_key = self.ll_key(key) + rdict.ll_dict_delitem(self.l_dict, ll_key) + del self.reference[key] + assert not rdict.ll_contains(self.l_dict, ll_key) + + def copydict(self): + self.l_dict = rdict.ll_copy(self.l_dict) + + def cleardict(self): + rdict.ll_clear(self.l_dict) + self.reference.clear() + assert rdict.ll_dict_len(self.l_dict) == 0 + + def fullcheck(self): + assert rdict.ll_dict_len(self.l_dict) == len(self.reference) + for key, value in self.reference.iteritems(): + assert (rdict.ll_dict_getitem(self.l_dict, self.ll_key(key)) == + self.ll_value(value)) + +class StressTest(GenericStateMachine): + def __init__(self): + self.space = None + + def st_setitem(self): + return builds(Action, + just('setitem'), tuples(self.st_keys, self.st_values)) + + def st_updateitem(self): + return builds(Action, + just('setitem'), + tuples(sampled_from(self.space.reference), self.st_values)) + + def st_delitem(self): + return builds(Action, + just('delitem'), tuples(sampled_from(self.space.reference))) + + def steps(self): + if not self.space: + return builds(Action, just('setup'), tuples(st_keys, st_values)) + global_actions = [Action('copydict', ()), Action('cleardict', ())] + if self.space.reference: + return ( + self.st_setitem() | sampled_from(global_actions) | + self.st_updateitem() | self.st_delitem()) + else: + return (self.st_setitem() | sampled_from(global_actions)) + + def execute_step(self, action): + if action.method == 'setup': + self.space = Space(*action.args) + self.st_keys = ann2strategy(self.space.s_key) + self.st_values = ann2strategy(self.space.s_value) + return + with signal_timeout(1): # catches infinite loops + action.execute(self.space) + + def teardown(self): + if self.space: + self.space.fullcheck() + +def test_hypothesis(): + run_state_machine_as_test(StressTest, settings(max_examples=500, stateful_step_count=100)) diff --git a/rpython/rtyper/test/test_rfloat.py b/rpython/rtyper/test/test_rfloat.py index e563d797c4..c87f3a4599 100644 --- a/rpython/rtyper/test/test_rfloat.py +++ b/rpython/rtyper/test/test_rfloat.py @@ -204,6 +204,14 @@ class TestRfloat(BaseRtypingTest): res = self.ll_to_string(self.interpret(f, [10/3.0])) assert res == '3.33' + def test_formatd_g(self): + from rpython.rlib import rfloat + for flags in [0, rfloat.DTSF_ADD_DOT_0]: + def f(y): + return rfloat.formatd(y, 'g', 2, flags) + + assert self.ll_to_string(self.interpret(f, [3.0])) == f(3.0) + def test_formatd_repr(self): from rpython.rlib.rfloat import formatd def f(x): diff --git a/rpython/rtyper/test/test_rpbc.py b/rpython/rtyper/test/test_rpbc.py index 0c80338eb5..0123ca3ef8 100644 --- a/rpython/rtyper/test/test_rpbc.py +++ b/rpython/rtyper/test/test_rpbc.py @@ -1,7 +1,7 @@ import py from rpython.annotator import model as annmodel -from rpython.annotator import policy, specialize +from rpython.annotator import specialize from rpython.rtyper.lltypesystem.lltype import typeOf from rpython.rtyper.test.tool import BaseRtypingTest from rpython.rtyper.llannotation import SomePtr, lltype_to_annotation @@ -1690,59 +1690,6 @@ class TestRPBC(BaseRtypingTest): # ____________________________________________________________ -class TestRPBCExtra(BaseRtypingTest): - - def test_folding_specialize_support(self): - - class S(object): - - def w(s, x): - if isinstance(x, int): - return x - if isinstance(x, str): - return len(x) - return -1 - w._annspecialcase_ = "specialize:w" - - def _freeze_(self): - return True - - s = S() - - def f(i, n): - w = s.w - if i == 0: - return w(0) - elif i == 1: - return w("abc") - elif i == 2: - return w(3*n) - elif i == 3: - return w(str(n)) - return -1 - - class P(policy.AnnotatorPolicy): - def specialize__w(pol, funcdesc, args_s): - typ = args_s[1].knowntype - if args_s[0].is_constant() and args_s[1].is_constant(): - x = args_s[1].const - v = s.w(x) - builder = specialize.make_constgraphbuilder(2, v) - return funcdesc.cachedgraph(x, builder=builder) - return funcdesc.cachedgraph(typ) - - p = P() - - res = self.interpret(f, [0, 66], policy=p) - assert res == 0 - res = self.interpret(f, [1, 66], policy=p) - assert res == 3 - res = self.interpret(f, [2, 4], policy=p) - assert res == 12 - res = self.interpret(f, [3, 5555], policy=p) - assert res == 4 - - def test_hlinvoke_simple(): def f(a,b): return a + b @@ -1998,7 +1945,7 @@ class TestSmallFuncSets(TestRPBC): def interpret(self, fn, args, **kwds): kwds['config'] = self.config - return TestRPBC.interpret(self, fn, args, **kwds) + return TestRPBC.interpret(fn, args, **kwds) def test_smallfuncsets_basic(): from rpython.translator.translator import TranslationContext, graphof diff --git a/rpython/rtyper/test/tool.py b/rpython/rtyper/test/tool.py index d565f1ecc7..079abaa9c7 100644 --- a/rpython/rtyper/test/tool.py +++ b/rpython/rtyper/test/tool.py @@ -5,22 +5,27 @@ from rpython.rtyper.test.test_llinterp import gengraph, interpret, interpret_rai class BaseRtypingTest(object): FLOAT_PRECISION = 8 - def gengraph(self, func, argtypes=[], viewbefore='auto', policy=None, + @staticmethod + def gengraph(func, argtypes=[], viewbefore='auto', policy=None, backendopt=False, config=None): return gengraph(func, argtypes, viewbefore, policy, backendopt=backendopt, config=config) - def interpret(self, fn, args, **kwds): + @staticmethod + def interpret(fn, args, **kwds): return interpret(fn, args, **kwds) - def interpret_raises(self, exc, fn, args, **kwds): + @staticmethod + def interpret_raises(exc, fn, args, **kwds): return interpret_raises(exc, fn, args, **kwds) - def float_eq(self, x, y): + @staticmethod + def float_eq(x, y): return x == y - def float_eq_approx(self, x, y): - maxError = 10**-self.FLOAT_PRECISION + @classmethod + def float_eq_approx(cls, x, y): + maxError = 10**-cls.FLOAT_PRECISION if abs(x-y) < maxError: return True @@ -31,45 +36,66 @@ class BaseRtypingTest(object): return relativeError < maxError - def is_of_type(self, x, type_): + @staticmethod + def is_of_type(x, type_): return type(x) is type_ - def _skip_llinterpreter(self, reason): + @staticmethod + def _skip_llinterpreter(reason): py.test.skip("lltypesystem doesn't support %s, yet" % reason) - def ll_to_string(self, s): + @staticmethod + def ll_to_string(s): if not s: return None return ''.join(s.chars) - def ll_to_unicode(self, s): + @staticmethod + def ll_to_unicode(s): return u''.join(s.chars) - def string_to_ll(self, s): - from rpython.rtyper.module.support import LLSupport - return LLSupport.to_rstr(s) - - def unicode_to_ll(self, s): - from rpython.rtyper.module.support import LLSupport - return LLSupport.to_runicode(s) - - def ll_to_list(self, l): + @staticmethod + def string_to_ll(s): + from rpython.rtyper.lltypesystem.rstr import STR, mallocstr + if s is None: + return lltype.nullptr(STR) + p = mallocstr(len(s)) + for i in range(len(s)): + p.chars[i] = s[i] + return p + + @staticmethod + def unicode_to_ll(s): + from rpython.rtyper.lltypesystem.rstr import UNICODE, mallocunicode + if s is None: + return lltype.nullptr(UNICODE) + p = mallocunicode(len(s)) + for i in range(len(s)): + p.chars[i] = s[i] + return p + + @staticmethod + def ll_to_list(l): r = [] items = l.ll_items() for i in range(l.ll_length()): r.append(items[i]) return r - def ll_unpack_tuple(self, t, length): + @staticmethod + def ll_unpack_tuple(t, length): return tuple([getattr(t, 'item%d' % i) for i in range(length)]) - def get_callable(self, fnptr): + @staticmethod + def get_callable(fnptr): return fnptr._obj._callable - def class_name(self, value): + @staticmethod + def class_name(value): return ''.join(value.super.typeptr.name.chars) - def read_attr(self, value, attr_name): + @staticmethod + def read_attr(value, attr_name): value = value._obj while value is not None: attr = getattr(value, "inst_" + attr_name, None) @@ -79,6 +105,7 @@ class BaseRtypingTest(object): return attr raise AttributeError() - def is_of_instance_type(self, val): + @staticmethod + def is_of_instance_type(val): T = lltype.typeOf(val) return isinstance(T, lltype.Ptr) and isinstance(T.TO, lltype.GcStruct) diff --git a/rpython/rtyper/tool/rffi_platform.py b/rpython/rtyper/tool/rffi_platform.py index 0366c88454..eade70e2bd 100755 --- a/rpython/rtyper/tool/rffi_platform.py +++ b/rpython/rtyper/tool/rffi_platform.py @@ -263,10 +263,11 @@ class Struct(CConfigEntry): """An entry in a CConfig class that stands for an externally defined structure. """ - def __init__(self, name, interesting_fields, ifdef=None): + def __init__(self, name, interesting_fields, ifdef=None, adtmeths={}): self.name = name self.interesting_fields = interesting_fields self.ifdef = ifdef + self.adtmeths = adtmeths def prepare_code(self): if self.ifdef is not None: @@ -313,7 +314,9 @@ class Struct(CConfigEntry): offset = info['fldofs ' + fieldname] size = info['fldsize ' + fieldname] sign = info.get('fldunsigned ' + fieldname, False) - if (size, sign) != rffi.size_and_sign(fieldtype): + if is_array_nolength(fieldtype): + pass # ignore size and sign + elif (size, sign) != rffi.size_and_sign(fieldtype): fieldtype = fixup_ctype(fieldtype, fieldname, (size, sign)) layout_addfield(layout, offset, fieldtype, fieldname) @@ -353,7 +356,7 @@ class Struct(CConfigEntry): name = name[7:] else: hints['typedef'] = True - kwds = {'hints': hints} + kwds = {'hints': hints, 'adtmeths': self.adtmeths} return rffi.CStruct(name, *fields, **kwds) class SimpleType(CConfigEntry): @@ -682,8 +685,14 @@ class Field(object): def __repr__(self): return '<field %s: %s>' % (self.name, self.ctype) +def is_array_nolength(TYPE): + return isinstance(TYPE, lltype.Array) and TYPE._hints.get('nolength', False) + def layout_addfield(layout, offset, ctype, prefix): - size = _sizeof(ctype) + if is_array_nolength(ctype): + size = len(layout) - offset # all the rest of the struct + else: + size = _sizeof(ctype) name = prefix i = 0 while name in layout: diff --git a/rpython/rtyper/tool/test/test_rffi_platform.py b/rpython/rtyper/tool/test/test_rffi_platform.py index 3945167464..bf7db60286 100644 --- a/rpython/rtyper/tool/test/test_rffi_platform.py +++ b/rpython/rtyper/tool/test/test_rffi_platform.py @@ -270,6 +270,19 @@ def test_array(): [("d_name", lltype.FixedSizeArray(rffi.CHAR, 1))]) assert dirent.c_d_name.length == 32 +def test_array_varsized_struct(): + dirent = rffi_platform.getstruct("struct dirent", + """ + struct dirent /* for this example only, not the exact dirent */ + { + int d_off; + char d_name[1]; + }; + """, + [("d_name", rffi.CArray(rffi.CHAR))]) + assert rffi.offsetof(dirent, 'c_d_name') == 4 + assert dirent.c_d_name == rffi.CArray(rffi.CHAR) + def test_has_0001(): assert rffi_platform.has("x", "int x = 3;") assert not rffi_platform.has("x", "") diff --git a/rpython/translator/c/funcgen.py b/rpython/translator/c/funcgen.py index dc13fcdc10..1015118990 100644 --- a/rpython/translator/c/funcgen.py +++ b/rpython/translator/c/funcgen.py @@ -842,6 +842,12 @@ class FunctionCodeGenerator(object): def OP_JIT_FFI_SAVE_RESULT(self, op): return '/* JIT_FFI_SAVE_RESULT %s */' % op + def OP_JIT_ENTER_PORTAL_FRAME(self, op): + return '/* JIT_ENTER_PORTAL_FRAME %s */' % op + + def OP_JIT_LEAVE_PORTAL_FRAME(self, op): + return '/* JIT_LEAVE_PORTAL_FRAME %s */' % op + def OP_GET_GROUP_MEMBER(self, op): typename = self.db.gettype(op.result.concretetype) return '%s = (%s)_OP_GET_GROUP_MEMBER(%s, %s);' % ( diff --git a/rpython/translator/c/node.py b/rpython/translator/c/node.py index c8bd2a2f05..dd604b86e3 100644 --- a/rpython/translator/c/node.py +++ b/rpython/translator/c/node.py @@ -546,7 +546,7 @@ class StructNode(ContainerNode): if needs_gcheader(T): gct = self.db.gctransformer if gct is not None: - self.gc_init = gct.gcheader_initdata(self) + self.gc_init = gct.gcheader_initdata(self.obj) db.getcontainernode(self.gc_init) else: self.gc_init = None @@ -677,7 +677,7 @@ class ArrayNode(ContainerNode): if needs_gcheader(T): gct = self.db.gctransformer if gct is not None: - self.gc_init = gct.gcheader_initdata(self) + self.gc_init = gct.gcheader_initdata(self.obj) db.getcontainernode(self.gc_init) else: self.gc_init = None @@ -913,6 +913,7 @@ class ExternalFuncNode(FuncNodeBase): return [] def new_funcnode(db, T, obj, forcename=None): + from rpython.rtyper.rtyper import llinterp_backend if db.sandbox: if (getattr(obj, 'external', None) is not None and not obj._safe_not_sandboxed): @@ -934,6 +935,9 @@ def new_funcnode(db, T, obj, forcename=None): return ExternalFuncNode(db, T, obj, name) elif hasattr(obj._callable, "c_name"): return ExternalFuncNode(db, T, obj, name) # this case should only be used for entrypoints + elif db.translator.rtyper.backend is llinterp_backend: + # on llinterp, anything goes + return ExternalFuncNode(db, T, obj, name) else: raise ValueError("don't know how to generate code for %r" % (obj,)) diff --git a/rpython/translator/sandbox/test/test_sandbox.py b/rpython/translator/sandbox/test/test_sandbox.py index 58e70667d8..ed46da595f 100644 --- a/rpython/translator/sandbox/test/test_sandbox.py +++ b/rpython/translator/sandbox/test/test_sandbox.py @@ -292,6 +292,21 @@ def test_unsafe_mmap(): rescode = pipe.wait() assert rescode == 0 +def test_environ_items(): + def entry_point(argv): + print os.environ.items() + return 0 + + exe = compile(entry_point) + g, f = run_in_subprocess(exe) + expect(f, g, "ll_os.ll_os_envitems", (), []) + expect(f, g, "ll_os.ll_os_write", (1, "[]\n"), 3) + g.close() + tail = f.read() + f.close() + assert tail == "" + + class TestPrintedResults: def run(self, entry_point, args, expected): |