Browse Source

Fix bug in MemorySnapshot.shrink_stack. Example 7 from Day & Atlee almost executing correctly.

Joeri Exelmans 5 years ago
parent
commit
4f95b97760

+ 2 - 2
src/sccd/execution/builtin_scope.py

@@ -22,7 +22,7 @@ def _float_to_int(ctx: EvalContext, x: float) -> int:
 
 builtin_scope.add_python_function("float_to_int", _float_to_int)
 
-def _log(ctx: EvalContext, s: str):
+def _log(ctx: EvalContext, s: str) -> None:
   print_debug(termcolor.colored("log: ",'blue')+s)
 
 builtin_scope.add_python_function("log", _log)
@@ -30,4 +30,4 @@ builtin_scope.add_python_function("log", _log)
 def _int_to_str(ctx: EvalContext, i: int) -> str:
   return str(i)
 
-builtin_scope.add_python_function("int_to_str", _int_to_str)
+builtin_scope.add_python_function("int_to_str", _int_to_str)

+ 2 - 1
src/sccd/execution/memory.py

@@ -44,7 +44,8 @@ class MemorySnapshot:
 
   def shrink_stack(self):
     scope = self.scope.pop()
-    del self.stack[-scope.local_size():]
+    if scope.local_size() > 0:
+      del self.stack[-scope.local_size():]
 
   def flush_temp(self):
     assert len(self.stack) == 0 # only allowed to be called in between statement executions or expression evaluations

+ 29 - 27
src/sccd/execution/round.py

@@ -180,43 +180,45 @@ class SmallStep(Round):
         self.concurrency = concurrency
 
     def _internal_run(self, forbidden_arenas: Bitmap) -> RoundResult:
-        enabled_events = self.enabled_events()
-        candidates = self.generator.generate(self.state, enabled_events, forbidden_arenas)
+        enabled_events = None
+        def get_candidates(extra_forbidden):
+            nonlocal enabled_events
+            enabled_events = self.enabled_events()
+            candidates = self.generator.generate(self.state, enabled_events, forbidden_arenas |  extra_forbidden)
+
+            if is_debug():
+                candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
+                if candidates:
+                    print_debug("")
+                    if enabled_events:
+                        print_debug("events: " + str(enabled_events))
+                    print_debug("candidates: " + str(candidates))
+                candidates = iter(candidates)
 
-        if is_debug():
-            candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
-            if candidates:
-                print_debug("")
-                if enabled_events:
-                    print_debug("events: " + str(enabled_events))
-                print_debug("candidates: " + str(candidates))
+            return candidates
 
         arenas = Bitmap()
         stable_arenas = Bitmap()
-        for t in candidates:
+
+        candidates = get_candidates(0)
+        t = next(candidates, None)
+        while t:
             arena = t.gen.arena_bitmap
             if not (arenas & arena):
                 self.state.fire_transition(enabled_events, t)
+                arenas |= arena
+                if t.targets[0].stable:
+                    stable_arenas |= arena
 
-            arenas |= arena
-            if t.targets[0].stable:
-                stable_arenas |= arena
+                if not self.concurrency:
+                    # Return after first transition execution
+                    break
 
-            if not self.concurrency:
-                # Return after first transition execution
-                break
+                # need to re-generate candidates after firing transition
+                # because possibly the set of current events has changed
+                candidates = get_candidates(extra_forbidden=arenas)
 
-            enabled_events = self.enabled_events()
-            candidates = self.generator.generate(self.state, enabled_events, forbidden_arenas)
-
-            if is_debug():
-                candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
-                if candidates:
-                    print_debug("")
-                    if enabled_events:
-                        print_debug("events: " + str(enabled_events))
-                    print_debug("candidates: " + str(candidates))
+            t = next(candidates, None)
 
         return (arenas, stable_arenas)
 
-

+ 1 - 1
src/sccd/execution/statechart_instance.py

@@ -11,7 +11,7 @@ from sccd.execution.memory import *
 
 # Hardcoded limit on number of sub-rounds of combo and big step to detect never-ending superrounds.
 # TODO: make this configurable
-LIMIT = 20
+LIMIT = 1000
 
 class StatechartInstance(Instance):
     def __init__(self, statechart: Statechart, object_manager):

+ 14 - 8
src/sccd/execution/statechart_state.py

@@ -44,6 +44,7 @@ class StatechartState:
             print_debug(termcolor.colored('  ENTER %s'%state.gen.full_name, 'green'))
             self.eventless_states += state.gen.has_eventless_transitions
             self._perform_actions([], state.enter)
+            self.rhs_memory.flush_temp()
             self._start_timers(state.gen.after_triggers)
 
     def fire_transition(self, events, t: Transition):
@@ -77,17 +78,21 @@ class StatechartState:
                 if isinstance(h, DeepHistoryState):
                     f = lambda s0: not s0.gen.descendants and s0 in s.gen.descendants
                 self.history_values[h.gen.state_id] = list(filter(f, self.configuration))
-        # print_debug('')
+
+        ctx = EvalContext(current_state=self, events=events, memory=self.rhs_memory)
+
         print_debug("fire " + str(t))
         for s in exit_set:
             print_debug(termcolor.colored('  EXIT %s' % s.gen.full_name, 'green'))
             self.eventless_states -= s.gen.has_eventless_transitions
             # execute exit action(s)
-            self._perform_actions(events, s.exit)
+            self._perform_actions(ctx, s.exit)
             self.configuration_bitmap &= ~s.gen.state_id_bitmap
                 
         # execute transition action(s)
-        self._perform_actions(events, t.actions)
+        self.rhs_memory.grow_stack(t.scope) # make room for event parameters on stack
+        self._perform_actions(ctx, t.actions)
+        self.rhs_memory.shrink_stack()
             
         # enter states...
         targets = __getEffectiveTargetStates()
@@ -97,13 +102,15 @@ class StatechartState:
             self.eventless_states += s.gen.has_eventless_transitions
             self.configuration_bitmap |= s.gen.state_id_bitmap
             # execute enter action(s)
-            self._perform_actions(events, s.enter)
+            self._perform_actions(ctx, s.enter)
             self._start_timers(s.gen.after_triggers)
         try:
             self.configuration = self.config_mem[self.configuration_bitmap]
         except KeyError:
             self.configuration = self.config_mem[self.configuration_bitmap] = [s for s in self.model.tree.state_list if self.configuration_bitmap & s.gen.state_id_bitmap]
 
+        self.rhs_memory.flush_temp()
+
     def check_guard(self, t, events) -> bool:
         # Special case: after trigger
         if isinstance(t.trigger, AfterTrigger):
@@ -122,18 +129,17 @@ class StatechartState:
     def check_source(self, t) -> bool:
         return self.configuration_bitmap & t.source.gen.state_id_bitmap
 
-    def _perform_actions(self, events, actions: List[Action]):
-        ctx = EvalContext(current_state=self, events=events, memory=self.rhs_memory)
+    @staticmethod
+    def _perform_actions(ctx: EvalContext, actions: List[Action]):
         for a in actions:
             a.exec(ctx)
-        self.rhs_memory.flush_temp()
 
     def _start_timers(self, triggers: List[AfterTrigger]):
         for after in triggers:
             delay: Duration = after.delay.eval(
                 EvalContext(current_state=self, events=[], memory=self.gc_memory))
-            timer_id = self._next_timer_id(after)
             self.gc_memory.flush_temp()
+            timer_id = self._next_timer_id(after)
             self.output.append(OutputEvent(
                 Event(id=after.id, name=after.name, params=[timer_id]),
                 target=InstancesTarget([self.instance]),

+ 13 - 2
src/sccd/syntax/scope.py

@@ -61,7 +61,7 @@ class EventParam(Variable):
       value = e.params[self.param_offset]
       # "cache" the parameter value on our reserved stack position so the next
       # 'load' will be faster
-      Variable.store(self, ctx)
+      Variable.store(self, ctx, value)
       return value
 
   def store(self, ctx: EvalContext, value):
@@ -101,7 +101,7 @@ class Scope:
     self.named_values: Dict[str, Value] = {}
 
     # All non-constant values, ordered by memory position
-    self.variables: List[Value] = []
+    self.variables: List[Variable] = []
 
   def local_size(self) -> int:
     return len(self.variables)
@@ -121,6 +121,17 @@ class Scope:
     else:
       return [self.name]
 
+  def __str__(self):
+    s = "Scope: %s\n" % self.name
+    for v in reversed(self.variables):
+      s += "  %d: %s: %s\n" % (v.offset, v.name, str(v.type))
+    constants = [v for v in self.named_values.values() if v not in self.variables]
+    for c in constants:
+      s += " (constant) %s: %s\n" % (c.name, str(c.type))
+    if self.parent:
+      s += self.parent.__str__()
+    return s
+
   def _internal_lookup(self, name: str) -> Optional[Tuple['Scope', Value]]:
     try:
       return (self, self.named_values[name])

+ 3 - 3
test/lib/test.py

@@ -88,9 +88,9 @@ class Test(unittest.TestCase):
 
           # Sort both expected and actual lists of events before comparing.
           # In theory the set of events at the end of a big step is unordered.
-          key_f = lambda e: "%s.%s"%(e.port, e.name)
-          actual_bag.sort(key=key_f)
-          expected_bag.sort(key=key_f)
+          # key_f = lambda e: "%s.%s"%(e.port, e.name)
+          # actual_bag.sort(key=key_f)
+          # expected_bag.sort(key=key_f)
 
           for (act_event, exp_event) in zip(actual_bag, expected_bag):
             if act_event != exp_event:

+ 12 - 5
test/test_files/day_atlee/statechart_fig1_redialer.xml

@@ -33,7 +33,9 @@
               c += 1;
               lp = lp * 10 + d;
             </code>
-            <raise port="out" event="out(d)"/>
+            <raise port="out" event="out">
+              <param expr="d"/>
+            </raise>
           </transition>
           <!-- t2 -->
           <transition event="dial(d:int), redial" cond="c == 0" target="../DialDigits">
@@ -41,7 +43,9 @@
               lp = d;
               c = 1;
             </code>
-            <raise port="out" event="out(d)"/>
+            <raise port="out" event="out">
+              <param expr="d"/>
+            </raise>
           </transition>
         </state>
         <state id="DialDigits">
@@ -51,7 +55,9 @@
               lp = lp * 10 + d;
               c += 1;
             </code>
-            <raise port="out" event="out(d)"/>
+            <raise port="out" event="out">
+              <param expr="d"/>
+            </raise>
           </transition>
           <!-- t4 -->
           <transition cond="c == 10" target="../WaitForDial"/>
@@ -64,11 +70,12 @@
           <transition event="redial" cond="c == 0" target="../RedialDigits">
             <code>
               p = lp;
-              log("p: " + int_to_str(p));
+              log("p:  " + int_to_str(p));
               log("lp: " + int_to_str(lp));
+              log("c:  " + int_to_str(c));
             </code>
             <raise event="dial">
-              <param expr="digit(lp, 1)"/>
+              <param expr="digit(lp, 0)"/>
             </raise>
           </transition>
         </state>