[Python-Dev] Accessing globals without dict lookup

Jeremy Hylton jeremy@alum.mit.edu
Sat, 9 Feb 2002 19:04:19 -0500


Here's a brief review of the example function.

def mylen(s):
    return len(s)

LOAD_BUILTIN       0 (len)
LOAD_FAST          0 (s)
CALL_FUNCTION      1
RETURN_VALUE

The interpreter has a dlict for all the builtins.  The details don't
matter here.  Let's say that len is at index 4.

The function mylen has an array:
func_builtin_index = [4]  # an index for each builtin used in mylen

The entry at index 0 of func_builtin_index is the index of len in the
interpreter's builtin dlict.  It is either initialized when the
function is created or on first use of len.  (It doesn't matter for
the mechanism and there's no need to decide which is better yet.)

The module has an md_globals_dirty flag.  If it is true, then a
global was introduced dynamically, i.e. a name binding op occurred
that the compiler did not detect statically.

The code object has a co_builtin_names that is like co_names except
that it only contains the names of builtins used by LOAD_BUILTIN.
It's there to get the correct behavior when shadowing of a builtin by
a local occurs at runtime.

The frame grows a bunch of pointers -- 

    f_module from the function (which stores it instead of func_globals)
    f_builtin_names from the code object
    f_builtins from the interpreter

The implementation of LOAD_BUILTIN 0 is straightforward -- in pidgin C:

case LOAD_BUILTIN:
    if (f->f_module->md_globals_dirty) {
        PyObject *w = PyTuple_GET_ITEM(f->f_builtin_names);
        ... /* rest is just like current LOAD_GLOBAL 
               except that is used PyDLict_GetItem()
             */
    } else {
        int builtin_index = f->f_builtin_index[oparg];
        PyObject *x = f->f_builtins[builtin_index];
        if (x == NULL)  
           raise NameError
        Py_INCREF(x);
        PUSH(x);
    }

The LOAD_GLOBAL opcode ends up looking basically the same, except that
it doesn't need to check md_globals_dirty.

case LOAD_GLOBAL:
    int global_index = f->f_global_index[oparg];
    PyObject *x = f->f_module->md_globals[global_index];
    if (x == NULL) {
       check for dynamically introduced builtin
    }
    Py_INCREF(x);
    PUSH(x);

In the x == NULL case above, we need to take extra care for a builtin
that the compiler didn't expect.  It's an odd case.  There is a
global for the module named spam that hasn't yet been assigned to in
the module and there's also a builtin named spam that will be hidden
once spam is bound in the module.

Jeremy