[pypy-commit] lang-smalltalk storage: Merged.

anton_gulenko noreply at buildbot.pypy.org
Sun Jul 27 12:22:37 CEST 2014


Author: Anton Gulenko <anton.gulenko at 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)
 


More information about the pypy-commit mailing list