[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