Kaynağa Gözat

Fixed bugs in action lang -> Rust code generation

Joeri Exelmans 4 yıl önce
ebeveyn
işleme
9affd9e75e

+ 22 - 32
src/sccd/action_lang/codegen/rust.py

@@ -92,33 +92,26 @@ class ActionLangRustGenerator(Visitor):
 
     def write_parent_params(self, scope, with_identifiers=True):
         ctr = 1
-        while scope is not self.scope.root():
-            if ctr > scope.deepest_lookup:
-                break
+        while scope is not self.scope.root() and ctr <= scope.deepest_lookup:
             if with_identifiers:
                 self.w.wno("parent%d: " % ctr)
             self.w.wno("&mut %s, " % self.scope.type(scope.parent, scope.parent_offset))
             ctr += 1
             scope = scope.parent
 
-    def write_parent_call_params(self, scope, skip=0):
+    def write_parent_call_params(self, scope, skip=1):
         ctr = 0
-        while scope is not self.scope.root():
-            if ctr == skip:
-                break
-            ctr += 1
-            scope = scope.parent
-
-        while scope is not self.scope.root():
-            if ctr > scope.deepest_lookup:
-                break
+        while scope is not self.scope.root() and ctr < scope.deepest_lookup:
             if ctr == skip:
                 self.w.wno("&mut scope, ")
-            else:
-                self.w.wno("&mut parent%d, " % ctr-skip)
+                # self.w.wno("scope, ")
+            elif ctr > skip:
+                # self.w.wno("&mut parent%d, " % (ctr-skip))
+                self.w.wno("parent%d, " % (ctr-skip))
             ctr += 1
             scope = scope.parent
 
+
     # This is not a visit method because Scopes may be encountered whenever there's a function call, but they are written as structs and constructor functions, which can only be written at the module level.
     # When compiling Rust code, the Visitable.accept method must be called on the root of the AST, to write code wherever desired (e.g. in a main-function) followed by 'write_scope' at the module level.
     def write_decls(self):
@@ -131,10 +124,11 @@ class ActionLangRustGenerator(Visitor):
             # self.w.write("fn %s(parent_scope: &mut %s, " % (identifier, self.scope.type(scope.parent, scope.parent_offset)))
             self.w.write("fn %s(" % (identifier))
 
-            self.write_parent_params(scope)
-
             for p in function.params_decl:
                 p.accept(self)
+                self.w.wno(", ")
+
+            self.write_parent_params(scope)
 
             # self.w.wno(") -> (%s," % self.scope.type(scope, scope.size()))
             self.w.wno(") -> ")
@@ -178,7 +172,8 @@ class ActionLangRustGenerator(Visitor):
         self.w.wno(" = ")
         stmt.rhs.accept(self)
         self.w.wnoln(";")
-        self.w.writeln("eprintln!(\"%s\");" % termcolor.colored(stmt.render(),'blue'))
+        if DEBUG:
+            self.w.writeln("eprintln!(\"%s\");" % termcolor.colored(stmt.render(),'blue'))
 
     def visit_IfStatement(self, stmt):
         self.w.write("if ")
@@ -275,21 +270,23 @@ class ActionLangRustGenerator(Visitor):
             expr.function.accept(self) # an Identifier or a FunctionDeclaration (=anonymous function)
             self.w.wno(", ")
 
-            self.write_parent_call_params(expr.function_being_called.scope, skip=1)
         else:
             self.w.wno("(")
             expr.function.accept(self) # an Identifier or a FunctionDeclaration (=anonymous function)
             self.w.wno(")(")
 
-            # Parent scope mut refs
-            self.write_parent_call_params(expr.function_being_called.scope, skip=1)
 
         # Call parameters
         for p in expr.params:
             p.accept(self)
             self.w.wno(", ")
-        self.w.wno(")")
 
+        if isinstance(expr.function.get_type(), SCCDClosureObject):
+            self.write_parent_call_params(expr.function_being_called.scope, skip=1)
+        else:
+            self.write_parent_call_params(expr.function_being_called.scope, skip=0)
+
+        self.w.wno(")")
         # if not isinstance(expr.get_type(), SCCDClosureObject):
         #     # In our generated code, a function always returns a pair of
         #     #   0) the called function's scope
@@ -321,33 +318,26 @@ class ActionLangRustGenerator(Visitor):
         self.w.wno(lval.name)
 
     def visit_SCCDClosureObject(self, type):
-        # self.w.wno("<closure type> ")
-
         self.w.wno("(%s, " % self.scope.type(type.scope, type.scope.size()))
         type.function_type.accept(self)
         self.w.wno(")")
 
     def write_return_type(self, function: FunctionDeclaration):
-        # self.w.wno("(%s, " % self.scope.type(function.scope, function.scope.size()))
-        # type.function_type.accept(self)
         if function.return_type is None:
-            self.w.wno("Empty")
+            self.w.wno("()")
         else:
             function.return_type.accept(self)
-        # self.w.wno(")")
 
     def visit_SCCDFunction(self, type):
-        # self.w.wno("<function type> ")
         scope = type.function.scope
-        # self.w.wno("fn(&mut %s, " % (self.scope.type(scope.parent, scope.parent_offset)))
         self.w.wno("fn(")
 
-        self.write_parent_params(scope, with_identifiers=False)
-
         for p in type.param_types:
             p.accept(self)
             self.w.wno(", ")
 
+        self.write_parent_params(scope, with_identifiers=False)
+
         self.w.wno(") -> ")
         self.write_return_type(type.function)
 

+ 4 - 1
src/sccd/action_lang/static/scope.py

@@ -106,7 +106,10 @@ class Scope(Visitable):
     if offset >= 0:
       return 0
     else:
-      return 1 + self.parent.nested_levels(offset + self.parent_offset)
+      if self.parent is not None:
+        return 1 + self.parent.nested_levels(offset + self.parent_offset)
+      else:
+        return 0
 
   # Create name in this scope
   # Precondition: _internal_lookup of name returns 'None'

+ 6 - 0
src/sccd/statechart/codegen/code_generation.txt

@@ -130,3 +130,9 @@ binary size, no opt:                            478976 bytes
 binary size, opt-level=3 inline-threshold=1000: 410880 bytes
 binary size, opt-level=z:                       413576 bytes
 instruction count (perf stat) (opt-level=3):    580.701 instructions:u
+
+
+Performance insights
+
+  - instruction count improved a little over time (but could be due to non-relevant changes such as debug output)
+  - binary size slightly worse when optimizing for size (opt-level=z). this means that inlining is actually reducing the binaries' size, as we would expect (lots of dead code can be eliminated)

+ 3 - 1
src/sccd/statechart/codegen/libstatechart.rs

@@ -155,7 +155,7 @@ Controller<EventType, OutputCallback> {
         }
         // OK, handle event
         self.simtime = entry.id.timestamp;
-        println!("time is now {}", self.simtime);
+        // eprintln!("time is now {}", self.simtime);
         sc.big_step(Some(entry.event), self);
         self.queue.pop();
       }
@@ -174,6 +174,7 @@ use std::ops::DerefMut;
 // traits are implemented to return a reference to the base struct
 macro_rules! inherit_struct {
     ($name: ident ($base: ty) { $($element: ident: $ty: ty),* $(,)? } ) => {
+        #[derive(Copy, Clone)]
         struct $name {
             _base: $base,
             $($element: $ty),*
@@ -193,6 +194,7 @@ macro_rules! inherit_struct {
 }
 
 // "Base struct" for all scopes
+#[derive(Copy, Clone)]
 struct Empty{}
 
 // A closure object is a pair of a functions first argument and that function.

+ 12 - 5
src/sccd/statechart/codegen/rust.py

@@ -119,7 +119,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
         # Enter actions: Executes enter actions of only this state
         self.w.writeln("  fn enter_actions<OutputCallback: FnMut(OutEvent)>(timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, ctrl: &mut Controller<InEvent, OutputCallback>) {")
-        self.w.writeln("    eprintln!(\"enter %s\");" % state.full_name);
+        if DEBUG:
+            self.w.writeln("    eprintln!(\"enter %s\");" % state.full_name);
         self.w.writeln("    let scope = data;")
         self.w.indent(); self.w.indent()
         for a in state.enter:
@@ -137,7 +138,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.indent(); self.w.indent()
         for a in state.exit:
             a.accept(self)
-        self.w.writeln("    eprintln!(\"exit %s\");" % state.full_name);
+        if DEBUG:
+            self.w.writeln("    eprintln!(\"exit %s\");" % state.full_name);
         self.w.dedent(); self.w.dedent()
         self.w.writeln("  }")
 
@@ -469,6 +471,9 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
                     self.w.writeln("let parent1 = &mut scope;")
 
+                    if t.scope.size() > 0:
+                        raise UnsupportedFeature("Event parameters")
+
                     if t.guard is not None:
                         self.w.write("if ")
                         t.guard.accept(self)
@@ -485,7 +490,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                     enter_path_bm = t.arena.descendants & (t.target.state_id_bitmap | t.target.ancestors) # bitmap
                     enter_path = list(tree.bitmap_to_states(enter_path_bm)) # list of states
 
-                    self.w.writeln("eprintln!(\"fire %s\");" % str(t))
+                    if DEBUG:
+                        self.w.writeln("eprintln!(\"fire %s\");" % str(t))
 
                     self.w.writeln("// Exit actions")
                     write_exit(exit_path)
@@ -653,9 +659,10 @@ class StatechartRustGenerator(ActionLangRustGenerator):
             self.w.writeln("fn debug_print_sizes() {")
             self.w.writeln("  eprintln!(\"------------------------\");")
             self.w.writeln("  eprintln!(\"info: Statechart: {} bytes\", size_of::<Statechart>());")
-            self.w.writeln("  eprintln!(\"info: Timers: {} bytes\", size_of::<Timers>());")
+            self.w.writeln("  eprintln!(\"info:   DataModel: {} bytes\", size_of::<DataModel>());")
+            self.w.writeln("  eprintln!(\"info:   Timers: {} bytes\", size_of::<Timers>());")
             def write_state_size(state):
-                self.w.writeln("  eprintln!(\"info: State %s: {} bytes\", size_of::<%s>());" % (state.full_name, ident_type(state)))
+                self.w.writeln("  eprintln!(\"info:   State %s: {} bytes\", size_of::<%s>());" % (state.full_name, ident_type(state)))
                 for child in state.real_children:
                     write_state_size(child)
             write_state_size(tree.root)

+ 3 - 1
src/sccd/test/codegen/rust.py

@@ -20,6 +20,7 @@ def compile_test(variants: List[TestVariant], w: IndentingWriter):
     w.writeln("#![allow(unused_variables)]")
     w.writeln("#![allow(dead_code)]")
     w.writeln("#![allow(unused_parens)]")
+    w.writeln("#![allow(unused_macros)]")
 
     with open(rustlib, 'r') as file:
         data = file.read()
@@ -41,7 +42,8 @@ def compile_test(variants: List[TestVariant], w: IndentingWriter):
         w.writeln("// Test variant %d" % n)
         w.writeln("let mut raised = Vec::<OutEvent>::new();")
         w.writeln("let mut output = |out: OutEvent| {")
-        w.writeln("  eprintln!(\"^{}:{}\", out.port, out.event);")
+        if DEBUG:
+            w.writeln("  eprintln!(\"^{}:{}\", out.port, out.event);")
         w.writeln("  raised.push(out);")
         w.writeln("};")
         w.writeln("let mut controller = Controller::<InEvent,_>::new(&mut output);")