# HG changeset patch # User Paul Boddie # Date 1242514707 -7200 # Node ID 626da7ae6d5e57f79746300195c0615d8b30d7f0 # Parent 398f466a7011d7b64f5f80818aec2cc358ffdc49 Moved argument checking inside functions, changing the role of CheckFrame and introducing a separate FillDefaults instruction. Introduced a JumpWithFrameDirect instruction which is able, in conjunction with a new code_body_location attribute on Function and Class instances (for function bodies and class instantiator bodies respectively), to skip argument checking for invocations which could be checked at compile-time. Removed the invocation details from the common object structure. Improved various tests. diff -r 398f466a7011 -r 626da7ae6d5e docs/concepts.txt --- a/docs/concepts.txt Sat May 16 01:34:03 2009 +0200 +++ b/docs/concepts.txt Sun May 17 00:58:27 2009 +0200 @@ -189,16 +189,14 @@ certain common features and operations are supported in the same way for all of these things. To permit this, a common data structure format is used. - Header............................................................................ Attributes..... + Header.................................................... Attributes................. - Identifier Identifier Address Details Identifier Size Object Object ... + Identifier Identifier Address Identifier Size Object Object ... - 0 1 2 3 4 5 6 7 8 - classcode attrcode/ invocation invocation funccode size __class__ attribute ... - instance reference #args, reference reference - status defaults, - * parameter - details + 0 1 2 3 4 5 6 7 + classcode attrcode/ invocation funccode size __class__ attribute ... + instance reference reference reference + status Classcode --------- @@ -221,7 +219,8 @@ removed and the attrcode is not specified for classes: the presence of an attrcode indicates that a given object is an instance. -See below for details of attrcodes. +See the "Testing Instance Compatibility with Classes (Attrcode)" section below +for details of attrcodes. Invocation Reference -------------------- @@ -231,15 +230,20 @@ This is the address of the code to be executed when an invocation is performed on the object. -Invocation Arguments --------------------- +Funccode +-------- -Used when an object is called. +Used to look up argument positions by name. -The argument details consist of the number of positional arguments involved in -an invocation, the number of defaults available to compensate for missing -arguments, and whether a star (*) parameter is available to accept superfluous -arguments. +The strategy with keyword arguments in micropython is to attempt to position +such arguments in the invocation frame as it is being constructed. + +See the "Parameters and Lookups" section for more information. + +Size +---- + +Used to indicate the number of attributes associated with an object. Attributes ---------- @@ -254,37 +258,31 @@ Class C: - 0 1 2 3 4 5 6 7 8 - classcode (unused) __new__ __new__ funccode size class type attribute ... - for C reference #args, for reference reference - defaults, instantiator - * parameter - details + 0 1 2 3 4 5 6 7 + classcode (unused) __new__ funccode size class type attribute ... + for C reference for reference reference + instantiator Instance of C: - 0 1 2 3 4 5 6 7 8 - classcode attrcode C.__call__ C.__call__ funccode size class C attribute ... - for C for C reference #args, for reference reference - (if exists) defaults, C.__call__ - * parameter - details + 0 1 2 3 4 5 6 7 + classcode attrcode C.__call__ funccode size class C attribute ... + for C for C reference for reference reference + (if exists) C.__call__ Function f: - 0 1 2 3 4 5 6 7 8 - classcode attrcode code code funccode size class attribute ... - for for reference #args, function (default) - function function defaults, reference reference - * parameter - details + 0 1 2 3 4 5 6 7 + classcode attrcode code funccode size class attribute ... + for for reference function (default) + function function reference reference Module m: - 0 1 2 3 4 5 6 7 8 - classcode attrcode (unused) (unused) module type attribute ... - for m for m reference (global) - reference + 0 1 2 3 4 5 6 7 + classcode attrcode (unused) module type attribute ... + for m for m reference (global) + reference The __class__ Attribute ----------------------- diff -r 398f466a7011 -r 626da7ae6d5e micropython/__init__.py --- a/micropython/__init__.py Sat May 16 01:34:03 2009 +0200 +++ b/micropython/__init__.py Sun May 17 00:58:27 2009 +0200 @@ -210,6 +210,8 @@ # Position the objects. pos = 0 + current_function = None + for item in self.code: # Blocks are positioned leaving space for their expansion. @@ -218,6 +220,14 @@ item.location = pos pos += len(item.code) + # Set code body information on functions, assuming that the + # first block is for argument checks. + + if current_function is not None: + current_function.code_body_location = pos + + current_function = None + # Other multi-location objects. elif isinstance(item, ( @@ -253,8 +263,14 @@ else: item.code_location = pos + len(item.defaults) + current_function = item + elif isinstance(item, micropython.data.Const): pos += len(item.raw_data()) + current_function = None + + else: + current_function = None else: pos += 1 diff -r 398f466a7011 -r 626da7ae6d5e micropython/ast.py --- a/micropython/ast.py Sat May 16 01:34:03 2009 +0200 +++ b/micropython/ast.py Sun May 17 00:58:27 2009 +0200 @@ -593,15 +593,32 @@ # Visiting of the code occurs when get_code is invoked on this node. else: + body_block = self.new_block() + + # Check frames using the function's details. + + fn = node.unit + nparams = len(fn.positional_names) + ndefaults = len(fn.defaults) + + self.new_op(CheckFrame((nparams, ndefaults, fn.has_star))) + self.new_op(FillDefaults((nparams, ndefaults))) + + # Produce the body. + + self.set_block(body_block) + extend = ExtendFrame() self.new_op(extend) self.dispatch(node.code) + if not isinstance(self.last_op(), Return): self.dispatch(compiler.ast.Name("None")) self.new_op(StoreResult()) + self.new_op(Return()) - self.new_op(Return()) + # Make sure that enough frame space is reserved from the start. self.set_frame_usage(node, extend) @@ -645,10 +662,33 @@ # Visiting of the code occurs when get_code is invoked on this node. else: + body_block = self.new_block() + + # Check frames using the function's details. + + fn = node.unit + nparams = len(fn.positional_names) + ndefaults = len(fn.defaults) + + self.new_op(CheckFrame((nparams, ndefaults, fn.has_star))) + self.new_op(FillDefaults((nparams, ndefaults))) + + # Produce the body. + + self.set_block(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) + def visitModule(self, node): extend = ExtendFrame() self.new_op(extend) diff -r 398f466a7011 -r 626da7ae6d5e micropython/data.py --- a/micropython/data.py Sat May 16 01:34:03 2009 +0200 +++ b/micropython/data.py Sun May 17 00:58:27 2009 +0200 @@ -605,6 +605,9 @@ ) ] + def get_direct_invocation_location(self): + return self.get_instantiator().blocks[0].location + # Namespace-related methods. def get_updated_context_values(self, context_values): @@ -935,6 +938,7 @@ self.location = None self.code_location = None + self.code_body_location = None # Program-related details. @@ -955,8 +959,9 @@ def __repr__(self): if self.location is not None: - return "Function(%r, %s, %r, location=%r, code_location=%r)" % ( - self.name, shortrepr(self.parent), self.argnames, self.location, self.code_location + return "Function(%r, %s, %r, location=%r, code_location=%r, code_body_location=%r)" % ( + self.name, shortrepr(self.parent), self.argnames, self.location, self.code_location, + self.code_body_location ) else: return "Function(%r, %s, %r)" % ( @@ -986,6 +991,9 @@ ) ] + def get_direct_invocation_location(self): + return self.code_body_location + # Namespace-related methods. def store_default(self, value): diff -r 398f466a7011 -r 626da7ae6d5e micropython/rsvp.py --- a/micropython/rsvp.py Sat May 16 01:34:03 2009 +0200 +++ b/micropython/rsvp.py Sun May 17 00:58:27 2009 +0200 @@ -128,6 +128,18 @@ Address = AddressInstruction +class TargetInstruction(Instruction): + + "An instruction loading the address of an invocation target." + + def __repr__(self): + return "%s(%r) # %r" % (self.__class__.__name__, self.get_operand(), name(self.attr)) + + def get_operand(self): + return self.attr.get_direct_invocation_location() + +Target = TargetInstruction + class ImmediateInstruction(Instruction): "An instruction employing a constant." @@ -187,12 +199,17 @@ class StoreFrame(Immediate): "Store the current value as an argument for the parameter with the given position." class StoreFrameIndex(Immediate): "Store the source value as an argument of the current value for the parameter with the given index." class LoadContext(Instruction): "Load the context of an invocation." +class CheckSelf(Instruction): "Check the first argument of an invocation against the target." + +# Access to invocation frames upon dispatch. + class CheckFrame(Immediate): "Check the invocation frame and context for the target." -class CheckSelf(Instruction): "Check the first argument of an invocation against the target." +class FillDefaults(Immediate): "Fill frame positions with defaults, if appropriate." # Invocation-related instructions, using a special result "register". class JumpWithFrame(Instruction): "Jump, adopting the invocation frame, to the current callable." +class JumpWithFrameDirect(Target): "Jump to the specified address, adopting the invocation frame." class ExtendFrame(Immediate): "Extend the current frame for temporary storage use." class AdjustFrame(Immediate): "Adjust the current frame for corrected invocations." class Return(Instruction): "Return from a subprogram." diff -r 398f466a7011 -r 626da7ae6d5e micropython/trans.py --- a/micropython/trans.py Sat May 16 01:34:03 2009 +0200 +++ b/micropython/trans.py Sun May 17 00:58:27 2009 +0200 @@ -740,19 +740,10 @@ self._endCallFuncArgs(frame_size) - # Or generate instructions to do this at run-time. + # Or just set the frame size and have the function check the arguments. else: max_pos = max(max(employed_positions or [-1]), max_keyword_pos, frame_pos - 1) - - # Only check non-empty frames (using the callable's details). - - if employed_positions or max_pos >= 0: - self.new_op(temp) - self.new_op(CheckFrame(max_pos + 1)) - - # Set the frame size. - self._endCallFuncArgs(max_pos + 1) def _generateCallFuncDefaultArgs(self, target, temp, nargs_min, nargs_max, employed_positions): @@ -788,13 +779,12 @@ # For classes, the target itself is used, since the instantiator will be # obtained via the class. - if isinstance(target, Class): - self.new_op(LoadConst(target)) + if isinstance(target, (Class, Function)): + self.new_op(JumpWithFrameDirect(target)) else: self.new_op(instruction) - - self.new_op(LoadCallable()) - self.new_op(JumpWithFrame()) + self.new_op(LoadCallable()) + self.new_op(JumpWithFrame()) def _endCallFuncArgs(self, nargs): diff -r 398f466a7011 -r 626da7ae6d5e rsvp.py --- a/rsvp.py Sat May 16 01:34:03 2009 +0200 +++ b/rsvp.py Sun May 17 00:58:27 2009 +0200 @@ -486,40 +486,51 @@ self.value = None, context # context of context is not interesting def CheckFrame(self): - operand = self.operand - frame = self.invocation_sp_stack[-1] - context, ref = self.value - data = self.load(ref) + (nargs, ndefaults, has_star) = self.operand + + # The frame is actually installed as the locals. + # Retrieve the context from the first local. + + frame = self.local_sp_stack[-1] + context, ref = self.frame_stack[frame] # + 0 + nlocals = len(self.frame_stack[frame:]) # Support sliding of the frame to exclude any inappropriate context. if context is None: - self.invocation_sp_stack[-1] += 1 - operand -= 1 + self.local_sp_stack[-1] += 1 + nlocals -= 1 else: context_data = self.load(context) if context_data.attrcode is None: # absent attrcode == class - self.invocation_sp_stack[-1] += 1 - operand -= 1 + self.local_sp_stack[-1] += 1 + nlocals -= 1 # Test the frame size. + # NOTE: Raise a proper exception here. - nargs, ndefaults = data.codedetails - if not ((nargs - ndefaults) <= operand <= nargs): - raise Exception, "CheckFrame %r (%r <= %r <= %r)" % (self.operand, nargs - ndefaults, operand, nargs) + if not ((nargs - ndefaults) <= nlocals and (nlocals <= nargs or has_star)): + raise Exception, "CheckFrame %r (%r <= %r <= %r)" % (self.operand, nargs - ndefaults, nlocals, nargs) + + def FillDefaults(self): + (nargs, ndefaults) = self.operand + + # The frame is actually installed as the locals. + + frame = self.local_sp_stack[-1] + nlocals = len(self.frame_stack[frame:]) # Support population of defaults. # This involves copying the "attributes" of a function into the frame. - default = operand - (nargs - ndefaults) - self.frame_stack.extend([None] * (nargs - operand)) - pos = self.operand + default = nlocals - (nargs - ndefaults) + self.frame_stack.extend([None] * (nargs - nlocals)) + pos = nlocals - while operand < nargs: + while pos < nargs: self.frame_stack[frame + pos] = self.load(ref + default + 1) # skip header default += 1 pos += 1 - operand += 1 def CheckSelf(self): context, ref = self.value @@ -534,6 +545,11 @@ self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame return self.jump(codeaddr, self.pc + 1) # return to the instruction after this one + def JumpWithFrameDirect(self): + operand = self.operand + self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame + return self.jump(operand, self.pc + 1) # return to the instruction after this one + def ExtendFrame(self): self.frame_stack.extend([None] * self.operand) diff -r 398f466a7011 -r 626da7ae6d5e tests/call_func.py --- a/tests/call_func.py Sat May 16 01:34:03 2009 +0200 +++ b/tests/call_func.py Sun May 17 00:58:27 2009 +0200 @@ -1,10 +1,10 @@ #!/usr/bin/env python def f(a, b, c): - pass + return c -f(1, 2, 3) -f(1, b=2, c=3) -f(c=3, b=2, a=1) +x = f(1, 2, 3) +y = f(1, b=2, c=3) +z = f(c=3, b=2, a=1) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 398f466a7011 -r 626da7ae6d5e tests/call_func_uncertain.py --- a/tests/call_func_uncertain.py Sat May 16 01:34:03 2009 +0200 +++ b/tests/call_func_uncertain.py Sun May 17 00:58:27 2009 +0200 @@ -1,14 +1,14 @@ #!/usr/bin/env python def f(a, b, c): - pass + return c g = f -g(1, c=3, b=2) +x = g(1, c=3, b=2) def g(a, c, b): - pass + return c -g(1, c=3, b=2) +y = g(1, c=3, b=2) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 398f466a7011 -r 626da7ae6d5e tests/failure/argument_surplus.py --- a/tests/failure/argument_surplus.py Sat May 16 01:34:03 2009 +0200 +++ b/tests/failure/argument_surplus.py Sun May 17 00:58:27 2009 +0200 @@ -3,14 +3,6 @@ def f(a, b, c): pass -g = f -g(1, 2, 3, 4) # uncertain target - not detected - -def g(a, c, b): - pass - -g(1, a=3, b=2) - f(1, 2, 3) f(1, 2, 3, 4) diff -r 398f466a7011 -r 626da7ae6d5e tests/failure/argument_surplus_uncertain.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/failure/argument_surplus_uncertain.py Sun May 17 00:58:27 2009 +0200 @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +def f(): + pass + +g = f + +def g(a, c, b): + pass + +g(1, a=3, b=2) +g(1, 2, 3, 4) # uncertain target - not detected + +# vim: tabstop=4 expandtab shiftwidth=4