Quellcode durchsuchen

Made IndentingWriter smarter

Joeri Exelmans vor 4 Jahren
Ursprung
Commit
c20f0c6633

+ 0 - 1
.gitignore

@@ -18,4 +18,3 @@ src/MANIFEST
 __pycache__/
 .mypy_cache/
 *.smcat
-codegen/

+ 47 - 51
src/sccd/action_lang/codegen/rust.py

@@ -80,13 +80,13 @@ class ScopeHelper():
 
     def write_rvalue(self, name, offset, writer):
         if offset < 0:
-            writer.wno("parent%d." % self.current().scope.nested_levels(offset))
-            writer.wno(ident_local(name))
+            writer.write("parent%d." % self.current().scope.nested_levels(offset))
+            writer.write(ident_local(name))
         elif offset < self.current().committed:
-            writer.wno("scope.")
-            writer.wno(ident_local(name))
+            writer.write("scope.")
+            writer.write(ident_local(name))
         else:
-            writer.wno(ident_local(name))
+            writer.write(ident_local(name))
 
 
 class ActionLangRustGenerator(Visitor):
@@ -96,7 +96,7 @@ class ActionLangRustGenerator(Visitor):
         self.functions_to_write = [] # Function and Rust identifier
 
     def default(self, what):
-        # self.w.wno("<%s>" % what)
+        # self.w.write("<%s>" % what)
         raise UnsupportedFeature(what)
 
     def debug_print_stack(self):
@@ -117,7 +117,7 @@ class ActionLangRustGenerator(Visitor):
             ctr += 1
             scope = scope.parent
 
-        self.w.wno(", ".join(reversed(args)))
+        self.w.write(", ".join(reversed(args)))
 
     def write_parent_call_params(self, scope, skip: int = 0):
         args = []
@@ -130,7 +130,7 @@ class ActionLangRustGenerator(Visitor):
             ctr += 1
             scope = scope.parent
 
-        self.w.wno(", ".join(reversed(args)))
+        self.w.write(", ".join(reversed(args)))
 
     # 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.
@@ -144,15 +144,15 @@ class ActionLangRustGenerator(Visitor):
 
             for p in function.params_decl:
                 p.accept(self)
-                self.w.wno(", ")
+                self.w.write(", ")
 
             self.write_parent_params(scope)
 
-            self.w.wno(") -> ")
+            self.w.write(") -> ")
 
             self.write_return_type(function)
 
-            self.w.wnoln(" {")
+            self.w.writeln(" {")
             self.w.indent()
             self.w.writeln("let scope = action_lang::Empty{};")
 
@@ -178,7 +178,7 @@ class ActionLangRustGenerator(Visitor):
                 for v in scope.variables[commit.start: commit.end]:
                     self.w.write("  %s: " % ident_local(v.name))
                     v.type.accept(self)
-                    self.w.wnoln(",")
+                    self.w.writeln(",")
                 self.w.writeln("}")
                 self.w.dedent()
                 self.w.writeln("}")
@@ -189,18 +189,17 @@ class ActionLangRustGenerator(Visitor):
             s.accept(self)
 
     def visit_Assignment(self, stmt):
-        #self.w.write('') # indent
         stmt.lhs.accept(self)
-        self.w.wno(" = ")
+        self.w.write(" = ")
         stmt.rhs.accept(self)
-        self.w.wnoln(";")
+        self.w.writeln(";")
         if DEBUG:
             self.w.writeln("eprintln!(\"%s\");" % termcolor.colored(stmt.render(),'blue'))
 
     def visit_IfStatement(self, stmt):
         self.w.write("if ")
         stmt.cond.accept(self)
-        self.w.wnoln(" {")
+        self.w.writeln(" {")
         self.w.indent()
         stmt.if_body.accept(self)
         self.w.dedent()
@@ -221,88 +220,86 @@ class ActionLangRustGenerator(Visitor):
 
         self.w.write("return ")
         if returns_closure_obj:
-            self.w.wno("(scope, ")
+            self.w.write("(scope, ")
         stmt.expr.accept(self)
         if returns_closure_obj:
-            self.w.wno(")")
-        self.w.wnoln(";")
+            self.w.write(")")
+        self.w.writeln(";")
 
     def visit_ExpressionStatement(self, stmt):
         self.w.write('')
         stmt.expr.accept(self)
-        self.w.wnoln(";")
+        self.w.writeln(";")
 
     def visit_BoolLiteral(self, expr):
-        self.w.wno("true" if expr.b else "false")
+        self.w.write("true" if expr.b else "false")
 
     def visit_IntLiteral(self, expr):
-        self.w.wno(str(expr.i))
+        self.w.write(str(expr.i))
 
     def visit_StringLiteral(self, expr):
-        self.w.wno('"'+expr.string+'"')
+        self.w.write('"'+expr.string+'"')
 
     def visit_Array(self, expr):
-        self.w.wno("[")
+        self.w.write("[")
         for el in expr.elements:
             el.accept(self)
-            self.w.wno(", ")
-        self.w.wno("]")
+            self.w.write(", ")
+        self.w.write("]")
 
     def visit_BinaryExpression(self, expr):
         if expr.operator == "**":
             raise UnsupportedFeature("exponent operator")
         else:
             # always put parentheses
-            self.w.wno("(")
+            self.w.write("(")
             expr.lhs.accept(self)
-            self.w.wno(" %s " % expr.operator
+            self.w.write(" %s " % expr.operator
                 .replace('and', '&&')
                 .replace('or', '||')
                 .replace('//', '/')) # integer division
             expr.rhs.accept(self)
-            self.w.wno(")")
+            self.w.write(")")
 
     def visit_UnaryExpression(self, expr):
-        self.w.wno(expr.operator
+        self.w.write(expr.operator
             .replace('not', '! '))
         expr.expr.accept(self)
 
     def visit_Group(self, expr):
-        # self.w.wno(" (")
         expr.subexpr.accept(self)
-        # self.w.wno(") ")
 
     def visit_ParamDecl(self, expr):
-        self.w.wno(ident_local(expr.name))
-        self.w.wno(": ")
+        self.w.write(ident_local(expr.name))
+        self.w.write(": ")
         expr.formal_type.accept(self)
 
     def visit_FunctionDeclaration(self, expr):
         function_identifier = "f%d_%s" % (len(self.functions_to_write), expr.scope.name)
         self.functions_to_write.append( (expr, function_identifier) )
-        self.w.wno(function_identifier)
+        self.w.write(function_identifier)
 
     def visit_FunctionCall(self, expr):
         if isinstance(expr.function.get_type(), SCCDClosureObject):
-            self.w.wno("call_closure!(")
+            self.w.write("call_closure!(")
             expr.function.accept(self)
-            self.w.wno(", ")
+            self.w.write(", ")
         else:
-            self.w.wno("(")
+            self.w.write("(")
             expr.function.accept(self)
-            self.w.wno(")(")
+            self.w.write(")(")
 
         # Call parameters
         for p in expr.params:
             p.accept(self)
-            self.w.wno(", ")
+            self.w.write(", ")
 
         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)
 
-        self.w.wno(")")
+        self.w.write(")")
 
 
     def visit_Identifier(self, lval):
@@ -313,41 +310,40 @@ class ActionLangRustGenerator(Visitor):
                 # a child scope exists at the current offset (typically because we encountered a function declaration) - so we must commit our scope
                 self.scope.commit(lval.offset, self.w)
 
-            # self.w.wno("/* is_lvalue */")
             self.w.write('') # indent
 
         if lval.is_init:
-            self.w.wno("let mut ")
-            self.w.wno(ident_local(lval.name))
+            self.w.write("let mut ")
+            self.w.write(ident_local(lval.name))
         else:
             self.scope.write_rvalue(lval.name, lval.offset, self.w)
 
     def visit_SCCDClosureObject(self, type):
-        self.w.wno("(%s, " % self.scope.type(type.scope, type.scope.size()))
+        self.w.write("(%s, " % self.scope.type(type.scope, type.scope.size()))
         type.function_type.accept(self)
-        self.w.wno(")")
+        self.w.write(")")
 
     def write_return_type(self, function: FunctionDeclaration):
         if function.return_type is None:
-            self.w.wno("()")
+            self.w.write("()")
         else:
             function.return_type.accept(self)
 
     def visit_SCCDFunction(self, type):
         scope = type.function.scope
-        self.w.wno("fn(")
+        self.w.write("fn(")
 
         for p in type.param_types:
             p.accept(self)
-            self.w.wno(", ")
+            self.w.write(", ")
 
         self.write_parent_params(scope, with_identifiers=False)
 
-        self.w.wno(") -> ")
+        self.w.write(") -> ")
         self.write_return_type(type.function)
 
     def visit__SCCDSimpleType(self, type):
-        self.w.wno(type.name
+        self.w.write(type.name
             .replace("int", "i32")
             .replace("float", "f64")
         )

+ 13 - 4
src/sccd/statechart/codegen/code_generation.txt

@@ -102,7 +102,7 @@ Roadmap
 
   (DONE) Milestone 6: Action language
 
-  Milestone 7: INSTATE-function -> INSTATE-macro
+  (DONE) Milestone 7: INSTATE-function -> INSTATE-macro
 
     - INVARIANT: Action lang must never depend on statechart lang!
       - Evaluation of action lang expression can only read/write to the expression's scope (and parent scopes, if expression occurs in a function)
@@ -130,7 +130,6 @@ Roadmap
     - Syntactic output events
     - Event parameters
     - Memory Protocol semantics
-    - 'Queuing' internal event semantics
     - Concurrency semantics
 
 
@@ -149,9 +148,16 @@ Performance over time
 
 for test semantics/big_step_maximality/test_ortho_takemany.xml
 
+commit ae8a94e186ee972c0d3e0b229b77901352137c64 - Dec 23, 2020 - implement additional event lifeline semantics, pass input event by reference instead of by value
+
+binary size, no opt:                            800592 bytes
+binary size, opt-level=3:                       422200 bytes
+instruction count (perf stat) (opt-level=3):    570.824 instructions:u
+
 
 commit 39fc866428c595c7ed909569d998981d1d350059 - Dec 22, 2020 - various fixes, support INSTATE
 
+binary size, no opt:                            800544 bytes
 binary size, opt-level=3:                       422200 bytes
 instruction count (perf stat) (opt-level=3):    570.776 instructions:u
 
@@ -181,5 +187,8 @@ 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)
+  - non-optimized binary size increased as features were added, optimized binary size stayed pretty much the same. this assures that Rust is able to inline and optimize away the many unused parameters of enter and exit functions.
+
+  - the same conclusion can be drawn from binary size being slightly worse when setting a lower inline threshold (opt-level=z). this means that inlining is actually reducing the binaries' size, as we would expect (lots of unused parameters can be eliminated)
+
+  - passing input event by reference instead of by value did not change the optimized binary size. probably evidence of Rust inlining or passing by value anyway.

+ 17 - 23
src/sccd/statechart/codegen/rust.py

@@ -116,7 +116,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         source = instate.ref.source
         target = instate.ref.target
 
-        self.w.wnoln("{ // macro expansion for @in(\"%s\")" % target.full_name)
+        self.w.writeln("{ // macro expansion for @in(\"%s\")" % target.full_name)
         self.w.indent()
 
         # Non-exhaustive set of current states, given that 'source' is a current state
@@ -125,12 +125,12 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         # Deconstruct state configuration tuple
         self.w.write("let (")
         for parent in parents:
-            self.w.wno("ref ")
-            self.w.wno(ident_var(parent))
-            self.w.wno(", ")
-        self.w.wno(") = ")
+            self.w.write("ref ")
+            self.w.write(ident_var(parent))
+            self.w.write(", ")
+        self.w.write(") = ")
         self.scope.write_rvalue("@conf", instate.offset, self.w)
-        self.w.wnoln(";")
+        self.w.writeln(";")
 
         for parent in parents + [source]:
             if is_ancestor(parent=target, child=parent):
@@ -168,7 +168,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.write("}")
 
     def visit_SCCDStateConfiguration(self, type):
-        self.w.wno(self.get_parallel_states_tuple_type(type.state))
+        self.w.write(self.get_parallel_states_tuple_type(type.state))
 
     def visit_RaiseOutputEvent(self, a):
         # TODO: evaluate event parameters
@@ -190,8 +190,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
         self.w.write()
         a.block.accept(self) # block is a function
-        self.w.wno("(%s, scope);" % self.get_parallel_states_tuple(self.state_stack[-1])) # call it!
-        self.w.wnoln()
+        self.w.write("(%s, scope);" % self.get_parallel_states_tuple(self.state_stack[-1])) # call it!
+        self.w.writeln()
 
     def visit_State(self, state):
         self.state_stack.append(state)
@@ -356,13 +356,6 @@ class StatechartRustGenerator(ActionLangRustGenerator):
             self.w.writeln("  // TODO: event parameters")
             self.w.writeln("}")
 
-        # if internal_queue:
-        #     self.w.writeln("enum InternalEvent {")
-        #     for event_name in internal_events:
-        #         self.w.writeln("  %s," % ident_event_type(event_name))
-        #     self.w.writeln("}")
-        # else:
-
         # Implement internal events as a set
         self.w.writeln("// Set of (raised) internal events")
         self.w.writeln("#[derive(Default)]")
@@ -373,6 +366,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln("}")
 
         if self.internal_queue:
+            # Internal events are treated like input events
             self.w.writeln("type InternalLifeline = ();")
         else:
             if internal_same_round:
@@ -394,7 +388,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                 bm |= arenas.get(d, 0)
             arenas[arena] = bm
         self.w.writeln("// Transition arenas (bitmap type)")
-        # if syntactic_maximality:
+
         for size, typ in [(8, 'u8'), (16, 'u16'), (32, 'u32'), (64, 'u64'), (128, 'u128')]:
             if len(arenas) + 1 <= size:
                 self.w.writeln("type Arenas = %s;" % typ)
@@ -411,8 +405,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln("impl<TimerId: Default> Default for Statechart<TimerId> {")
         self.w.writeln("  fn default() -> Self {")
         self.w.writeln("    // Initialize data model")
-        self.w.indent(); self.w.indent();
         self.w.writeln("    let scope = action_lang::Empty{};")
+        self.w.indent(); self.w.indent();
         if sc.datamodel is not None:
             sc.datamodel.accept(self)
         datamodel_type = self.scope.commit(sc.scope.size(), self.w)
@@ -599,13 +593,13 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                             raise UnsupportedFeature("Event parameters")
                         self.w.write("if ")
                         t.guard.accept(self) # guard is a function...
-                        self.w.wno("(") # call it!
-                        self.w.wno(self.get_parallel_states_tuple(t.source))
-                        self.w.wno(", ")
+                        self.w.write("(") # call it!
+                        self.w.write(self.get_parallel_states_tuple(t.source))
+                        self.w.write(", ")
                         # TODO: write event parameters here
                         self.write_parent_call_params(t.guard.scope)
-                        self.w.wno(")")
-                        self.w.wnoln(" {")
+                        self.w.write(")")
+                        self.w.writeln(" {")
                         self.w.indent()
 
                     # 1. Execute transition's actions

+ 4 - 0
src/sccd/test/codegen/write_crate.py

@@ -74,3 +74,7 @@ def write_crate(src, target):
         w.writeln("name = \"statechartgen\"")
         w.writeln("path = \"statechartgen.rs\"")
         w.writeln()
+
+    with open(target+"/.gitignore", 'w') as file:
+        w = IndentingWriter(out=file)
+        w.writeln("/target/")

+ 8 - 11
src/sccd/util/indenting_writer.py

@@ -5,6 +5,7 @@ class IndentingWriter:
         self.spaces = spaces
         self.out = out
         self.state = initial
+        self.newline = True
 
     def indent(self):
         self.state += self.spaces
@@ -13,17 +14,13 @@ class IndentingWriter:
         self.state -= self.spaces
 
     def writeln(self, s=""):
-        if s == "":
-            self.out.write('\n')
-        else:
-            self.out.write(' '*self.state + s + '\n')
-
-    # "write no indent"
-    def wno(self, s):
-        self.out.write(s)
-
-    def wnoln(self, s=""):
+        if self.newline:
+            self.out.write(' '*self.state)
         self.out.write(s + '\n')
+        self.newline = True
 
     def write(self, s=""):
-        self.out.write(' '*self.state + s)
+        if self.newline:
+            self.out.write(' '*self.state)
+        self.out.write(s)
+        self.newline = False