PyPy
PyPy[chained_getattr]

"chained getattr/module global lookup" optimization (discussion during trillke-sprint 2007, anto/holger, a bit of samuele and cf earlier on)

random example:

code:
import os.path normed = [os.path.normpath(p) for p in somelist]
bytecode:
[...]
LOAD_GLOBAL (os) LOAD_ATTR (path) LOAD_ATTR (normpath) LOAD_FAST (p) CALL_FUNCTION 1

would be turned by pypy-compiler into:

LOAD_CHAINED_GLOBAL (os,path,normpath) LOAD_FAST (p) CALL_FUNCTION 1

now for the LOAD_CHAINED_GLOBAL bytecode implementation:

Module dicts have a special implemnetation, providing:

  • an extra "fastlookup" rpython-dict serving as a cache for LOAD_CHAINED_GLOBAL places within the modules:

    • keys are e.g. ('os', 'path', 'normpath')

    • values are tuples of the form: ([obj1, obj2, obj3], [ver1, ver2])

      "ver1" refer to the version of the globals of "os" "ver2" refer to the version of the globals of "os.path" "obj3" is the resulting "normpath" function

  • upon changes to the global dict, "fastlookup.clear()" is called

  • after the fastlookup entry is filled for a given LOAD_CHAINED_GLOBAL index, the following checks need to be performed in the bytecode implementation:

      value = f_globals.fastlookup.get(key, None)
      if value is None:
         # fill entry
      else:
          # check that our cached lookups are still valid
          assert isinstance(value, tuple)
          objects, versions = value
          i = 0
          while i < len(versions):
              lastversion = versions[i]
              ver = getver_for_obj(objects[i])
              if ver == -1 or ver != lastversion:
                 name = key[i]
                 objects[i] = space.getattr(curobj, name)
                 versions[i] = ver
              curobj = objects[i]
              i += 1
      return objects[i]
    
    def getver_for_obj(obj):
        if "obj is not Module":
            return -1
        return obj.w_dict.version