

"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 
