1.1 --- a/deducer.py Sun Sep 03 21:25:51 2023 +0200
1.2 +++ b/deducer.py Mon Sep 04 01:19:08 2023 +0200
1.3 @@ -19,6 +19,7 @@
1.4 this program. If not, see <http://www.gnu.org/licenses/>.
1.5 """
1.6
1.7 +from access_plan import AccessPlan
1.8 from common import first, get_assigned_attributes, get_attrnames, \
1.9 get_invoked_attributes, get_name_path, init_item, \
1.10 order_dependencies_partial, sorted_output, \
1.11 @@ -2631,367 +2632,8 @@
1.12
1.13 "Expand access plans into instruction sequences."
1.14
1.15 - for access_location, p in self.access_plans.items():
1.16 -
1.17 - # Emit instructions by appending them to a list.
1.18 -
1.19 - instructions = []
1.20 - emit = instructions.append
1.21 -
1.22 - # Identify any static original accessor.
1.23 -
1.24 - original_accessor = p.get_original_accessor()
1.25 -
1.26 - # Prepare for any first attribute access.
1.27 -
1.28 - traversed = p.traversed
1.29 - traversal_modes = p.traversal_modes
1.30 - remaining = p.remaining
1.31 -
1.32 - if traversed:
1.33 - attrname = traversed[0]
1.34 - del traversed[0]
1.35 - del traversal_modes[0]
1.36 - elif remaining:
1.37 - attrname = remaining[0]
1.38 - del remaining[0]
1.39 -
1.40 - # Perform the first access explicitly if at least one operation
1.41 - # requires it.
1.42 -
1.43 - access_first_attribute = p.final_method in ("access", "access-invoke", "assign") or traversed or remaining
1.44 -
1.45 - # Determine whether the first access involves assignment.
1.46 -
1.47 - assigning = not traversed and not remaining and p.final_method == "assign"
1.48 - set_accessor = assigning and "<set_target_accessor>" or "<set_accessor>"
1.49 - stored_accessor = assigning and "<target_accessor>" or "<accessor>"
1.50 -
1.51 - # Set the context if already available.
1.52 -
1.53 - context_var = None
1.54 -
1.55 - if p.context == "base":
1.56 - accessor = context_var = (p.base,)
1.57 - elif p.context == "original-accessor":
1.58 -
1.59 - # Prevent re-evaluation of any dynamic expression by storing it.
1.60 -
1.61 - if original_accessor == "<expr>":
1.62 - if p.final_method in ("access-invoke", "static-invoke"):
1.63 - emit(("<set_context>", original_accessor))
1.64 - accessor = context_var = ("<context>",)
1.65 - else:
1.66 - emit((set_accessor, original_accessor))
1.67 - accessor = context_var = (stored_accessor,)
1.68 - else:
1.69 - accessor = context_var = (original_accessor,)
1.70 -
1.71 - # Assigning does not set the context.
1.72 -
1.73 - elif p.context in ("final-accessor", "unset") and access_first_attribute:
1.74 -
1.75 - # Prevent re-evaluation of any dynamic expression by storing it.
1.76 -
1.77 - if original_accessor == "<expr>":
1.78 - emit((set_accessor, original_accessor))
1.79 - accessor = (stored_accessor,)
1.80 - else:
1.81 - accessor = (original_accessor,)
1.82 - else:
1.83 - accessor = None
1.84 -
1.85 - # Apply any test.
1.86 -
1.87 - if p.test[0] == "test":
1.88 - test_accessor = accessor = ("__%s_%s_%s" % p.test, accessor, p.test_type)
1.89 - else:
1.90 - test_accessor = None
1.91 -
1.92 - # Perform the first or final access.
1.93 - # The access only needs performing if the resulting accessor is used.
1.94 -
1.95 - num_remaining = len(traversed + remaining)
1.96 -
1.97 - if access_first_attribute:
1.98 -
1.99 - if p.first_method == "relative-class":
1.100 - if assigning:
1.101 - emit(("__store_via_class", accessor, attrname, "<assexpr>"))
1.102 - else:
1.103 - accessor = ("__load_via_class", accessor, attrname)
1.104 -
1.105 - elif p.first_method == "relative-object":
1.106 - if assigning:
1.107 - emit(("__store_via_object", accessor, attrname, "<assexpr>"))
1.108 - else:
1.109 - accessor = ("__load_via_object", accessor, attrname)
1.110 -
1.111 - elif p.first_method == "relative-object-class":
1.112 - if assigning:
1.113 - emit(("__get_class_and_store", accessor, attrname, "<assexpr>"))
1.114 - else:
1.115 - accessor = ("__get_class_and_load", accessor, attrname)
1.116 -
1.117 - elif p.first_method == "check-class":
1.118 - if assigning:
1.119 - emit(("__check_and_store_via_class", accessor, attrname, "<assexpr>"))
1.120 - else:
1.121 - accessor = ("__check_and_load_via_class", accessor, attrname)
1.122 -
1.123 - elif p.first_method == "check-object":
1.124 - if assigning:
1.125 - emit(("__check_and_store_via_object", accessor, attrname, "<assexpr>"))
1.126 - else:
1.127 - accessor = ("__check_and_load_via_object", accessor, attrname)
1.128 -
1.129 - elif p.first_method == "check-object-class":
1.130 - if assigning:
1.131 - emit(("__check_and_store_via_any", accessor, attrname, "<assexpr>"))
1.132 - else:
1.133 - accessor = ("__check_and_load_via_any", accessor, attrname)
1.134 -
1.135 - # Traverse attributes using the accessor.
1.136 -
1.137 - if traversed:
1.138 - for attrname, traversal_mode in zip(traversed, p.traversal_modes):
1.139 - assigning = num_remaining == 1 and p.final_method == "assign"
1.140 -
1.141 - # Set the context, if appropriate.
1.142 -
1.143 - if num_remaining == 1 and p.final_method != "assign" and p.context == "final-accessor":
1.144 -
1.145 - # Invoked attributes employ a separate context accessed
1.146 - # during invocation.
1.147 -
1.148 - if p.final_method in ("access-invoke", "static-invoke"):
1.149 - emit(("<set_context>", accessor))
1.150 - accessor = context_var = "<context>"
1.151 -
1.152 - # A private context within the access is otherwise
1.153 - # retained.
1.154 -
1.155 - else:
1.156 - emit(("<set_private_context>", accessor))
1.157 - accessor = context_var = "<private_context>"
1.158 -
1.159 - # Perform the access only if not achieved directly.
1.160 -
1.161 - if num_remaining > 1 or p.final_method in ("access", "access-invoke", "assign"):
1.162 -
1.163 - if traversal_mode == "class":
1.164 - if assigning:
1.165 - emit(("__store_via_class", accessor, attrname, "<assexpr>"))
1.166 - else:
1.167 - accessor = ("__load_via_class", accessor, attrname)
1.168 - else:
1.169 - if assigning:
1.170 - emit(("__store_via_object", accessor, attrname, "<assexpr>"))
1.171 - else:
1.172 - accessor = ("__load_via_object", accessor, attrname)
1.173 -
1.174 - num_remaining -= 1
1.175 -
1.176 - if remaining:
1.177 - for attrname in remaining:
1.178 - assigning = num_remaining == 1 and p.final_method == "assign"
1.179 -
1.180 - # Set the context, if appropriate.
1.181 -
1.182 - if num_remaining == 1 and p.final_method != "assign" and p.context == "final-accessor":
1.183 -
1.184 - # Invoked attributes employ a separate context accessed
1.185 - # during invocation.
1.186 -
1.187 - if p.final_method in ("access-invoke", "static-invoke"):
1.188 - emit(("<set_context>", accessor))
1.189 - accessor = context_var = "<context>"
1.190 -
1.191 - # A private context within the access is otherwise
1.192 - # retained.
1.193 -
1.194 - else:
1.195 - emit(("<set_private_context>", accessor))
1.196 - accessor = context_var = "<private_context>"
1.197 -
1.198 - # Perform the access only if not achieved directly.
1.199 -
1.200 - if num_remaining > 1 or p.final_method in ("access", "access-invoke", "assign"):
1.201 -
1.202 - # Constrain instructions involving certain special
1.203 - # attribute names.
1.204 -
1.205 - to_search = attrname == "__data__" and "object" or "any"
1.206 -
1.207 - if assigning:
1.208 - emit(("__check_and_store_via_%s" % to_search, accessor, attrname, "<assexpr>"))
1.209 - else:
1.210 - accessor = ("__check_and_load_via_%s" % to_search, accessor, attrname)
1.211 -
1.212 - num_remaining -= 1
1.213 -
1.214 - # Make any accessor test available if not emitted.
1.215 -
1.216 - test_accessor = not instructions and test_accessor or None
1.217 -
1.218 - # Define or emit the means of accessing the actual target.
1.219 -
1.220 - if p.final_method in ("static", "static-assign", "static-invoke"):
1.221 -
1.222 - if test_accessor:
1.223 - emit(test_accessor)
1.224 -
1.225 - # Assignments to known attributes.
1.226 -
1.227 - if p.final_method == "static-assign":
1.228 - parent, attrname = p.origin.rsplit(".", 1)
1.229 - emit(("__store_via_object", parent, attrname, "<assexpr>"))
1.230 -
1.231 - # Invoked attributes employ a separate context.
1.232 -
1.233 - elif p.final_method in ("static", "static-invoke"):
1.234 - accessor = ("__load_static_ignore", p.origin)
1.235 -
1.236 - # Wrap accesses in context operations.
1.237 -
1.238 - if p.context_test == "test":
1.239 -
1.240 - # Test and combine the context with static attribute details.
1.241 -
1.242 - if p.final_method == "static":
1.243 - emit(("__load_static_test", context_var, p.origin))
1.244 -
1.245 - # Test the context, storing it separately if required for the
1.246 - # immediately invoked static attribute.
1.247 -
1.248 - elif p.final_method == "static-invoke":
1.249 - emit(("<test_context_static>", context_var, p.origin))
1.250 -
1.251 - # Test the context, storing it separately if required for an
1.252 - # immediately invoked attribute.
1.253 -
1.254 - elif p.final_method == "access-invoke":
1.255 - emit(("<test_context_revert>", context_var, accessor))
1.256 -
1.257 - # Test the context and update the attribute details if
1.258 - # appropriate.
1.259 -
1.260 - else:
1.261 - emit(("__test_context", context_var, accessor))
1.262 -
1.263 - elif p.context_test == "replace":
1.264 -
1.265 - # Produce an object with updated context.
1.266 -
1.267 - if p.final_method == "static":
1.268 - emit(("__load_static_replace", context_var, p.origin))
1.269 -
1.270 - # Omit the context update operation where the target is static
1.271 - # and the context is recorded separately.
1.272 -
1.273 - elif p.final_method == "static-invoke":
1.274 - pass
1.275 -
1.276 - # If a separate context is used for an immediate invocation,
1.277 - # produce the attribute details unchanged.
1.278 -
1.279 - elif p.final_method == "access-invoke":
1.280 - emit(accessor)
1.281 -
1.282 - # Update the context in the attribute details.
1.283 -
1.284 - else:
1.285 - emit(("__update_context", context_var, accessor))
1.286 -
1.287 - # Omit the accessor for assignments and for invocations of static
1.288 - # targets. Otherwise, emit the accessor which may involve the
1.289 - # invocation of a test.
1.290 -
1.291 - elif p.final_method not in ("assign", "static-assign", "static-invoke"):
1.292 - emit(accessor)
1.293 -
1.294 - # Produce an advisory instruction regarding the context.
1.295 -
1.296 - if context_var:
1.297 -
1.298 - # Only verify the context for invocation purposes if a suitable
1.299 - # test has been performed.
1.300 -
1.301 - if p.context_test in ("ignore", "replace") or \
1.302 - p.final_method in ("access-invoke", "static-invoke"):
1.303 -
1.304 - emit(("<context_identity_verified>", context_var))
1.305 - else:
1.306 - emit(("<context_identity>", context_var))
1.307 -
1.308 - # Produce an advisory instruction regarding the final attribute.
1.309 -
1.310 - if p.origin:
1.311 - emit(("<final_identity>", p.origin))
1.312 -
1.313 - self.access_instructions[access_location] = instructions
1.314 - self.accessor_kinds[access_location] = p.accessor_kinds
1.315 -
1.316 -class AccessPlan:
1.317 -
1.318 - "An access plan."
1.319 -
1.320 - def __init__(self, name, test, test_type, base, traversed, traversal_modes,
1.321 - remaining, context, context_test, first_method, final_method,
1.322 - origin, accessor_kinds):
1.323 -
1.324 - "Initialise the plan."
1.325 -
1.326 - (self.name, self.test, self.test_type, self.base,
1.327 - self.traversed, self.traversal_modes, self.remaining,
1.328 - self.context, self.context_test,
1.329 - self.first_method, self.final_method,
1.330 - self.origin, self.accessor_kinds) = (
1.331 -
1.332 - name, test, test_type, base,
1.333 - traversed, traversal_modes, remaining,
1.334 - context, context_test,
1.335 - first_method, final_method,
1.336 - origin, accessor_kinds)
1.337 -
1.338 - def get_original_accessor(self):
1.339 -
1.340 - "Return the original accessor details."
1.341 -
1.342 - # Identify any static original accessor.
1.343 -
1.344 - if self.base:
1.345 - return self.base
1.346 -
1.347 - # Employ names as contexts unless the context needs testing and
1.348 - # potentially updating. In such cases, temporary context storage is
1.349 - # used instead.
1.350 -
1.351 - elif self.name and not (self.context_test == "test" and
1.352 - self.final_method in ("access-invoke", "static-invoke")):
1.353 -
1.354 - return "<name>"
1.355 -
1.356 - # Use a generic placeholder representing the access expression in
1.357 - # the general case.
1.358 -
1.359 - else:
1.360 - return "<expr>"
1.361 -
1.362 - def write(self, f, location):
1.363 -
1.364 - "Write the plan to file 'f' with the given 'location' information."
1.365 -
1.366 - print >>f, encode_access_location(location), \
1.367 - self.name or "{}", \
1.368 - self.test and "-".join(self.test) or "{}", \
1.369 - self.test_type or "{}", \
1.370 - self.base or "{}", \
1.371 - ".".join(self.traversed) or "{}", \
1.372 - ".".join(self.traversal_modes) or "{}", \
1.373 - ".".join(self.remaining) or "{}", \
1.374 - self.context, self.context_test, \
1.375 - self.first_method, self.final_method, self.origin or "{}", \
1.376 - ",".join(self.accessor_kinds)
1.377 + for access_location, access_plan in self.access_plans.items():
1.378 + self.access_instructions[access_location] = access_plan.get_instructions()
1.379 + self.accessor_kinds[access_location] = access_plan.accessor_kinds
1.380
1.381 # vim: tabstop=4 expandtab shiftwidth=4