Author: Anton Gulenko <anton.gule...@googlemail.com> Branch: storage Changeset: r960:3c59d53022d4 Date: 2014-07-27 11:24 +0200 http://bitbucket.org/pypy/lang-smalltalk/changeset/3c59d53022d4/
Log: Merged. diff --git a/spyvm/interpreter.py b/spyvm/interpreter.py --- a/spyvm/interpreter.py +++ b/spyvm/interpreter.py @@ -1,7 +1,7 @@ import os -from spyvm.shadow import MethodContextShadow -from spyvm import model, constants, wrapper, objspace, interpreter_bytecodes +from spyvm.shadow import MethodContextShadow, ActiveContext, InactiveContext, DirtyContext +from spyvm import model, constants, wrapper, objspace, interpreter_bytecodes, error from rpython.rlib import jit, rstackovf, unroll @@ -11,23 +11,34 @@ self.object = object class Return(Exception): - _attrs_ = ["value", "s_target_context", "is_local"] + _attrs_ = ["value", "s_target_context", "is_local", "arrived_at_target"] + _immutable_attrs_ = ["value", "s_target_context", "is_local"] def __init__(self, s_target_context, w_result): self.value = w_result self.s_target_context = s_target_context - self.is_local = False + self.arrived_at_target = False + self.is_local = s_target_context is None + +class NonVirtualReturn(Exception): + _attrs_ = ["s_target_context", "s_current_context", "value"] + def __init__(self, s_target_context, s_current_context, w_result): + self.value = w_result + self.s_target_context = s_target_context + self.s_current_context = s_current_context + + def print_trace(self): + print "\n====== Sender Chain Manipulation, contexts forced to heap at: %s" % self.s_current_context.short_str() class ContextSwitchException(Exception): """General Exception that causes the interpreter to leave the current context.""" - _attrs_ = ["s_new_context"] type = "ContextSwitch" def __init__(self, s_new_context): self.s_new_context = s_new_context - def print_trace(self, old_context): - print "====== %s, contexts forced to heap at: %s" % (self.type, self.s_new_context.short_str()) + def print_trace(self): + print "\n====== %s at: %s" % (self.type, self.s_new_context.short_str()) class StackOverflow(ContextSwitchException): """This causes the current jit-loop to be left, dumping all virtualized objects to the heap. @@ -38,17 +49,8 @@ class ProcessSwitch(ContextSwitchException): """This causes the interpreter to switch the executed context. Triggered when switching the process.""" + type = "Process Switch" - def print_trace(self, old_context): - print "====== Switched process from: %s" % old_context.short_str() - print "====== to: %s " % self.s_new_context.short_str() - -class SenderChainManipulation(ContextSwitchException): - """Manipulation of the sender chain can invalidate the jitted C stack. - We have to dump all virtual objects and rebuild the stack. - We try to raise this as rarely as possible and as late as possible.""" - type = "Sender Manipulation" - UNROLLING_BYTECODE_RANGES = unroll.unrolling_iterable(interpreter_bytecodes.BYTECODE_RANGES) def get_printable_location(pc, self, method): @@ -94,25 +96,54 @@ def loop(self, w_active_context): # This is the top-level loop and is not invoked recursively. - s_new_context = w_active_context.as_context_get_shadow(self.space) + s_context = w_active_context.as_context_get_shadow(self.space) while True: - s_sender = s_new_context.s_sender() + s_sender = s_context.s_sender() try: - self.loop_bytecodes(s_new_context) + self.stack_frame(s_context, None) raise Exception("loop_bytecodes left without raising...") except ContextSwitchException, e: - if self.is_tracing() or self.trace_important: - e.print_trace(s_new_context) - s_new_context = e.s_new_context - except Return, nlr: - assert nlr.s_target_context or nlr.is_local - s_new_context = s_sender - if not nlr.is_local: - while s_new_context is not nlr.s_target_context: - s_sender = s_new_context.s_sender() - s_new_context._activate_unwind_context(self) - s_new_context = s_sender - s_new_context.push(nlr.value) + if self.is_tracing(): + e.print_trace() + s_context = e.s_new_context + except Return, ret: + target = s_sender if ret.arrived_at_target else ret.s_target_context + s_context = self.unwind_context_chain(s_sender, target, ret.value) + except NonVirtualReturn, ret: + if self.is_tracing(): + ret.print_trace() + s_context = self.unwind_context_chain(ret.s_current_context, ret.s_target_context, ret.value) + + # This is a wrapper around loop_bytecodes that cleanly enters/leaves the frame, + # handles the stack overflow protection mechanism and handles/dispatches Returns. + def stack_frame(self, s_frame, s_sender, may_context_switch=True): + try: + if self.is_tracing(): + self.stack_depth += 1 + if s_frame._s_sender is None and s_sender is not None: + s_frame.store_s_sender(s_sender) + # Now (continue to) execute the context bytecodes + assert s_frame.state is InactiveContext + s_frame.state = ActiveContext + self.loop_bytecodes(s_frame, may_context_switch) + except rstackovf.StackOverflow: + rstackovf.check_stack_overflow() + raise StackOverflow(s_frame) + except Return, ret: + if s_frame.state is DirtyContext: + s_sender = s_frame.s_sender() # The sender has changed! + s_frame._activate_unwind_context(self) + target_context = s_sender if ret.is_local else ret.s_target_context + raise NonVirtualReturn(target_context, s_sender, ret.value) + else: + s_frame._activate_unwind_context(self) + if ret.is_local or ret.s_target_context is s_sender: + ret.arrived_at_target = True + raise ret + finally: + if self.is_tracing(): + self.stack_depth -= 1 + s_frame.state = InactiveContext def loop_bytecodes(self, s_context, may_context_switch=True): old_pc = 0 @@ -134,34 +165,33 @@ s_context=s_context) try: self.step(s_context) - except Return, nlr: - if nlr.s_target_context is s_context or nlr.is_local: - s_context.push(nlr.value) + except Return, ret: + if ret.arrived_at_target: + s_context.push(ret.value) else: - if nlr.s_target_context is None: - # This is the case where we are returning to our sender. - # Mark the return as local, so our sender will take it - nlr.is_local = True - s_context._activate_unwind_context(self) - raise nlr - - # This is a wrapper around loop_bytecodes that cleanly enters/leaves the frame - # and handles the stack overflow protection mechanism. - def stack_frame(self, s_frame, s_sender, may_context_switch=True): - try: - if self.is_tracing(): - self.stack_depth += 1 - if s_frame._s_sender is None and s_sender is not None: - s_frame.store_s_sender(s_sender, raise_error=False) - # Now (continue to) execute the context bytecodes - self.loop_bytecodes(s_frame, may_context_switch) - except rstackovf.StackOverflow: - rstackovf.check_stack_overflow() - raise StackOverflow(s_frame) - finally: - if self.is_tracing(): - self.stack_depth -= 1 - + raise ret + + def unwind_context_chain(self, start_context, target_context, return_value): + if start_context is None: + # This is the toplevel frame. Execution ended. + raise ReturnFromTopLevel(return_value) + assert target_context + context = start_context + while context is not target_context: + if not context: + msg = "Context chain ended while trying to return\n%s\nfrom\n%s\n(pc %s)\nto\n%s\n(pc %s)" % ( + return_value.as_repr_string(), + start_context.short_str(), + start_context.pc(), + target_context.short_str(), + start_context.pc()) + raise error.FatalError(msg) + s_sender = context.s_sender() + context._activate_unwind_context(self) + context = s_sender + context.push(return_value) + return context + def step(self, context): bytecode = context.fetch_next_bytecode() for entry in UNROLLING_BYTECODE_RANGES: diff --git a/spyvm/interpreter_bytecodes.py b/spyvm/interpreter_bytecodes.py --- a/spyvm/interpreter_bytecodes.py +++ b/spyvm/interpreter_bytecodes.py @@ -30,7 +30,8 @@ parameters += (self.fetch_next_bytecode(), ) i = i + 1 # This is a good place to step through bytecodes. - self.debug_bytecode() + + self.debug_bytecode(interp) return actual_implementation_method(self, interp, current_bytecode, *parameters) bytecode_implementation_wrapper.func_name = actual_implementation_method.func_name return bytecode_implementation_wrapper @@ -118,7 +119,7 @@ def pushLiteralVariableBytecode(self, interp, current_bytecode): # this bytecode assumes that literals[index] is an Association # which is an object with two named vars, and fetches the second - # named var (the value). + # named var (the value). index = current_bytecode & 31 w_association = self.w_method().getliteral(index) association = wrapper.AssociationWrapper(self.space, w_association) @@ -337,8 +338,6 @@ # ###################################################################### if interp.is_tracing(): interp.print_padded('-> %s %s' % (special_selector, s_frame.short_str())) - if not objectmodel.we_are_translated(): - import pdb; pdb.set_trace() return interp.stack_frame(s_frame, self) @@ -394,18 +393,12 @@ # it will find the sender as a local, and we don't have to # force the reference s_return_to = None - return_from_top = self.s_sender() is None else: s_return_to = self.s_home().s_sender() - return_from_top = s_return_to is None + assert s_return_to, "No sender to return to!" - if return_from_top: - # This should never happen while executing a normal image. - from spyvm.interpreter import ReturnFromTopLevel - raise ReturnFromTopLevel(return_value) - else: - from spyvm.interpreter import Return - raise Return(s_return_to, return_value) + from spyvm.interpreter import Return + raise Return(s_return_to, return_value) # ====== Send/Return bytecodes ====== @@ -450,7 +443,6 @@ @bytecode_implementation(parameter_bytes=2) def doubleExtendedDoAnythingBytecode(self, interp, current_bytecode, second, third): - from spyvm.interpreter import SenderChainManipulation opType = second >> 5 if opType == 0: # selfsend @@ -472,16 +464,9 @@ association = wrapper.AssociationWrapper(self.space, w_association) self.push(association.value()) elif opType == 5: - # TODO - the following two special cases should not be necessary - try: - self.w_receiver().store(self.space, third, self.top()) - except SenderChainManipulation, e: - raise SenderChainManipulation(self) + self.w_receiver().store(self.space, third, self.top()) elif opType == 6: - try: - self.w_receiver().store(self.space, third, self.pop()) - except SenderChainManipulation, e: - raise SenderChainManipulation(self) + self.w_receiver().store(self.space, third, self.pop()) elif opType == 7: w_association = self.w_method().getliteral(third) association = wrapper.AssociationWrapper(self.space, w_association) @@ -511,13 +496,13 @@ from spyvm.interpreter import Return try: self.bytecodePrimValue(interp, 0) - except Return, nlr: - assert nlr.s_target_context or nlr.is_local - if self is not nlr.s_target_context and not nlr.is_local: - raise nlr + except Return, ret: + # Local return value of ensure: block is ignored + if not ret.arrived_at_target: + raise ret finally: self.mark_returned() - + @bytecode_implementation() def unknownBytecode(self, interp, current_bytecode): raise error.MissingBytecode("unknownBytecode") @@ -613,7 +598,7 @@ bytecodePrimPointX = make_send_selector_bytecode("x", 0) bytecodePrimPointY = make_send_selector_bytecode("y", 0) - def debug_bytecode(self): + def debug_bytecode(self, interp): # Hook used in interpreter_debugging pass diff --git a/spyvm/interpreter_debugging.py b/spyvm/interpreter_debugging.py --- a/spyvm/interpreter_debugging.py +++ b/spyvm/interpreter_debugging.py @@ -49,8 +49,8 @@ @patch_context def debug_bytecode(original): - def meth(self): - if self.step_bytecodes: + def meth(self, interp): + if interp.step_bytecodes: _break() # Continue stepping from here to get to the current bytecode execution return meth diff --git a/spyvm/shadow.py b/spyvm/shadow.py --- a/spyvm/shadow.py +++ b/spyvm/shadow.py @@ -633,18 +633,29 @@ def size(self): return self._w_self_size +class ContextState(object): + def __init__(self, name): + self.name = name + def __str__(self): + return self.name + def __repr__(self): + return self.name +InactiveContext = ContextState("InactiveContext") +ActiveContext = ContextState("ActiveContext") +DirtyContext = ContextState("DirtyContext") + class ContextPartShadow(AbstractRedirectingShadow): __metaclass__ = extendabletype _attrs_ = ['_s_sender', '_pc', '_temps_and_stack', - '_stack_ptr', 'instances_w'] + '_stack_ptr', 'instances_w', 'state'] repr_classname = "ContextPartShadow" _virtualizable_ = [ '_s_sender', "_pc", "_temps_and_stack[*]", "_stack_ptr", - "_w_self", "_w_self_size" + "_w_self", "_w_self_size", 'state' ] # ______________________________________________________________________ @@ -654,13 +665,7 @@ self._s_sender = None AbstractRedirectingShadow.__init__(self, space, w_self, size) self.instances_w = {} - - def copy_field_from(self, n0, other_shadow): - from spyvm.interpreter import SenderChainManipulation - try: - AbstractRedirectingShadow.copy_field_from(self, n0, other_shadow) - except SenderChainManipulation, e: - assert e.s_new_context == self + self.state = InactiveContext def copy_from(self, other_shadow): # Some fields have to be initialized before the rest, to ensure correct initialization. @@ -702,7 +707,7 @@ if n0 == constants.CTXPART_SENDER_INDEX: assert isinstance(w_value, model.W_PointersObject) if w_value.is_nil(self.space): - self.store_s_sender(None, raise_error=False) + self.store_s_sender(None) else: self.store_s_sender(w_value.as_context_get_shadow(self.space)) return @@ -722,12 +727,12 @@ # === Sender === - def store_s_sender(self, s_sender, raise_error=True): + def store_s_sender(self, s_sender): if s_sender is not self._s_sender: self._s_sender = s_sender - if raise_error: - from spyvm.interpreter import SenderChainManipulation - raise SenderChainManipulation(self) + # If new sender is None, we are just being marked as returned. + if s_sender is not None and self.state is ActiveContext: + self.state = DirtyContext def w_sender(self): sender = self.s_sender() @@ -819,7 +824,7 @@ def mark_returned(self): self.store_pc(-1) - self.store_s_sender(None, raise_error=False) + self.store_s_sender(None) def is_returned(self): return self.pc() == -1 and self.w_sender().is_nil(self.space) diff --git a/spyvm/test/test_interpreter.py b/spyvm/test/test_interpreter.py --- a/spyvm/test/test_interpreter.py +++ b/spyvm/test/test_interpreter.py @@ -978,9 +978,34 @@ 2, "value:value:"]], test) -def test_c_stack_reset_on_sender_chain_manipulation(): +def test_frame_dirty_if_active(): bytes = reduce(operator.add, map(chr, [0x84, 0xc0, 0x00])) w_frame, s_frame = new_frame(bytes) s_frame.store_w_receiver(w_frame) s_frame.push(w_frame) - py.test.raises(interpreter.SenderChainManipulation, step_in_interp, s_frame) + s_frame.state = shadow.ActiveContext + step_in_interp(s_frame) + assert s_frame.state is shadow.DirtyContext + +def test_frame_not_dirty_if_inactive(): + bytes = reduce(operator.add, map(chr, [0x84, 0xc0, 0x00])) + w_frame, s_frame = new_frame(bytes) + w_other_frame, s_other_frame = new_frame("") + s_frame.store_w_receiver(w_other_frame) + s_frame.push(w_frame) + s_frame.state = shadow.ActiveContext + step_in_interp(s_frame) + assert s_frame.state is shadow.ActiveContext + assert s_other_frame.state is shadow.InactiveContext + +def test_raise_NonVirtualReturn_on_dirty_frame(): + bytes = reduce(operator.add, map(chr, [0x84, 0xc0, 0x00])) + returnTopFromMethodBytecode + w_frame, s_frame = new_frame(bytes) + s_frame.store_w_receiver(w_frame) + s_frame.push(w_frame) + + interp._loop = True + def do_test(): + interp.stack_frame(s_frame, None) + py.test.raises(interpreter.NonVirtualReturn, do_test) + \ No newline at end of file diff --git a/spyvm/test/test_zin_squeak_4_5_image.py b/spyvm/test/test_zin_squeak_4_5_image.py --- a/spyvm/test/test_zin_squeak_4_5_image.py +++ b/spyvm/test/test_zin_squeak_4_5_image.py @@ -1,6 +1,8 @@ from spyvm import squeakimage, model, constants, interpreter, shadow, objspace from .util import read_image, find_symbol_in_methoddict_of, copy_to_module, cleanup_module +import operator + def setup_module(): space, interp, image, reader = read_image('Squeak4.5-12568.image') w = space.w @@ -25,56 +27,156 @@ w_method.setliterals(literals) return w_method -def test_ensure(): - #ensure - # [^'b1'] ensure: [^'b2'] - import operator - bytes = reduce(operator.add, map(chr, [0x8F, 0, 0, 2, 0x21, 0x7c, - 0x8F, 0, 0, 2, 0x22, 0x7c, - 0xe0, 0x87, 0x78])) - - s_class = space.w_BlockClosure.as_class_get_shadow(space) - ensure_ = find_symbol_in_methoddict_of('ensure:', s_class) - assert ensure_ is not None, 'Using image without #ensure:-method.' - - w_method = create_method(bytes, [ensure_, w('b1'), w('b2'), - w('ensure'), space.w_BlockClosure]) - +def find_symbol(w_class, symbolname): + s_class = w_class.as_class_get_shadow(space) + symbol = find_symbol_in_methoddict_of(symbolname, s_class) + assert symbol is not None, 'Using image without %s method.' % symbolname + return symbol + +def execute_frame(w_method): # create a frame for our newly crafted method with a valid sender (to avoid raising returnFromTop to early) s_initial_frame = create_method(chr(0x7c)).create_frame(space, w(0), []) s_frame = w_method.create_frame(space, w(0)) - s_frame.store_s_sender(s_initial_frame, raise_error=False) + s_frame.store_s_sender(s_initial_frame) try: interp.loop(s_frame.w_self()) except interpreter.ReturnFromTopLevel, e: - assert e.object.as_string() == 'b2' - except interpreter.StackOverflow, e: - assert False + return e.object + +def test_ensure(): + #ensure + # [^'b1'] ensure: [^'b2'] + + ensure_ = find_symbol(space.w_BlockClosure, "ensure:") + bytes = reduce(operator.add, map(chr, [0x8F, 0, 0, 2, 0x21, 0x7c, + 0x8F, 0, 0, 2, 0x22, 0x7c, + 0xe0, 0x87, 0x78])) + + w_method = create_method(bytes, [ensure_, w('b1'), w('b2'), + w('ensure'), space.w_BlockClosure]) + result = execute_frame(w_method) + assert result.as_string() == 'b2' def test_ensure_save_original_nlr(): #ensure # [^'b1'] ensure: ['b2'] - import operator + + ensure_ = find_symbol(space.w_BlockClosure, "ensure:") bytes = reduce(operator.add, map(chr, [0x8F, 0, 0, 2, 0x21, 0x7c, 0x8F, 0, 0, 2, 0x22, 0x7d, 0xe0, 0x87, 0x78])) - s_class = space.w_BlockClosure.as_class_get_shadow(space) - ensure_ = find_symbol_in_methoddict_of('ensure:', s_class) - assert ensure_ is not None, 'Using image without #ensure:-method.' - w_method = create_method(bytes, [ensure_, w('b1'), w('b2'), w('ensure'), space.w_BlockClosure]) + result = execute_frame(w_method) + assert result.as_string() == 'b1' - # create a frame for our newly crafted method with a valid sender (to avoid raising returnFromTop to early) - s_initial_frame = create_method(chr(0x7c)).create_frame(space, w(0)) - s_frame = w_method.create_frame(space, w(0)) - s_frame.store_s_sender(s_initial_frame, raise_error=False) +def test_ContextPart_jump(): + """ + Code: Create a Block context that jumps back to its sender, instead of returning normally. + The Block is not executed to the end, the sender chain is manipulated. + The local variable should be the value pushed on the sender context before jumping to it. + a := 5. + a := [ thisContext sender push: 2. thisContext sender jump. 10 ] value. + ^ a + """ + ContextPart = space.w_MethodContext.as_class_get_shadow(space).s_superclass().w_self() + push = find_symbol(ContextPart, "push:") + sender = find_symbol(ContextPart, "sender") + jump = find_symbol(ContextPart, "jump") - try: - interp.loop(s_frame.w_self()) - except interpreter.ReturnFromTopLevel, e: - assert e.object.as_string() == 'b1' - except interpreter.StackOverflow, e: - assert False + bytes = reduce(operator.add, map(chr, [0x21, 0x82, 0xc0, # Set a + 0x8f, 0x00, 0x00, 0x0b, # Push block + 0x89, 0xd3, # Send sender + 0x77, 0xe2, # Send push + 0x87, 0x89, 0xd3, 0xd4, # Send jump + 0x87, 0x25, 0x7d, # Block rest (not executed) + 0xc9, 0x82, 0xc0, 0x40, 0x7c])) # Send value and return + + Association = space.classtable["w_Point"] # Wrong class, doesn't matter. + assoc = model.W_PointersObject(space, Association, 2) + assoc.store(space, 0, w('a')) + assoc.store(space, 1, w(3)) + w_method = create_method(bytes, [assoc, w(5), push, sender, jump, w(10)]) + result = execute_frame(w_method) + assert isinstance(result, model.W_SmallInteger) + assert result.value == 2 + +def test_ContextPart_jump_nonlocal(): + """ + Like above test, but with three blocks to make the return non-local. + Also, store the outer context beforehand. + a := 5. + outer := thisContext. + a := [[[ outer push: 2. outer jump. 10 ] value ] value] value. + ^ a + """ + ContextPart = space.w_MethodContext.as_class_get_shadow(space).s_superclass().w_self() + push = find_symbol(ContextPart, "push:") + jump = find_symbol(ContextPart, "jump") + + bytes = reduce(operator.add, map(chr, [0x21, 0x82, 0xc0, # Set a + 0x89, 0x82, 0xc2, # Set outer + 0x8f, 0x00, 0x00, 0x15, # Push block + 0x8f, 0x00, 0x00, 0x0f, # Push block + 0x8f, 0x00, 0x00, 0x09, # Push block + 0x42, 0x77, 0xe3, # Push 2 + 0x87, 0x42, 0xd4, # Send jump + 0x87, 0x25, 0x7d, # Block rest (not executed) + 0xc9, 0x7d, # Send value and return + 0xc9, 0x7d, # Send value and return + 0xc9, 0x82, 0xc0, 0x40, 0x7c])) # Send value and return + + Association = space.classtable["w_Point"] # Wrong class, doesn't matter. + assoc = model.W_PointersObject(space, Association, 2) + assoc.store(space, 0, w('a')) + assoc.store(space, 1, space.w_nil) + assoc2 = model.W_PointersObject(space, Association, 2) + assoc2.store(space, 0, w('outer')) + assoc2.store(space, 1, space.w_nil) + w_method = create_method(bytes, [assoc, w(5), assoc2, push, jump, w(10)]) + result = execute_frame(w_method) + assert isinstance(result, model.W_SmallInteger) + assert result.value == 2 + +def test_contextOn_do_(): + """ + contextOn:do: is some very heavy meta programming. It creates and returns a separate stack frame, + settings it's sender to nil, thereby manipulating the senders of two contexts. + The Point in there should actually be UnhandledError or something. + The test here is just that this works. + ctx := ContextPart contextOn: Point do: ['nothing'] + """ + ContextPart = space.w_MethodContext.as_class_get_shadow(space).s_superclass().w_self() + ContextPartClass = ContextPart.getclass(space).as_class_get_shadow(space).w_self() + contextOnDo = find_symbol(ContextPartClass, "contextOn:do:") + + bytes = reduce(operator.add, map(chr, [ + 0x42, 0x43, # Push the classes + 0x8f, 0x00, 0x00, 0x02, # Push block, + 0x24, 0x7d, # in the block + 0xf1, 0x81, 0xc0, 0x7c # Send contextOn:do: + ])) + + Association = space.classtable["w_Point"] # Wrong class, doesn't matter. + ctxAssoc = model.W_PointersObject(space, Association, 2) + ctxAssoc.store(space, 0, w('ctx')) + ctxAssoc.store(space, 1, space.w_nil) + contextPartAssoc = model.W_PointersObject(space, Association, 2) + contextPartAssoc.store(space, 0, w('ContextPart')) + contextPartAssoc.store(space, 1, ContextPart) + errorAssoc = model.W_PointersObject(space, Association, 2) + errorAssoc.store(space, 0, w('Point')) + errorAssoc.store(space, 1, Association) + w_method = create_method(bytes, [ctxAssoc, contextOnDo, contextPartAssoc, errorAssoc, w('nothing')]) + + interp.trace = True + + result = execute_frame(w_method) + assert isinstance(result, model.W_PointersObject) + s = result.as_context_get_shadow(space) + assert s.w_method().lookup_selector == "on:do:" + assert s.w_method().primitive() == 199 + assert s.s_sender() == None + \ No newline at end of file diff --git a/spyvm/test/util.py b/spyvm/test/util.py --- a/spyvm/test/util.py +++ b/spyvm/test/util.py @@ -85,7 +85,7 @@ if not self._loop: # this test is done to not loop in test, but rather step just once where wanted # Unfortunately, we have to mimick some of the original behaviour. - s_new_frame.store_s_sender(s_sender, raise_error=False) + s_new_frame.store_s_sender(s_sender) return s_new_frame return interpreter.Interpreter.stack_frame(self, s_new_frame, s_sender, may_context_switch) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit