# HG changeset patch # User Paul Boddie # Date 1693783148 -7200 # Node ID ee4709557b91c53c8b4b12bfe3c3e622c463d6c2 # Parent d5feb616f63774cbb0ae520f495c845d664344e9 Introduced a separate access plan abstraction. diff -r d5feb616f637 -r ee4709557b91 access_plan.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/access_plan.py Mon Sep 04 01:19:08 2023 +0200 @@ -0,0 +1,508 @@ +#!/usr/bin/env python + +""" +Attribute access plan translation. + +Copyright (C) 2014-2018, 2023 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from encoders import encode_access_location + +class AccessPlan: + + "An attribute access plan." + + def __init__(self, name, test, test_type, base, traversed, traversal_modes, + remaining, context, context_test, first_method, final_method, + origin, accessor_kinds): + + "Initialise the plan." + + # With instance attribute initialisers, the assignment below would be + # generated automatically. + + ( + self.name, self.test, self.test_type, self.base, + self.traversed, self.traversal_modes, self.remaining, + self.context, self.context_test, + self.first_method, self.final_method, + self.origin, self.accessor_kinds) = ( + + name, test, test_type, base, + traversed, traversal_modes, remaining, + context, context_test, + first_method, final_method, + origin, accessor_kinds) + + # Define the first attribute access and subsequent accesses. + + self.first_attrname = None + self.traversed_attrnames = traversed + self.traversed_attrname_modes = traversal_modes + self.remaining_attrnames = remaining + + if traversed: + self.first_attrname = traversed[0] + self.traversed_attrnames = traversed[1:] + self.traversed_attrname_modes = traversal_modes[1:] + elif remaining: + self.first_attrname = remaining[0] + self.remaining_attrnames = remaining[1:] + + def access_first_attribute(self): + + "Return whether the first attribute is to be accessed." + + return self.final_method in ("access", "access-invoke", "assign") or \ + self.all_subsequent_attributes() + + def assigning_first_attribute(self): + + "Return whether the first attribute access involves assignment." + + return not self.all_subsequent_attributes() and self.final_method == "assign" + + def get_first_attribute_name(self): + + "Return any first attribute name to be used in an initial access." + + return self.first_attrname + + def all_subsequent_attributes(self): + + "Return all subsequent attribute names involved in accesses." + + return self.traversed_attrnames + self.remaining_attrnames + + def attribute_traversals(self): + + "Return a collection of (attribute name, traversal mode) tuples." + + return zip(self.traversed_attrnames, self.traversed_attrname_modes) + + def stored_accessor(self): + + "Return the variable used to obtain the accessor." + + return self.assigning_first_attribute() and "" or "" + + def stored_accessor_modifier(self): + + "Return the variable used to set the accessor." + + return self.assigning_first_attribute() and "" or "" + + def get_original_accessor(self): + + "Return the original accessor details." + + # Identify any static original accessor. + + if self.base: + return self.base + + # Employ names as contexts unless the context needs testing and + # potentially updating. In such cases, temporary context storage is + # used instead. + + elif self.name and not (self.context_test == "test" and + self.final_method in ("access-invoke", "static-invoke")): + + return "" + + # Use a generic placeholder representing the access expression in + # the general case. + + else: + return "" + + def get_instructions(self): + + "Return a list of instructions corresponding to the plan." + + # Emit instructions by appending them to a list. + + instructions = [] + emit = instructions.append + + # Set up any initial instructions. + + accessor, context = self.process_initialisation(emit) + + # Apply any test. + + if self.test[0] == "test": + test_accessor = accessor = ("__%s_%s_%s" % self.test, accessor, self.test_type) + else: + test_accessor = None + + # Perform the first or final access. + # The access only needs performing if the resulting accessor is used. + + accessor = self.process_first_attribute(accessor, emit) + + # Perform accesses for the traversed and remaining attributes. + + accessor, context = self.process_traversed_attributes(accessor, context, emit) + accessor, context = self.process_remaining_attributes(accessor, context, emit) + + # Make any accessor test available if not emitted. + + test_accessor = not instructions and test_accessor or None + + # Perform the access on the actual target. + + accessor = self.process_attribute_access(accessor, context, test_accessor, emit) + + # Produce an advisory instruction regarding the context. + + self.process_context_identity(context, emit) + + # Produce an advisory instruction regarding the final attribute. + + if self.origin: + emit(("", self.origin)) + + return instructions + + def process_initialisation(self, emit): + + """ + Use 'emit' to generate instructions for any initialisation of attribute + access. Return the potentially revised accessor and context indicators. + """ + + # Identify any static original accessor. + + original_accessor = self.get_original_accessor() + + # Determine whether the first access involves assignment. + + set_accessor = self.stored_accessor_modifier() + stored_accessor = self.stored_accessor() + + # Set the context if already available. + + context = None + + if self.context == "base": + accessor = context = (self.base,) + elif self.context == "original-accessor": + + # Prevent re-evaluation of any dynamic expression by storing it. + + if original_accessor == "": + if self.final_method in ("access-invoke", "static-invoke"): + emit(("", original_accessor)) + accessor = context = ("",) + else: + emit((set_accessor, original_accessor)) + accessor = context = (stored_accessor,) + else: + accessor = context = (original_accessor,) + + # Assigning does not set the context. + + elif self.context in ("final-accessor", "unset") and self.access_first_attribute(): + + # Prevent re-evaluation of any dynamic expression by storing it. + + if original_accessor == "": + emit((set_accessor, original_accessor)) + accessor = (stored_accessor,) + else: + accessor = (original_accessor,) + else: + accessor = None + + return accessor, context + + def process_first_attribute(self, accessor, emit): + + """ + Using 'accessor', use 'emit' to generate instructions for any first + attribute access. Return the potentially revised accessor. + """ + + if self.access_first_attribute(): + attrname = self.get_first_attribute_name() + assigning = self.assigning_first_attribute() + + if self.first_method == "relative-class": + if assigning: + emit(("__store_via_class", accessor, attrname, "")) + else: + accessor = ("__load_via_class", accessor, attrname) + + elif self.first_method == "relative-object": + if assigning: + emit(("__store_via_object", accessor, attrname, "")) + else: + accessor = ("__load_via_object", accessor, attrname) + + elif self.first_method == "relative-object-class": + if assigning: + emit(("__get_class_and_store", accessor, attrname, "")) + else: + accessor = ("__get_class_and_load", accessor, attrname) + + elif self.first_method == "check-class": + if assigning: + emit(("__check_and_store_via_class", accessor, attrname, "")) + else: + accessor = ("__check_and_load_via_class", accessor, attrname) + + elif self.first_method == "check-object": + if assigning: + emit(("__check_and_store_via_object", accessor, attrname, "")) + else: + accessor = ("__check_and_load_via_object", accessor, attrname) + + elif self.first_method == "check-object-class": + if assigning: + emit(("__check_and_store_via_any", accessor, attrname, "")) + else: + accessor = ("__check_and_load_via_any", accessor, attrname) + + return accessor + + def process_traversed_attributes(self, accessor, context, emit): + + """ + Using 'accessor' and 'context', use 'emit' to generate instructions + for the traversed attribute accesses. Return the potentially revised + accessor and context indicators. + """ + + # Traverse attributes using the accessor. + + num_remaining = len(self.all_subsequent_attributes()) + + if self.traversed_attrnames: + for attrname, traversal_mode in self.attribute_traversals(): + assigning = num_remaining == 1 and self.final_method == "assign" + + # Set the context, if appropriate. + + if num_remaining == 1 and self.final_method != "assign" and self.context == "final-accessor": + + # Invoked attributes employ a separate context accessed + # during invocation. + + if self.final_method in ("access-invoke", "static-invoke"): + emit(("", accessor)) + accessor = context = "" + + # A private context within the access is otherwise + # retained. + + else: + emit(("", accessor)) + accessor = context = "" + + # Perform the access only if not achieved directly. + + if num_remaining > 1 or self.final_method in ("access", "access-invoke", "assign"): + + if traversal_mode == "class": + if assigning: + emit(("__store_via_class", accessor, attrname, "")) + else: + accessor = ("__load_via_class", accessor, attrname) + else: + if assigning: + emit(("__store_via_object", accessor, attrname, "")) + else: + accessor = ("__load_via_object", accessor, attrname) + + num_remaining -= 1 + + return accessor, context + + def process_remaining_attributes(self, accessor, context, emit): + + """ + Using 'accessor' and 'context', use 'emit' to generate instructions + for the remaining attribute accesses. Return the potentially revised + accessor and context indicators. + """ + + remaining = self.remaining_attrnames + + if remaining: + num_remaining = len(remaining) + + for attrname in remaining: + assigning = num_remaining == 1 and self.final_method == "assign" + + # Set the context, if appropriate. + + if num_remaining == 1 and self.final_method != "assign" and self.context == "final-accessor": + + # Invoked attributes employ a separate context accessed + # during invocation. + + if self.final_method in ("access-invoke", "static-invoke"): + emit(("", accessor)) + accessor = context = "" + + # A private context within the access is otherwise + # retained. + + else: + emit(("", accessor)) + accessor = context = "" + + # Perform the access only if not achieved directly. + + if num_remaining > 1 or self.final_method in ("access", "access-invoke", "assign"): + + # Constrain instructions involving certain special + # attribute names. + + to_search = attrname == "__data__" and "object" or "any" + + if assigning: + emit(("__check_and_store_via_%s" % to_search, accessor, attrname, "")) + else: + accessor = ("__check_and_load_via_%s" % to_search, accessor, attrname) + + num_remaining -= 1 + + return accessor, context + + def process_attribute_access(self, accessor, context, test_accessor, emit): + + """ + Using 'accessor','context' and any 'test_accessor' operation, use 'emit' + to generate instructions for the final attribute access. Return the + potentially revised accessor. + """ + + # Define or emit the means of accessing the actual target. + + if self.final_method in ("static", "static-assign", "static-invoke"): + + if test_accessor: + emit(test_accessor) + + # Assignments to known attributes. + + if self.final_method == "static-assign": + parent, attrname = self.origin.rsplit(".", 1) + emit(("__store_via_object", parent, attrname, "")) + + # Invoked attributes employ a separate context. + + elif self.final_method in ("static", "static-invoke"): + accessor = ("__load_static_ignore", self.origin) + + # Wrap accesses in context operations. + + if self.context_test == "test": + + # Test and combine the context with static attribute details. + + if self.final_method == "static": + emit(("__load_static_test", context, self.origin)) + + # Test the context, storing it separately if required for the + # immediately invoked static attribute. + + elif self.final_method == "static-invoke": + emit(("", context, self.origin)) + + # Test the context, storing it separately if required for an + # immediately invoked attribute. + + elif self.final_method == "access-invoke": + emit(("", context, accessor)) + + # Test the context and update the attribute details if + # appropriate. + + else: + emit(("__test_context", context, accessor)) + + elif self.context_test == "replace": + + # Produce an object with updated context. + + if self.final_method == "static": + emit(("__load_static_replace", context, self.origin)) + + # Omit the context update operation where the target is static + # and the context is recorded separately. + + elif self.final_method == "static-invoke": + pass + + # If a separate context is used for an immediate invocation, + # produce the attribute details unchanged. + + elif self.final_method == "access-invoke": + emit(accessor) + + # Update the context in the attribute details. + + else: + emit(("__update_context", context, accessor)) + + # Omit the accessor for assignments and for invocations of static + # targets. Otherwise, emit the accessor which may involve the + # invocation of a test. + + elif self.final_method not in ("assign", "static-assign", "static-invoke"): + emit(accessor) + + return accessor + + def process_context_identity(self, context, emit): + + """ + Using 'context', use 'emit' to generate instructions to test the context + identity. + """ + + if context: + + # Only verify the context for invocation purposes if a suitable + # test has been performed. + + if self.context_test in ("ignore", "replace") or \ + self.final_method in ("access-invoke", "static-invoke"): + + emit(("", context)) + else: + emit(("", context)) + + def write(self, f, location): + + "Write the plan to file 'f' with the given 'location' information." + + print >>f, encode_access_location(location), \ + self.name or "{}", \ + self.test and "-".join(self.test) or "{}", \ + self.test_type or "{}", \ + self.base or "{}", \ + ".".join(self.traversed_attrnames) or "{}", \ + ".".join(self.traversed_attrname_modes) or "{}", \ + ".".join(self.remaining_attrnames) or "{}", \ + self.context, self.context_test, \ + self.first_method, self.final_method, self.origin or "{}", \ + ",".join(self.accessor_kinds) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r d5feb616f637 -r ee4709557b91 deducer.py --- a/deducer.py Sun Sep 03 21:25:51 2023 +0200 +++ b/deducer.py Mon Sep 04 01:19:08 2023 +0200 @@ -19,6 +19,7 @@ this program. If not, see . """ +from access_plan import AccessPlan from common import first, get_assigned_attributes, get_attrnames, \ get_invoked_attributes, get_name_path, init_item, \ order_dependencies_partial, sorted_output, \ @@ -2631,367 +2632,8 @@ "Expand access plans into instruction sequences." - for access_location, p in self.access_plans.items(): - - # Emit instructions by appending them to a list. - - instructions = [] - emit = instructions.append - - # Identify any static original accessor. - - original_accessor = p.get_original_accessor() - - # Prepare for any first attribute access. - - traversed = p.traversed - traversal_modes = p.traversal_modes - remaining = p.remaining - - if traversed: - attrname = traversed[0] - del traversed[0] - del traversal_modes[0] - elif remaining: - attrname = remaining[0] - del remaining[0] - - # Perform the first access explicitly if at least one operation - # requires it. - - access_first_attribute = p.final_method in ("access", "access-invoke", "assign") or traversed or remaining - - # Determine whether the first access involves assignment. - - assigning = not traversed and not remaining and p.final_method == "assign" - set_accessor = assigning and "" or "" - stored_accessor = assigning and "" or "" - - # Set the context if already available. - - context_var = None - - if p.context == "base": - accessor = context_var = (p.base,) - elif p.context == "original-accessor": - - # Prevent re-evaluation of any dynamic expression by storing it. - - if original_accessor == "": - if p.final_method in ("access-invoke", "static-invoke"): - emit(("", original_accessor)) - accessor = context_var = ("",) - else: - emit((set_accessor, original_accessor)) - accessor = context_var = (stored_accessor,) - else: - accessor = context_var = (original_accessor,) - - # Assigning does not set the context. - - elif p.context in ("final-accessor", "unset") and access_first_attribute: - - # Prevent re-evaluation of any dynamic expression by storing it. - - if original_accessor == "": - emit((set_accessor, original_accessor)) - accessor = (stored_accessor,) - else: - accessor = (original_accessor,) - else: - accessor = None - - # Apply any test. - - if p.test[0] == "test": - test_accessor = accessor = ("__%s_%s_%s" % p.test, accessor, p.test_type) - else: - test_accessor = None - - # Perform the first or final access. - # The access only needs performing if the resulting accessor is used. - - num_remaining = len(traversed + remaining) - - if access_first_attribute: - - if p.first_method == "relative-class": - if assigning: - emit(("__store_via_class", accessor, attrname, "")) - else: - accessor = ("__load_via_class", accessor, attrname) - - elif p.first_method == "relative-object": - if assigning: - emit(("__store_via_object", accessor, attrname, "")) - else: - accessor = ("__load_via_object", accessor, attrname) - - elif p.first_method == "relative-object-class": - if assigning: - emit(("__get_class_and_store", accessor, attrname, "")) - else: - accessor = ("__get_class_and_load", accessor, attrname) - - elif p.first_method == "check-class": - if assigning: - emit(("__check_and_store_via_class", accessor, attrname, "")) - else: - accessor = ("__check_and_load_via_class", accessor, attrname) - - elif p.first_method == "check-object": - if assigning: - emit(("__check_and_store_via_object", accessor, attrname, "")) - else: - accessor = ("__check_and_load_via_object", accessor, attrname) - - elif p.first_method == "check-object-class": - if assigning: - emit(("__check_and_store_via_any", accessor, attrname, "")) - else: - accessor = ("__check_and_load_via_any", accessor, attrname) - - # Traverse attributes using the accessor. - - if traversed: - for attrname, traversal_mode in zip(traversed, p.traversal_modes): - assigning = num_remaining == 1 and p.final_method == "assign" - - # Set the context, if appropriate. - - if num_remaining == 1 and p.final_method != "assign" and p.context == "final-accessor": - - # Invoked attributes employ a separate context accessed - # during invocation. - - if p.final_method in ("access-invoke", "static-invoke"): - emit(("", accessor)) - accessor = context_var = "" - - # A private context within the access is otherwise - # retained. - - else: - emit(("", accessor)) - accessor = context_var = "" - - # Perform the access only if not achieved directly. - - if num_remaining > 1 or p.final_method in ("access", "access-invoke", "assign"): - - if traversal_mode == "class": - if assigning: - emit(("__store_via_class", accessor, attrname, "")) - else: - accessor = ("__load_via_class", accessor, attrname) - else: - if assigning: - emit(("__store_via_object", accessor, attrname, "")) - else: - accessor = ("__load_via_object", accessor, attrname) - - num_remaining -= 1 - - if remaining: - for attrname in remaining: - assigning = num_remaining == 1 and p.final_method == "assign" - - # Set the context, if appropriate. - - if num_remaining == 1 and p.final_method != "assign" and p.context == "final-accessor": - - # Invoked attributes employ a separate context accessed - # during invocation. - - if p.final_method in ("access-invoke", "static-invoke"): - emit(("", accessor)) - accessor = context_var = "" - - # A private context within the access is otherwise - # retained. - - else: - emit(("", accessor)) - accessor = context_var = "" - - # Perform the access only if not achieved directly. - - if num_remaining > 1 or p.final_method in ("access", "access-invoke", "assign"): - - # Constrain instructions involving certain special - # attribute names. - - to_search = attrname == "__data__" and "object" or "any" - - if assigning: - emit(("__check_and_store_via_%s" % to_search, accessor, attrname, "")) - else: - accessor = ("__check_and_load_via_%s" % to_search, accessor, attrname) - - num_remaining -= 1 - - # Make any accessor test available if not emitted. - - test_accessor = not instructions and test_accessor or None - - # Define or emit the means of accessing the actual target. - - if p.final_method in ("static", "static-assign", "static-invoke"): - - if test_accessor: - emit(test_accessor) - - # Assignments to known attributes. - - if p.final_method == "static-assign": - parent, attrname = p.origin.rsplit(".", 1) - emit(("__store_via_object", parent, attrname, "")) - - # Invoked attributes employ a separate context. - - elif p.final_method in ("static", "static-invoke"): - accessor = ("__load_static_ignore", p.origin) - - # Wrap accesses in context operations. - - if p.context_test == "test": - - # Test and combine the context with static attribute details. - - if p.final_method == "static": - emit(("__load_static_test", context_var, p.origin)) - - # Test the context, storing it separately if required for the - # immediately invoked static attribute. - - elif p.final_method == "static-invoke": - emit(("", context_var, p.origin)) - - # Test the context, storing it separately if required for an - # immediately invoked attribute. - - elif p.final_method == "access-invoke": - emit(("", context_var, accessor)) - - # Test the context and update the attribute details if - # appropriate. - - else: - emit(("__test_context", context_var, accessor)) - - elif p.context_test == "replace": - - # Produce an object with updated context. - - if p.final_method == "static": - emit(("__load_static_replace", context_var, p.origin)) - - # Omit the context update operation where the target is static - # and the context is recorded separately. - - elif p.final_method == "static-invoke": - pass - - # If a separate context is used for an immediate invocation, - # produce the attribute details unchanged. - - elif p.final_method == "access-invoke": - emit(accessor) - - # Update the context in the attribute details. - - else: - emit(("__update_context", context_var, accessor)) - - # Omit the accessor for assignments and for invocations of static - # targets. Otherwise, emit the accessor which may involve the - # invocation of a test. - - elif p.final_method not in ("assign", "static-assign", "static-invoke"): - emit(accessor) - - # Produce an advisory instruction regarding the context. - - if context_var: - - # Only verify the context for invocation purposes if a suitable - # test has been performed. - - if p.context_test in ("ignore", "replace") or \ - p.final_method in ("access-invoke", "static-invoke"): - - emit(("", context_var)) - else: - emit(("", context_var)) - - # Produce an advisory instruction regarding the final attribute. - - if p.origin: - emit(("", p.origin)) - - self.access_instructions[access_location] = instructions - self.accessor_kinds[access_location] = p.accessor_kinds - -class AccessPlan: - - "An access plan." - - def __init__(self, name, test, test_type, base, traversed, traversal_modes, - remaining, context, context_test, first_method, final_method, - origin, accessor_kinds): - - "Initialise the plan." - - (self.name, self.test, self.test_type, self.base, - self.traversed, self.traversal_modes, self.remaining, - self.context, self.context_test, - self.first_method, self.final_method, - self.origin, self.accessor_kinds) = ( - - name, test, test_type, base, - traversed, traversal_modes, remaining, - context, context_test, - first_method, final_method, - origin, accessor_kinds) - - def get_original_accessor(self): - - "Return the original accessor details." - - # Identify any static original accessor. - - if self.base: - return self.base - - # Employ names as contexts unless the context needs testing and - # potentially updating. In such cases, temporary context storage is - # used instead. - - elif self.name and not (self.context_test == "test" and - self.final_method in ("access-invoke", "static-invoke")): - - return "" - - # Use a generic placeholder representing the access expression in - # the general case. - - else: - return "" - - def write(self, f, location): - - "Write the plan to file 'f' with the given 'location' information." - - print >>f, encode_access_location(location), \ - self.name or "{}", \ - self.test and "-".join(self.test) or "{}", \ - self.test_type or "{}", \ - self.base or "{}", \ - ".".join(self.traversed) or "{}", \ - ".".join(self.traversal_modes) or "{}", \ - ".".join(self.remaining) or "{}", \ - self.context, self.context_test, \ - self.first_method, self.final_method, self.origin or "{}", \ - ",".join(self.accessor_kinds) + for access_location, access_plan in self.access_plans.items(): + self.access_instructions[access_location] = access_plan.get_instructions() + self.accessor_kinds[access_location] = access_plan.accessor_kinds # vim: tabstop=4 expandtab shiftwidth=4