Lichen

Changeset

859:f7ddc68a38a0
2018-07-22 Paul Boddie raw files shortlog changelog graph Merged changes from the default branch. tuple-optimisations
lib/__builtins__/tuple.py (file)
     1.1 --- a/encoders.py	Thu Jul 12 18:19:02 2018 +0200
     1.2 +++ b/encoders.py	Sun Jul 22 00:19:35 2018 +0200
     1.3 @@ -259,6 +259,14 @@
     1.4      "__load_static_ignore", "__load_static_replace", "__load_static_test", "<test_context_static>",
     1.5      )
     1.6  
     1.7 +accessor_values = (
     1.8 +    "<accessor>",
     1.9 +    )
    1.10 +
    1.11 +accessor_ops = (
    1.12 +    "<accessor>", "<set_accessor>",
    1.13 +    )
    1.14 +
    1.15  context_values = (
    1.16      "<context>",
    1.17      )
    1.18 @@ -278,7 +286,7 @@
    1.19      "<accessor>", "<context>", "<name>", "<private_context>", "<target_accessor>"
    1.20      )
    1.21  
    1.22 -def encode_access_instruction(instruction, subs, context_index):
    1.23 +def encode_access_instruction(instruction, subs, accessor_index, context_index):
    1.24  
    1.25      """
    1.26      Encode the 'instruction' - a sequence starting with an operation and
    1.27 @@ -288,6 +296,9 @@
    1.28      The 'subs' parameter defines a mapping of substitutions for special values
    1.29      used in instructions.
    1.30  
    1.31 +    The 'accessor_index' parameter defines the position in local accessor
    1.32 +    storage for the referenced accessor or affected by an accessor operation.
    1.33 +
    1.34      The 'context_index' parameter defines the position in local context storage
    1.35      for the referenced context or affected by a context operation.
    1.36  
    1.37 @@ -304,7 +315,7 @@
    1.38      if args:
    1.39          converting_op = op
    1.40          for arg in args:
    1.41 -            s, _substituted = encode_access_instruction_arg(arg, subs, converting_op, context_index)
    1.42 +            s, _substituted = encode_access_instruction_arg(arg, subs, converting_op, accessor_index, context_index)
    1.43              substituted.update(_substituted)
    1.44              a.append(s)
    1.45              converting_op = None
    1.46 @@ -326,6 +337,11 @@
    1.47      elif op in static_ops:
    1.48          a[-1] = "&%s" % a[-1]
    1.49  
    1.50 +    # Add accessor storage information to certain operations.
    1.51 +
    1.52 +    if op in accessor_ops:
    1.53 +        a.insert(0, accessor_index)
    1.54 +
    1.55      # Add context storage information to certain operations.
    1.56  
    1.57      if op in context_ops:
    1.58 @@ -365,19 +381,19 @@
    1.59  
    1.60      return "%s%s" % (op, argstr), substituted
    1.61  
    1.62 -def encode_access_instruction_arg(arg, subs, op, context_index):
    1.63 +def encode_access_instruction_arg(arg, subs, op, accessor_index, context_index):
    1.64  
    1.65      """
    1.66      Encode 'arg' using 'subs' to define substitutions, 'op' to indicate the
    1.67 -    operation to which the argument belongs, and 'context_index' to indicate any
    1.68 -    affected context storage.
    1.69 +    operation to which the argument belongs, and with 'accessor_index' and
    1.70 +    'context_index' indicating any affected accessor and context storage.
    1.71  
    1.72      Return a tuple containing the encoded form of 'arg' along with a collection
    1.73      of any substituted values.
    1.74      """
    1.75  
    1.76      if isinstance(arg, tuple):
    1.77 -        encoded, substituted = encode_access_instruction(arg, subs, context_index)
    1.78 +        encoded, substituted = encode_access_instruction(arg, subs, accessor_index, context_index)
    1.79          return attribute_to_reference(op, arg[0], encoded, substituted)
    1.80  
    1.81      # Special values only need replacing, not encoding.
    1.82 @@ -386,7 +402,7 @@
    1.83  
    1.84          # Handle values modified by storage details.
    1.85  
    1.86 -        if arg in context_values:
    1.87 +        if arg in accessor_values or arg in context_values:
    1.88              encoded = "%s(%s)" % (subs.get(arg), context_index)
    1.89          else:
    1.90              encoded = subs.get(arg)
     2.1 --- a/lib/__builtins__/tuple.py	Thu Jul 12 18:19:02 2018 +0200
     2.2 +++ b/lib/__builtins__/tuple.py	Sun Jul 22 00:19:35 2018 +0200
     2.3 @@ -22,7 +22,8 @@
     2.4  from __builtins__.iteration.iterator import itemiterator
     2.5  from __builtins__.sequence import hashable, unpackable
     2.6  from native import tuple_init, \
     2.7 -                   list_element, list_len, list_setsize, list_setelement
     2.8 +                   list_element, list_len, list_setsize, list_setelement, \
     2.9 +                   isinstance as _isinstance
    2.10  
    2.11  class tuple(unpackable, hashable):
    2.12  
    2.13 @@ -73,7 +74,13 @@
    2.14  
    2.15          return list_len(self.__data__)
    2.16  
    2.17 -    def __add__(self, other): pass
    2.18 +    def __add__(self, other):
    2.19 +
    2.20 +        "Add this tuple to 'other'."
    2.21 +
    2.22 +        if not _isinstance(other, tuple):
    2.23 +            raise TypeError
    2.24 +        return tuple(tuplepair(self, other))
    2.25  
    2.26      def __str__(self):
    2.27  
    2.28 @@ -110,4 +117,28 @@
    2.29  
    2.30          raise TypeError
    2.31  
    2.32 +class tuplepair:
    2.33 +
    2.34 +    "A combination of tuples."
    2.35 +
    2.36 +    def __init__(self, a, b):
    2.37 +        self.a = a
    2.38 +        self.b = b
    2.39 +
    2.40 +    def __len__(self):
    2.41 +
    2.42 +        "Return the combined length of the tuples."
    2.43 +
    2.44 +        return len(self.a) + len(self.b)
    2.45 +
    2.46 +    def __getitem__(self, index):
    2.47 +
    2.48 +        "Return the value from 'index' in the combined tuple."
    2.49 +
    2.50 +        asize = len(self.a)
    2.51 +        if index < asize:
    2.52 +            return self.a.__get_single_item__(index)
    2.53 +        else:
    2.54 +            return self.b.__get_single_item__(index - asize)
    2.55 +
    2.56  # vim: tabstop=4 expandtab shiftwidth=4
     3.1 --- a/templates/ops.h	Thu Jul 12 18:19:02 2018 +0200
     3.2 +++ b/templates/ops.h	Sun Jul 22 00:19:35 2018 +0200
     3.3 @@ -107,10 +107,11 @@
     3.4  __attr __test_context_revert(int target, __attr context, __attr attr, __attr contexts[]);
     3.5  __attr __test_context_static(int target, __attr context, __ref value, __attr contexts[]);
     3.6  
     3.7 +#define __get_accessor(__TARGET) (__tmp_values[__TARGET])
     3.8  #define __get_context(__TARGET) (__tmp_contexts[__TARGET])
     3.9  #define __set_context(__TARGET, __ATTR) (__tmp_contexts[__TARGET] = (__ATTR))
    3.10  #define __set_private_context(__ATTR) (__tmp_private_context = (__ATTR))
    3.11 -#define __set_accessor(__ATTR) (__tmp_value = (__ATTR))
    3.12 +#define __set_accessor(__TARGET, __ATTR) (__tmp_values[__TARGET] = (__ATTR))
    3.13  #define __set_target_accessor(__ATTR) (__tmp_target_value = (__ATTR))
    3.14  
    3.15  /* Context testing for invocations. */
     4.1 --- a/tests/methods_rebound.py	Thu Jul 12 18:19:02 2018 +0200
     4.2 +++ b/tests/methods_rebound.py	Sun Jul 22 00:19:35 2018 +0200
     4.3 @@ -22,17 +22,30 @@
     4.4  
     4.5  e = E()
     4.6  
     4.7 +# Normal method access and invocation.
     4.8 +
     4.9  print c.f.__name__                  # f
    4.10  print c.f()                         # <__main__.C instance>
    4.11                                      # 123
    4.12 +
    4.13 +# Access and call assigned bound method.
    4.14 +
    4.15  print d.f.__name__                  # wrapper
    4.16  print d.f()                         # <__main__.C instance>
    4.17                                      # 123
    4.18  
    4.19 +# Access and call assigned function.
    4.20 +
    4.21  print e.f.__name__                  # fn
    4.22  print e.f()                         # 456
    4.23 +
    4.24 +# Access details of assigned method.
    4.25 +
    4.26  print e.g.__name__                  # f
    4.27  
    4.28 +# Attempt to call method belonging to another class via an incompatible
    4.29 +# instance. In Python, this would be an unbound method call attempt.
    4.30 +
    4.31  try:
    4.32      print e.g()
    4.33  except TypeError:
     5.1 --- a/tests/nested_calls.py	Thu Jul 12 18:19:02 2018 +0200
     5.2 +++ b/tests/nested_calls.py	Sun Jul 22 00:19:35 2018 +0200
     5.3 @@ -13,3 +13,31 @@
     5.4  
     5.5  c = C(3)
     5.6  print c.length()                        # 6
     5.7 +
     5.8 +# Explicit function for addition purposes.
     5.9 +
    5.10 +def combine(x, y, z):
    5.11 +    return x + y + z
    5.12 +
    5.13 +class Tree:
    5.14 +    def __init__(self, item, left=None, right=None):
    5.15 +        self.item = item
    5.16 +        self.left = left
    5.17 +        self.right = right
    5.18 +
    5.19 +    def count(self):
    5.20 +        if self.left and self.right:
    5.21 +            # Test calls in parameter lists needing separate temporary storage.
    5.22 +            return combine(self.item, self.left.count(), self.right.count())
    5.23 +        else:
    5.24 +            return self.item
    5.25 +
    5.26 +tree = \
    5.27 +    Tree(10000,
    5.28 +        Tree(2000,
    5.29 +            Tree(300),
    5.30 +            Tree(40)
    5.31 +            ),
    5.32 +        Tree(5))
    5.33 +
    5.34 +print tree.count()                      # 12345
     6.1 --- a/tests/tuple.py	Thu Jul 12 18:19:02 2018 +0200
     6.2 +++ b/tests/tuple.py	Sun Jul 22 00:19:35 2018 +0200
     6.3 @@ -31,3 +31,19 @@
     6.4      a, b, c = l
     6.5  except ValueError, exc:
     6.6      print "a, b, c = l: failed with length", exc.value
     6.7 +
     6.8 +# Add tuples together.
     6.9 +
    6.10 +m = ("five", 6, 7, 8)
    6.11 +n = t + m
    6.12 +print n                 # (1, 2, 3, "four", "five", 6, 7, 8)
    6.13 +
    6.14 +# Add to list, really testing tuple iteration.
    6.15 +
    6.16 +o = l + m
    6.17 +print o                 # [1, 2, 3, "four", "five", 6, 7, 8]
    6.18 +
    6.19 +try:
    6.20 +    print t + l         # should raise an exception
    6.21 +except TypeError:
    6.22 +    print "t + l: failed due to tuple-list addition"
     7.1 --- a/translator.py	Thu Jul 12 18:19:02 2018 +0200
     7.2 +++ b/translator.py	Sun Jul 22 00:19:35 2018 +0200
     7.3 @@ -115,6 +115,7 @@
     7.4  
     7.5          self.namespaces = []
     7.6          self.in_conditional = False
     7.7 +        self.in_parameter_list = False
     7.8  
     7.9          # Exception raising adjustments.
    7.10  
    7.11 @@ -317,6 +318,8 @@
    7.12              self.max_function_target = 0
    7.13              self.context_index = 0
    7.14              self.max_context_index = 0
    7.15 +            self.accessor_index = 0
    7.16 +            self.max_accessor_index = 0
    7.17              self.start_module()
    7.18              self.process_structure(node)
    7.19              self.end_module()
    7.20 @@ -554,11 +557,13 @@
    7.21          # The context set or retrieved will be that used by any enclosing
    7.22          # invocation.
    7.23  
    7.24 +        accessor_index = self.accessor_index
    7.25          context_index = self.context_index
    7.26          context_identity = None
    7.27          context_identity_verified = False
    7.28          final_identity = None
    7.29          accessor_test = False
    7.30 +        accessor_stored = False
    7.31  
    7.32          # Obtain encoded versions of each instruction, accumulating temporary
    7.33          # variables.
    7.34 @@ -568,7 +573,9 @@
    7.35              # Intercept a special instruction identifying the context.
    7.36  
    7.37              if instruction[0] in ("<context_identity>", "<context_identity_verified>"):
    7.38 -                context_identity, _substituted = encode_access_instruction_arg(instruction[1], subs, instruction[0], context_index)
    7.39 +                context_identity, _substituted = \
    7.40 +                    encode_access_instruction_arg(instruction[1], subs, instruction[0],
    7.41 +                                                  accessor_index, context_index)
    7.42                  context_identity_verified = instruction[0] == "<context_identity_verified>"
    7.43                  continue
    7.44  
    7.45 @@ -585,10 +592,16 @@
    7.46                  instruction = ("__to_error", instruction)
    7.47                  accessor_test = True
    7.48  
    7.49 +            # Intercept accessor storage.
    7.50 +
    7.51 +            elif instruction[0] == "<set_accessor>":
    7.52 +                accessor_stored = True
    7.53 +
    7.54              # Collect the encoded instruction, noting any temporary variables
    7.55              # required by it.
    7.56  
    7.57 -            encoded, _substituted = encode_access_instruction(instruction, subs, context_index)
    7.58 +            encoded, _substituted = encode_access_instruction(instruction, subs,
    7.59 +                                        accessor_index, context_index)
    7.60              output.append(encoded)
    7.61              substituted.update(_substituted)
    7.62  
    7.63 @@ -604,7 +617,9 @@
    7.64              refs = set([self.importer.identify(final_identity)])
    7.65  
    7.66          del self.attrs[0]
    7.67 -        return AttrResult(output, refs, location, context_identity, context_identity_verified, accessor_test)
    7.68 +        return AttrResult(output, refs, location,
    7.69 +                          context_identity, context_identity_verified,
    7.70 +                          accessor_test, accessor_stored)
    7.71  
    7.72      def init_substitutions(self):
    7.73  
    7.74 @@ -619,21 +634,22 @@
    7.75              # Substitutions used by instructions.
    7.76  
    7.77              "<private_context>" : "__tmp_private_context",
    7.78 -            "<accessor>" : "__tmp_value",
    7.79              "<target_accessor>" : "__tmp_target_value",
    7.80  
    7.81              # Mappings to be replaced by those given below.
    7.82  
    7.83 +            "<accessor>" : "__tmp_values",
    7.84              "<context>" : "__tmp_contexts",
    7.85              "<test_context_revert>" : "__tmp_contexts",
    7.86              "<test_context_static>" : "__tmp_contexts",
    7.87              "<set_context>" : "__tmp_contexts",
    7.88              "<set_private_context>" : "__tmp_private_context",
    7.89 -            "<set_accessor>" : "__tmp_value",
    7.90 +            "<set_accessor>" : "__tmp_values",
    7.91              "<set_target_accessor>" : "__tmp_target_value",
    7.92              }
    7.93  
    7.94          self.op_subs = {
    7.95 +            "<accessor>" : "__get_accessor",
    7.96              "<context>" : "__get_context",
    7.97              "<test_context_revert>" : "__test_context_revert",
    7.98              "<test_context_static>" : "__test_context_static",
    7.99 @@ -840,6 +856,8 @@
   7.100          self.max_function_target = 0
   7.101          self.context_index = 0
   7.102          self.max_context_index = 0
   7.103 +        self.accessor_index = 0
   7.104 +        self.max_accessor_index = 0
   7.105  
   7.106          # Volatile locals for exception handling.
   7.107  
   7.108 @@ -1082,6 +1100,7 @@
   7.109          # With such operations present, the expression cannot be eliminated.
   7.110  
   7.111          tests_accessor = have_access_context and expr.tests_accessor()
   7.112 +        stores_accessor = have_access_context and expr.stores_accessor()
   7.113  
   7.114          # Parameter details and parameter list dimensions.
   7.115  
   7.116 @@ -1258,6 +1277,9 @@
   7.117          if need_context_stored:
   7.118              self.record_temp("__tmp_contexts")
   7.119  
   7.120 +        if stores_accessor:
   7.121 +            self.record_temp("__tmp_values")
   7.122 +
   7.123          # Arguments are presented in a temporary frame array with any context
   7.124          # always being the first argument. Where it would be unused, it may be
   7.125          # set to null.
   7.126 @@ -1284,6 +1306,7 @@
   7.127  
   7.128          function_target = self.function_target
   7.129          context_index = self.context_index
   7.130 +        accessor_index = self.accessor_index
   7.131  
   7.132          if need_target_stored:
   7.133              self.next_target()
   7.134 @@ -1291,6 +1314,12 @@
   7.135          if need_context_stored:
   7.136              self.next_context()
   7.137  
   7.138 +        if stores_accessor:
   7.139 +            self.next_accessor()
   7.140 +
   7.141 +        in_parameter_list = self.in_parameter_list
   7.142 +        self.in_parameter_list = True
   7.143 +
   7.144          for i, arg in enumerate(n.args):
   7.145              argexpr = self.process_structure_node(arg)
   7.146  
   7.147 @@ -1329,8 +1358,12 @@
   7.148  
   7.149          # Reference the current target again.
   7.150  
   7.151 -        self.function_target = function_target
   7.152 -        self.context_index = context_index
   7.153 +        self.in_parameter_list = in_parameter_list
   7.154 +
   7.155 +        if not self.in_parameter_list:
   7.156 +            self.function_target = function_target
   7.157 +            self.context_index = context_index
   7.158 +            self.accessor_index = accessor_index
   7.159  
   7.160          # Defaults are added to the frame where arguments are missing.
   7.161  
   7.162 @@ -1473,6 +1506,13 @@
   7.163          self.context_index += 1
   7.164          self.max_context_index = max(self.context_index, self.max_context_index)
   7.165  
   7.166 +    def next_accessor(self):
   7.167 +
   7.168 +        "Allocate the next accessor value storage."
   7.169 +
   7.170 +        self.accessor_index += 1
   7.171 +        self.max_accessor_index = max(self.accessor_index, self.max_accessor_index)
   7.172 +
   7.173      def always_callable(self, refs):
   7.174  
   7.175          "Determine whether all 'refs' are callable."
   7.176 @@ -1527,8 +1567,9 @@
   7.177  
   7.178          name = self.get_lambda_name()
   7.179          function_name = self.get_object_path(name)
   7.180 -
   7.181 -        defaults = self.process_function_defaults(n, name, function_name, "__tmp_value")
   7.182 +        instance_name = "__get_accessor(%d)" % self.accessor_index
   7.183 +
   7.184 +        defaults = self.process_function_defaults(n, name, function_name, instance_name)
   7.185  
   7.186          # Without defaults, produce an attribute referring to the function.
   7.187  
   7.188 @@ -1539,11 +1580,16 @@
   7.189          # copy.
   7.190  
   7.191          else:
   7.192 -            self.record_temp("__tmp_value")
   7.193 -            return make_expression("(__tmp_value = __ATTRVALUE(__COPY(&%s, sizeof(%s))), %s, __tmp_value)" % (
   7.194 +            self.record_temp("__tmp_values")
   7.195 +            return make_expression("""\
   7.196 +(__set_accessor(%d, __ATTRVALUE(__COPY(&%s, sizeof(%s)))),
   7.197 + %s,
   7.198 + __get_accessor(%d))""" % (
   7.199 +                self.accessor_index,
   7.200                  encode_path(function_name),
   7.201                  encode_symbol("obj", function_name),
   7.202 -                ", ".join(defaults)))
   7.203 +                ", ".join(defaults),
   7.204 +                self.accessor_index))
   7.205  
   7.206      def process_logical_node(self, n):
   7.207  
   7.208 @@ -2119,24 +2165,21 @@
   7.209  
   7.210          "Write temporary storage employed by 'name'."
   7.211  
   7.212 -        # Provide space for the given number of targets.
   7.213 -
   7.214 -        targets = self.max_function_target
   7.215 +        # Provide space for the recorded number of temporary variables.
   7.216  
   7.217          if self.uses_temp(name, "__tmp_targets"):
   7.218 -            self.writeline("__attr __tmp_targets[%d];" % targets)
   7.219 -
   7.220 -        index = self.max_context_index
   7.221 +            self.writeline("__attr __tmp_targets[%d];" % self.max_function_target)
   7.222  
   7.223          if self.uses_temp(name, "__tmp_contexts"):
   7.224 -            self.writeline("__attr __tmp_contexts[%d];" % index)
   7.225 +            self.writeline("__attr __tmp_contexts[%d];" % self.max_context_index)
   7.226 +
   7.227 +        if self.uses_temp(name, "__tmp_values"):
   7.228 +            self.writeline("__attr __tmp_values[%d];" % self.max_accessor_index)
   7.229  
   7.230          # Add temporary variable usage details.
   7.231  
   7.232          if self.uses_temp(name, "__tmp_private_context"):
   7.233              self.writeline("__attr __tmp_private_context;")
   7.234 -        if self.uses_temp(name, "__tmp_value"):
   7.235 -            self.writeline("__attr __tmp_value;")
   7.236          if self.uses_temp(name, "__tmp_target_value"):
   7.237              self.writeline("__attr __tmp_target_value;")
   7.238          if self.uses_temp(name, "__tmp_result"):
     8.1 --- a/transresults.py	Thu Jul 12 18:19:02 2018 +0200
     8.2 +++ b/transresults.py	Sun Jul 22 00:19:35 2018 +0200
     8.3 @@ -3,7 +3,7 @@
     8.4  """
     8.5  Translation result abstractions.
     8.6  
     8.7 -Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk>
     8.8 +Copyright (C) 2016, 2017, 2018 Paul Boddie <paul@boddie.org.uk>
     8.9  
    8.10  This program is free software; you can redistribute it and/or modify it under
    8.11  the terms of the GNU General Public License as published by the Free Software
    8.12 @@ -179,13 +179,15 @@
    8.13      "A translation result for an attribute access."
    8.14  
    8.15      def __init__(self, instructions, refs, location, context_identity,
    8.16 -                 context_identity_verified, accessor_test):
    8.17 +                 context_identity_verified, accessor_test, accessor_stored):
    8.18 +
    8.19          InstructionSequence.__init__(self, instructions)
    8.20          self.refs = refs
    8.21          self.location = location
    8.22          self.context_identity = context_identity
    8.23          self.context_identity_verified = context_identity_verified
    8.24          self.accessor_test = accessor_test
    8.25 +        self.accessor_stored = accessor_stored
    8.26  
    8.27      def references(self):
    8.28          return self.refs
    8.29 @@ -202,6 +204,9 @@
    8.30      def tests_accessor(self):
    8.31          return self.accessor_test
    8.32  
    8.33 +    def stores_accessor(self):
    8.34 +        return self.accessor_stored
    8.35 +
    8.36      def get_origin(self):
    8.37          return self.refs and len(self.refs) == 1 and first(self.refs).get_origin()
    8.38  
    8.39 @@ -220,8 +225,10 @@
    8.40          return encode_instructions(self.instructions)
    8.41  
    8.42      def __repr__(self):
    8.43 -        return "AttrResult(%r, %r, %r, %r, %r)" % (self.instructions, self.refs,
    8.44 -               self.location, self.context_identity, self.accessor_test)
    8.45 +        return "AttrResult(%r, %r, %r, %r, %r, %r, %r)" % (
    8.46 +                self.instructions, self.refs, self.location,
    8.47 +                self.context_identity, self.context_identity_verified,
    8.48 +                self.accessor_test, self.accessor_stored)
    8.49  
    8.50  class AliasResult(NameRef, Result):
    8.51