# HG changeset patch # User Paul Boddie # Date 1276210722 -7200 # Node ID 676798ee996f3e2f5c077743036b392880060feb # Parent c368d83770c7d0f4f1f87227efd30ba9170de336# Parent 5dadd4e4a171ee1a0a981337d10f339853f6c871 Merged dynamic/nested function and general development branches. diff -r c368d83770c7 -r 676798ee996f docs/invocation.txt --- a/docs/invocation.txt Fri Jun 11 00:56:08 2010 +0200 +++ b/docs/invocation.txt Fri Jun 11 00:58:42 2010 +0200 @@ -163,13 +163,31 @@ Approach #2 - add arguments, add defaults while checking frame -Defaults for lambda functions: +Dynamic functions: - f = lambda x, y=default: ... + def f(x): + def g(y=x): # dynamic: y depends on non-constant value + ... + def h(y=2): # static: y depends on constant value + ... + + def f(x): + g = lambda y=x: ... # dynamic: y depends on non-constant value + h = lambda y=2: ... # static: y depends on constant value - Defines instance of f with method: +Representation of dynamic functions: + + f = lambda x, y=nonconst: ... + + def f(x, y=nonconst): + ... - def (, x, y=default): + Defines instance with method: + + def (, x, y=nonconst): + ... + + def f(, x, y=nonconst): ... Where default is attribute #1. diff -r c368d83770c7 -r 676798ee996f micropython/__init__.py --- a/micropython/__init__.py Fri Jun 11 00:56:08 2010 +0200 +++ b/micropython/__init__.py Fri Jun 11 00:58:42 2010 +0200 @@ -167,9 +167,9 @@ self.code.append(obj) # Append any default values to the image. - # Only do this for named functions (not lambdas). + # Only do this for functions which are not dynamic. - if obj.name is not None: + if not obj.is_dynamic(): self.code += obj.default_attrs # Omit built-in function code where requested. diff -r c368d83770c7 -r 676798ee996f micropython/ast.py --- a/micropython/ast.py Fri Jun 11 00:56:08 2010 +0200 +++ b/micropython/ast.py Fri Jun 11 00:58:42 2010 +0200 @@ -586,70 +586,20 @@ # Only store the name when visiting this node from outside. if self.unit is not node.unit: - self.new_op(LoadFunction(node.unit)) + self._visitFunctionDeclaration(node) + + # Record the declared function. + self.record_value() self._visitName(node, self.name_store_instructions) # AssName equivalent self.set_source() self.discard_value() - self._generateFunctionDefaults(node.unit) - # Visiting of the code occurs when get_code is invoked on this node. else: - # Check frames using the function's details. - - fn = node.unit - nparams = len(fn.positional_names) - ndefaults = len(fn.defaults) - - fn.body_block = self.new_block() - - # Check the number of parameters and defaults. - - self.new_op(CheckFrame((nparams, ndefaults))) - if ndefaults > 0: - self.new_op(LoadFunction(fn)) - self.new_op(FillDefaults((nparams, ndefaults))) - - # Produce the body. - - self.set_block(fn.body_block) - - extend = ExtendFrame() - self.new_op(extend) - - # For functions with star parameters, make a special list for the - # extra arguments and re-map the parameter. - - if fn.has_star: - self.new_op(CopyExtra(nparams)) - - # Ensure that the star parameter has a slot in the frame. - - self.new_op(CheckExtra(nparams)) - self.new_op(StoreTemp(nparams)) - - # Add any attribute usage guards. - - if self.optimiser.should_optimise_accesses_by_attribute_usage() and hasattr(node, "_attrnames"): - self._generateGuards(node) - - # Visit the actual code. - - self.dispatch(node.code) - - # Add a return statement where one is not already produced. - - if not isinstance(self.last_op(), Return): - self.dispatch(compiler.ast.Name("None")) - self.new_op(StoreResult()) - self.new_op(Return()) - - # Make sure that enough frame space is reserved from the start. - - self.set_frame_usage(node, extend) + self._visitFunctionDefinition(node) def visitGlobal(self, node): pass @@ -675,58 +625,15 @@ # outside. if self.unit is not node.unit: - fn = node.unit - ndefaults = len(fn.defaults) - temp = self._generateFunctionDefaults(fn) - - if ndefaults > 0: - self.new_op(LoadConst(fn)) - else: - self.new_op(LoadFunction(fn)) - # Populate the new object required for the function. + # Provide the declared function. - if temp is not None: - self.new_op(LoadCallable()) - self.new_op(temp) - self.new_op(StoreCallable()) - - self.new_op(temp) - #self.discard_temp(temp) + self._visitFunctionDeclaration(node) # Visiting of the code occurs when get_code is invoked on this node. else: - # Check frames using the function's details. - - fn = node.unit - nparams = len(fn.positional_names) - ndefaults = len(fn.defaults) - - fn.body_block = self.new_block() - - # Check the number of parameters and defaults. - - self.new_op(CheckFrame((nparams, ndefaults))) - if ndefaults > 0: - self.new_op(LoadTemp(0)) # context provides storage - self.new_op(FillDefaults((nparams, ndefaults))) - - # Produce the body. - - self.set_block(fn.body_block) - - extend = ExtendFrame() - self.new_op(extend) - - self.dispatch(node.code) - - self.new_op(StoreResult()) - self.new_op(Return()) - - # Make sure that enough frame space is reserved from the start. - - self.set_frame_usage(node, extend) + self._visitFunctionDefinition(node) def visitModule(self, node): extend = ExtendFrame() diff -r c368d83770c7 -r 676798ee996f micropython/data.py --- a/micropython/data.py Fri Jun 11 00:56:08 2010 +0200 +++ b/micropython/data.py Fri Jun 11 00:58:42 2010 +0200 @@ -206,6 +206,9 @@ self.namespace[name] = Attr(None, self, name) attr = self.namespace[name] + self._set_using_attr(attr, attr_or_value, single_assignment) + + def _set_using_attr(self, attr, attr_or_value, single_assignment=1): # Handle attribute assignment as well as assignment of basic objects. # Attempt to fix the context if not explicitly defined. @@ -557,6 +560,25 @@ self.context_values.update(context_values) + def is_constant(self): + + """ + Return whether this attribute references something that can be regarded + as being constant within a particular scope. + """ + + return self.assignments == 1 + + def is_strict_constant(self): + + """ + Return whether this attribute references something that can be regarded + as being constant. + """ + + value = self.get_value() + return not (value is None or isinstance(value, Instance)) + def is_static_attribute(self): """ @@ -643,11 +665,16 @@ return 0 def __repr__(self): - return "Attr(%r, %s, %r) # [%s], %r" % ( + return "Attr(%r, %s, %r) # {[%s] (%r)}" % ( self.position, shortrepr(self.parent), self.name, self._context_values_str(), self.assignments ) + def __shortrepr__(self): + return "Attr(%r, %s, %r)" % ( + self.position, shortrepr(self.parent), self.name + ) + def _context_values_str(self): l = [] for (c, v) in self.context_values: @@ -1075,7 +1102,8 @@ "An inspected function." - def __init__(self, name, parent, argnames, defaults, has_star, has_dstar, module=None, node=None): + def __init__(self, name, parent, argnames, defaults, has_star, has_dstar, + dynamic_def=0, module=None, node=None): """ Initialise the function with the given 'name', 'parent', list of @@ -1091,14 +1119,10 @@ self.defaults = defaults self.has_star = has_star self.has_dstar = has_dstar + self.dynamic_def = dynamic_def self.astnode = node node._def = self - # For lambda functions with defaults, add a context argument. - - if name is None and defaults: - self.argnames.insert(0, "") - # Initialise the positional names. self.positional_names = self.argnames[:] @@ -1139,6 +1163,7 @@ # Image generation details. + self.dynamic = None self.location = None self.code_location = None self.code_body_location = None @@ -1157,7 +1182,7 @@ if isinstance(name, tuple): self._add_parameters(name) else: - self.set(name, None) + self.set(name, Instance()) def __repr__(self): if self.location is not None: @@ -1177,12 +1202,46 @@ def get_body_block(self): return self.body_block - # Namespace-related methods. + # Defaults-related methods. + + def store_default(self, attr_or_value): + + """ + Reserve space for defaults, set outside the function, potentially on a + dynamic basis, using the 'attr_or_value'. + """ + + attr = Attr(None, self, None) + self._set_using_attr(attr, attr_or_value) + self.default_attrs.append(attr) + + def make_dynamic(self): + + "Return whether this function must be handled using a dynamic object." - def store_default(self, value): - attr = Attr(None, self, None) - attr.update([self.get_context_and_value(value)], 1) - self.default_attrs.append(attr) + if self.dynamic is None: + for attr in self.default_attrs: + if not attr.is_strict_constant() and self.dynamic_def: + self.dynamic = 1 + self._make_dynamic() + break + else: + self.dynamic = 0 + + return self.dynamic + + is_dynamic = make_dynamic + + def _make_dynamic(self): + + "Where functions have dynamic defaults, add a context argument." + + name = "" + self.argnames.insert(0, name) + self.positional_names.insert(0, name) + self.set(name, Instance()) + + # Namespace-related methods. def make_global(self, name): if name not in self.argnames and not self.has_key(name): @@ -1285,7 +1344,7 @@ "Make an instantiator function from a method, keeping all arguments." function = Function(self.parent.name, self.parent.parent, self.argnames, self.defaults, - self.has_star, self.has_dstar, self.module, self.astnode) + self.has_star, self.has_dstar, self.dynamic_def, self.module, self.astnode) function.default_attrs = self.default_attrs return function diff -r c368d83770c7 -r 676798ee996f micropython/inspect.py --- a/micropython/inspect.py Fri Jun 11 00:56:08 2010 +0200 +++ b/micropython/inspect.py Fri Jun 11 00:58:42 2010 +0200 @@ -67,8 +67,9 @@ Assignments to names within functions are not generally considered to cause the targets of such assignments to provide constant values since functions can be -invoked many times with different inputs. However, there may be benefits in -considering a local to be constant within a single invocation. +invoked many times with different inputs. This affects particularly the +definition of functions or lambdas within functions. However, there may be +benefits in considering a local to be constant within a single invocation. """ from micropython.common import * @@ -111,6 +112,7 @@ self.in_init = 0 # Find instance attributes in __init__ methods. self.in_method = 0 # Find instance attributes in all methods. + self.in_function = 0 # Note function presence, affecting definitions. self.in_loop = 0 # Note loop "membership", affecting assignments. self.namespaces = [] self.module = None @@ -261,6 +263,10 @@ # Namespace methods. + def in_class(self, namespaces=None): + namespaces = namespaces or self.namespaces + return len(namespaces) > 1 and isinstance(namespaces[-2], Class) + def store(self, name, obj): "Record attribute or local 'name', storing 'obj'." @@ -436,6 +442,7 @@ node.defaults, (node.flags & 4 != 0), (node.flags & 8 != 0), + self.in_loop or self.in_function, self, node ) @@ -454,18 +461,16 @@ self.functions.append((node, self.namespaces + [function])) + # Store the function. + if name is not None: self.store(name, function) else: self.store_lambda(function) - # Where defaults exist, an instance needs creating. Thus, it makes - # no sense to return a reference to the function here, since the - # recipient will not be referencing the function itself. + # Test the defaults and assess whether an dynamic object will result. - if node.defaults: - return Instance() # indicates no known target - + function.make_dynamic() return function def _visitFunctionBody(self, node, namespaces): @@ -475,14 +480,21 @@ # Current namespace is the function. # Previous namespace is the class. - if len(namespaces) > 1 and isinstance(namespaces[-2], Class): + if self.in_class(namespaces): if namespaces[-1].name == "__init__": self.in_init = 1 self.in_method = 1 + in_function = self.in_function + in_loop = self.in_loop + self.in_function = 1 + self.in_loop = 0 + self.namespaces = namespaces self.dispatch(node.code) + self.in_loop = in_loop + self.in_function = in_function self.in_init = 0 self.in_method = 0 @@ -597,6 +609,9 @@ print "Class %r in %r is not global: ignored." % (node.name, self.namespaces[-1].full_name()) return None else: + if self.in_loop: + print "Warning: class %r in %r defined in a loop." % (node.name, self.full_name()) + cls = Class(node.name, self.get_namespace(), self, node) # Visit the base class expressions, attempting to find concrete @@ -698,6 +713,7 @@ self.use_name("__iter__", node.list) self.use_name("next") + in_loop = self.in_loop self.in_loop = 1 self.dispatch(node.assign) self.dispatch(node.list) @@ -708,7 +724,7 @@ self.new_branch() self.dispatch(node.body) self.shelve_branch() - self.in_loop = 0 + self.in_loop = in_loop # Maintain a branch for the else clause or the current retained usage # where execution avoids the conditional clauses. @@ -987,12 +1003,13 @@ # Propagate attribute usage to branches. + in_loop = self.in_loop self.in_loop = 1 self.dispatch(node.test) self.new_branch(node) self.dispatch(node.body) self.shelve_branch() - self.in_loop = 0 + self.in_loop = in_loop # Maintain a branch for the else clause or the current retained usage # where execution avoids the conditional clauses. diff -r c368d83770c7 -r 676798ee996f micropython/rsvp.py --- a/micropython/rsvp.py Fri Jun 11 00:56:08 2010 +0200 +++ b/micropython/rsvp.py Fri Jun 11 00:58:42 2010 +0200 @@ -177,12 +177,12 @@ if not self.is_generated(with_builtins): item.code_location = item.full_name() - # Skip any defaults for named functions. + # Skip any defaults for static functions. - elif item.name is not None: + elif not item.is_dynamic(): item.code_location = location + len(item.defaults) - # Skip any defaults for lambda functions. + # Skip any defaults for dynamic functions. else: item.code_location = location diff -r c368d83770c7 -r 676798ee996f micropython/trans.py --- a/micropython/trans.py Fri Jun 11 00:56:08 2010 +0200 +++ b/micropython/trans.py Fri Jun 11 00:58:42 2010 +0200 @@ -619,7 +619,10 @@ t = self.optimiser.optimise_known_target() if t: target, context = t - if isinstance(target, Instance): # lambda object + + # Detect dynamic functions acting like instances. + + if isinstance(target, Function) and target.is_dynamic(): target, context = None, None else: target, context = None, None @@ -904,10 +907,9 @@ 'employed_positions' collection. """ - # Where a lambda is involved, construct a dynamic object to hold the - # defaults. + # Where appropriate, construct a dynamic object to hold the defaults. - dynamic = target.name is None + dynamic = target.is_dynamic() # Here, we use negative index values to visit the right hand end of # the defaults list. @@ -1019,6 +1021,103 @@ if temp_context is not None: self.discard_temp(temp_context) + def _visitFunctionDeclaration(self, node): + + """ + Visit the function declaration at 'node', which can be a lambda or a + named function. As a consequence an instruction will be generated which + provides a reference to the function. + """ + + fn = node.unit + ndefaults = len(fn.defaults) + temp = self._generateFunctionDefaults(fn) + + # Populate the new object required for the function. + + if temp is not None: + self.new_op(LoadConst(fn)) + self.new_op(LoadCallable()) + self.new_op(temp) + self.new_op(StoreCallable()) + + self.new_op(temp) + #self.discard_temp(temp) + else: + self.new_op(LoadFunction(fn)) + + def _visitFunctionDefinition(self, node): + + """ + Visit the function definition at 'node', which can be a lambda or a + named function, generating the prelude with argument and default + checking, plus the body of the function itself. + """ + + # Check frames using the function's details. + + fn = node.unit + nparams = len(fn.positional_names) + ndefaults = len(fn.defaults) + + fn.body_block = self.new_block() + + # Check the number of parameters and defaults. + + self.new_op(CheckFrame((nparams, ndefaults))) + + if fn.is_dynamic(): + self.new_op(LoadTemp(0)) # context provides storage + else: + self.new_op(LoadFunction(fn)) + + if ndefaults > 0: + self.new_op(FillDefaults((nparams, ndefaults))) + + # Produce the body. + + self.set_block(fn.body_block) + + extend = ExtendFrame() + self.new_op(extend) + + # For functions with star parameters, make a special list for the + # extra arguments and re-map the parameter. + + if fn.has_star: + self.new_op(CopyExtra(nparams)) + + # Ensure that the star parameter has a slot in the frame. + + self.new_op(CheckExtra(nparams)) + self.new_op(StoreTemp(nparams)) + + # Add any attribute usage guards. + + if self.optimiser.should_optimise_accesses_by_attribute_usage() and hasattr(node, "_attrnames"): + self._generateGuards(node) + + # Visit the actual code. + + self.dispatch(node.code) + + # Add a return statement where one is not already produced. + + if not isinstance(self.last_op(), Return): + + # Return None for normal functions without explicit return + # statements. + + if fn.name is not None: + self.dispatch(compiler.ast.Name("None")) + + self.new_op(StoreResult()) + self.new_op(Return()) + + # Make sure that enough frame space is reserved from the start. + + self.set_frame_usage(node, extend) + def _generateFunctionDefaults(self, function): """ @@ -1031,10 +1130,10 @@ if not attr_to_default: return None - # Where a lambda is involved, construct a dynamic object to hold the - # defaults. + # Where non-constant defaults are involved, construct a dynamic object + # to hold the defaults. - dynamic = function.name is None + dynamic = function.is_dynamic() if dynamic: self.make_instance(self.get_builtin_class("function", function), len(attr_to_default)) diff -r c368d83770c7 -r 676798ee996f tests/call_func_default_global.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/call_func_default_global.py Fri Jun 11 00:58:42 2010 +0200 @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +d = 4 + +def f(a, b, c=d): + return c + +result_3 = f(1, 2, 3) +result_4 = f(1, 2) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r c368d83770c7 -r 676798ee996f tests/call_func_default_global_multiple.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/call_func_default_global_multiple.py Fri Jun 11 00:58:42 2010 +0200 @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +d = 4 + +i = 0 +l = [] + +while i < 3: + def f(a, b, c=d): + return c + l.append(f) + i += 1 + +f0 = l[0] +f1 = l[1] +f2 = l[2] + +result0_3 = f0(1, 2, 3) +result1_3 = f1(1, 2, 3) +result2_3 = f2(1, 2, 3) +result0_4 = f0(1, 2) +result1_4 = f1(1, 2) +result2_4 = f2(1, 2) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r c368d83770c7 -r 676798ee996f tests/call_func_default_global_non_constant.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/call_func_default_global_non_constant.py Fri Jun 11 00:58:42 2010 +0200 @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +d = 2 +d = 4 # make non-constant + +def f(a, b, c=d): + return c + +result_3 = f(1, 2, 3) +result_4 = f(1, 2) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r c368d83770c7 -r 676798ee996f tests/lambda_defaults_local_non_constant.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/lambda_defaults_local_non_constant.py Fri Jun 11 00:58:42 2010 +0200 @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +def make_add(x): + return lambda a, b=x: a + b + +def g(f, x): + return f(x) + +add_2 = make_add(2) +add_3 = make_add(3) + +result_3 = add_2(1) +result_4 = g(add_2, 2) + +result_5 = add_3(2) +result_6 = g(add_3, 3) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r c368d83770c7 -r 676798ee996f tests/lambda_defaults_non_constant.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/lambda_defaults_non_constant.py Fri Jun 11 00:58:42 2010 +0200 @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +x = 1 +x = 2 + +def make_add(): + return lambda a, b=x: a + x + +def g(f, x): + return f(x) + +add_2 = make_add() +result_3 = add_2(1) +result2_3 = g(add_2, 1) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r c368d83770c7 -r 676798ee996f tests/nested_functions_using_defaults.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/nested_functions_using_defaults.py Fri Jun 11 00:58:42 2010 +0200 @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +def a(x): + def b(p=x): + return p + return b + +f2 = a(2) +f3 = a(3) + +result_2 = f2() +result_3 = f3() + +# vim: tabstop=4 expandtab shiftwidth=4