Text moved to PEP 339.
This commit is contained in:
parent
5118517c16
commit
3909ff69e2
|
@ -1,507 +0,0 @@
|
|||
Developer Notes for Python Compiler
|
||||
===================================
|
||||
|
||||
Table of Contents
|
||||
-----------------
|
||||
|
||||
- Scope
|
||||
Defines the limits of the change
|
||||
- Parse Trees
|
||||
Describes the local (Python) concept
|
||||
- Abstract Syntax Trees (AST)
|
||||
Describes the AST technology used
|
||||
- Parse Tree to AST
|
||||
Defines the transform approach
|
||||
- Control Flow Graphs
|
||||
Defines the creation of "basic blocks"
|
||||
- AST to CFG to Bytecode
|
||||
Tracks the flow from AST to bytecode
|
||||
- Code Objects
|
||||
Pointer to making bytecode "executable"
|
||||
- Modified Files
|
||||
Files added/modified/removed from CPython compiler
|
||||
- ToDo
|
||||
Work yet remaining (before complete)
|
||||
- References
|
||||
Academic and technical references to technology used.
|
||||
|
||||
|
||||
Scope
|
||||
-----
|
||||
|
||||
Historically (through 2.4), compilation from source code to bytecode
|
||||
involved two steps:
|
||||
|
||||
1. Parse the source code into a parse tree (Parser/pgen.c)
|
||||
2. Emit bytecode based on the parse tree (Python/compile.c)
|
||||
|
||||
Historically, this is not how a standard compiler works. The usual
|
||||
steps for compilation are:
|
||||
|
||||
1. Parse source code into a parse tree (Parser/pgen.c)
|
||||
2. Transform parse tree into an Abstract Syntax Tree (Python/ast.c)
|
||||
3. Transform AST into a Control Flow Graph (Python/newcompile.c)
|
||||
4. Emit bytecode based on the Control Flow Graph (Python/newcompile.c)
|
||||
|
||||
Starting with Python 2.5, the above steps are now used. This change
|
||||
was done to simplify compilation by breaking it into three steps.
|
||||
The purpose of this document is to outline how the lattter three steps
|
||||
of the process works.
|
||||
|
||||
This document does not touch on how parsing works beyond what is needed
|
||||
to explain what is needed for compilation. It is also not exhaustive
|
||||
in terms of the how the entire system works. You will most likely need
|
||||
to read some source to have an exact understanding of all details.
|
||||
|
||||
|
||||
Parse Trees
|
||||
-----------
|
||||
|
||||
Python's parser is an LL(1) parser mostly based off of the
|
||||
implementation laid out in the Dragon Book [Aho86]_.
|
||||
|
||||
The grammar file for Python can be found in Grammar/Grammar with the
|
||||
numeric value of grammar rules are stored in Include/graminit.h. The
|
||||
numeric values for types of tokens (literal tokens, such as ``:``,
|
||||
numbers, etc.) are kept in Include/token.h). The parse tree made up of
|
||||
``node *`` structs (as defined in Include/node.h).
|
||||
|
||||
Querying data from the node structs can be done with the following
|
||||
macros (which are all defined in Include/token.h):
|
||||
|
||||
- ``CHILD(node *, int)``
|
||||
Returns the nth child of the node using zero-offset indexing
|
||||
- ``RCHILD(node *, int)``
|
||||
Returns the nth child of the node from the right side; use
|
||||
negative numbers!
|
||||
- ``NCH(node *)``
|
||||
Number of children the node has
|
||||
- ``STR(node *)``
|
||||
String representation of the node; e.g., will return ``:`` for a
|
||||
COLON token
|
||||
- ``TYPE(node *)``
|
||||
The type of node as specified in ``Include/graminit.h``
|
||||
- ``REQ(node *, TYPE)``
|
||||
Assert that the node is the type that is expected
|
||||
- ``LINENO(node *)``
|
||||
retrieve the line number of the source code that led to the
|
||||
creation of the parse rule; defined in Python/ast.c
|
||||
|
||||
To tie all of this example, consider the rule for 'while'::
|
||||
|
||||
while_stmt: 'while' test ':' suite ['else' ':' suite]
|
||||
|
||||
The node representing this will have ``TYPE(node) == while_stmt`` and
|
||||
the number of children can be 4 or 7 depending on if there is an 'else'
|
||||
statement. To access what should be the first ':' and require it be an
|
||||
actual ':' token, `(REQ(CHILD(node, 2), COLON)``.
|
||||
|
||||
|
||||
Abstract Syntax Trees (AST)
|
||||
---------------------------
|
||||
|
||||
The abstract syntax tree (AST) is a high-level representation of the
|
||||
program structure without the necessity of containing the source code;
|
||||
it can be thought of a abstract representation of the source code. The
|
||||
specification of the AST nodes is specified using the Zephyr Abstract
|
||||
Syntax Definition Language (ASDL) [Wang97]_.
|
||||
|
||||
The definition of the AST nodes for Python is found in the file
|
||||
Parser/Python.asdl .
|
||||
|
||||
Each AST node (representing statements, expressions, and several
|
||||
specialized types, like list comprehensions and exception handlers) is
|
||||
defined by the ASDL. Most definitions in the AST correspond to a
|
||||
particular source construct, such as an 'if' statement or an attribute
|
||||
lookup. The definition is independent of its realization in any
|
||||
particular programming language.
|
||||
|
||||
The following fragment of the Python ASDL construct demonstrates the
|
||||
approach and syntax::
|
||||
|
||||
module Python
|
||||
{
|
||||
stmt = FunctionDef(identifier name, arguments args, stmt* body,
|
||||
expr* decorators)
|
||||
| Return(expr? value) | Yield(expr value)
|
||||
attributes (int lineno)
|
||||
}
|
||||
|
||||
The preceding example describes three different kinds of statements;
|
||||
function definitions, return statements, and yield statements. All
|
||||
three kinds are considered of type stmt as shown by '|' separating the
|
||||
various kinds. They all take arguments of various kinds and amounts.
|
||||
|
||||
Modifiers on the argument type specify the number of values needed; '?'
|
||||
means it is optional, '*' means 0 or more, no modifier means only one
|
||||
value for the argument and it is required. FunctionDef, for instance,
|
||||
takes an identifier for the name, 'arguments' for args, zero or more
|
||||
stmt arguments for 'body', and zero or more expr arguments for
|
||||
'decorators'.
|
||||
|
||||
Do notice that something like 'arguments', which is a node type, is
|
||||
represented as a single AST node and not as a sequence of nodes as with
|
||||
stmt as one might expect.
|
||||
|
||||
All three kinds also have an 'attributes' argument; this is shown by the
|
||||
fact that 'attributes' lacks a '|' before it.
|
||||
|
||||
The statement definitions above generate the following C structure type::
|
||||
|
||||
typedef struct _stmt *stmt_ty;
|
||||
|
||||
struct _stmt {
|
||||
enum { FunctionDef_kind=1, Return_kind=2, Yield_kind=3 } kind;
|
||||
union {
|
||||
struct {
|
||||
identifier name;
|
||||
arguments_ty args;
|
||||
asdl_seq *body;
|
||||
} FunctionDef;
|
||||
|
||||
struct {
|
||||
expr_ty value;
|
||||
} Return;
|
||||
|
||||
struct {
|
||||
expr_ty value;
|
||||
} Yield;
|
||||
} v;
|
||||
int lineno;
|
||||
}
|
||||
|
||||
Also generated are a series of constructor functions that allocate (in
|
||||
this case) a stmt_ty struct with the appropriate initialization. The
|
||||
'kind' field specifies which component of the union is initialized. The
|
||||
FunctionDef() constructor function sets 'kind' to FunctionDef_kind and
|
||||
initializes the 'name', 'args', 'body', and 'attributes' fields.
|
||||
|
||||
*** NOTE: if you make a change here that can affect the output of bytecode that
|
||||
is already in existence, make sure to delete your old .py(c|o) files! Running
|
||||
``find . -name '*.py[co]' -exec rm -f {} ';'`` should do the trick.
|
||||
|
||||
|
||||
Parse Tree to AST
|
||||
-----------------
|
||||
|
||||
The AST is generated from the parse tree in (see Python/ast.c) using the
|
||||
function::
|
||||
|
||||
mod_ty PyAST_FromNode(const node *n);
|
||||
|
||||
The function begins a tree walk of the parse tree, creating various AST
|
||||
nodes as it goes along. It does this by allocating all new nodes it
|
||||
needs, calling the proper AST node creation functions for any required
|
||||
supporting functions, and connecting them as needed.
|
||||
|
||||
Do realize that there is no automated nor symbolic connection between
|
||||
the grammar specification and the nodes in the parse tree. No help is
|
||||
directly provided by the parse tree as in yacc.
|
||||
|
||||
For instance, one must keep track of
|
||||
which node in the parse tree one is working with (e.g., if you are
|
||||
working with an 'if' statement you need to watch out for the ':' token
|
||||
to find the end of the conditional). No help is directly provided by
|
||||
the parse tree as in yacc.
|
||||
|
||||
The functions called to generate AST nodes from the parse tree all have
|
||||
the name ast_for_xx where xx is what the grammar rule that the function
|
||||
handles (alias_for_import_name is the exception to this). These in turn
|
||||
call the constructor functions as defined by the ASDL grammar and
|
||||
contained in Python/Python-ast.c (which was generated by
|
||||
Parser/asdl_c.py) to create the nodes of the AST. This all leads to a
|
||||
sequence of AST nodes stored in asdl_seq structs.
|
||||
|
||||
|
||||
Function and macros for creating and using ``asdl_seq *`` types as found
|
||||
in Python/asdl.c and Include/asdl.h:
|
||||
|
||||
- ``asdl_seq_new(int)``
|
||||
Allocate memory for an asdl_seq for length 'size'
|
||||
- ``asdl_seq_free(asdl_seq *)``
|
||||
Free asdl_seq struct
|
||||
- ``asdl_seq_GET(asdl_seq *seq, int pos)``
|
||||
Get item held at 'pos'
|
||||
- ``asdl_seq_SET(asdl_seq *seq, int pos, void *val)``
|
||||
Set 'pos' in 'seq' to 'val'
|
||||
- ``asdl_seq_APPEND(asdl_seq *seq, void *val)``
|
||||
Set the end of 'seq' to 'val'
|
||||
- ``asdl_seq_LEN(asdl_seq *)``
|
||||
Return the length of 'seq'
|
||||
|
||||
If you are working with statements, you must also worry about keeping
|
||||
track of what line number generated the statement. Currently the line
|
||||
number is passed as the last parameter to each stmt_ty function.
|
||||
|
||||
|
||||
Control Flow Graphs
|
||||
-------------------
|
||||
|
||||
A control flow graph (often referenced by its acronym, CFG) is a
|
||||
directed graph that models the flow of a program using basic blocks that
|
||||
contain the intermediate representation (abbreviated "IR", and in this
|
||||
case is Python bytecode) within the blocks. Basic blocks themselves are
|
||||
a block of IR that has a single entry point but possibly multiple exit
|
||||
points. The single entry point is the key to basic blocks; it all has
|
||||
to do with jumps. An entry point is the target of something that
|
||||
changes control flow (such as a function call or a jump) while exit
|
||||
points are instructions that would change the flow of the program (such
|
||||
as jumps and 'return' statements). What this means is that a basic
|
||||
block is a chunk of code that starts at the entry point and runs to an
|
||||
exit point or the end of the block.
|
||||
|
||||
As an example, consider an 'if' statement with an 'else' block. The
|
||||
guard on the 'if' is a basic block which is pointed to by the basic
|
||||
block containing the code leading to the 'if' statement. The 'if'
|
||||
statement block contains jumps (which are exit points) to the true body
|
||||
of the 'if' and the 'else' body (which may be NULL), each of which are
|
||||
their own basic blocks. Both of those blocks in turn point to the
|
||||
basic block representing the code following the entire 'if' statement.
|
||||
|
||||
CFGs are usually one step away from final code output. Code is directly
|
||||
generated from the basic blocks (with jump targets adjusted based on the
|
||||
output order) by doing a post-order depth-first search on the CFG
|
||||
following the edges.
|
||||
|
||||
|
||||
AST to CFG to Bytecode
|
||||
----------------------
|
||||
|
||||
With the AST created, the next step is to create the CFG. The first step
|
||||
is to convert the AST to Python bytecode without having jump targets
|
||||
resolved to specific offsets (this is calculated when the CFG goes to
|
||||
final bytecode). Essentially, this transforms the AST into Python
|
||||
bytecode with control flow represented by the edges of the CFG.
|
||||
|
||||
Conversion is done in two passes. The first creates the namespace
|
||||
(variables can be classified as local, free/cell for closures, or
|
||||
global). With that done, the second pass essentially flattens the CFG
|
||||
into a list and calculates jump offsets for final output of bytecode.
|
||||
|
||||
The conversion process is initiated by a call to the function in
|
||||
Python/newcompile.c::
|
||||
|
||||
PyCodeObject * PyAST_Compile(mod_ty, const char *, PyCompilerFlags);
|
||||
|
||||
This function does both the conversion of the AST to a CFG and
|
||||
outputting final bytecode from the CFG. The AST to CFG step is handled
|
||||
mostly by the two functions called by PyAST_Compile()::
|
||||
|
||||
struct symtable * PySymtable_Build(mod_ty, const char *,
|
||||
PyFutureFeatures);
|
||||
PyCodeObject * compiler_mod(struct compiler *, mod_ty);
|
||||
|
||||
The former is in Python/symtable.c while the latter is in
|
||||
Python/newcompile.c .
|
||||
|
||||
PySymtable_Build() begins by entering the starting code block for the
|
||||
AST (passed-in) and then calling the proper symtable_visit_xx function
|
||||
(with xx being the AST node type). Next, the AST tree is walked with
|
||||
the various code blocks that delineate the reach of a local variable
|
||||
as blocks are entered and exited::
|
||||
|
||||
static int symtable_enter_block(struct symtable *, identifier,
|
||||
block_ty, void *, int);
|
||||
static int symtable_exit_block(struct symtable *, void *);
|
||||
|
||||
Once the symbol table is created, it is time for CFG creation, whose
|
||||
code is in Python/newcompile.c . This is handled by several functions
|
||||
that break the task down by various AST node types. The functions are
|
||||
all named compiler_visit_xx where xx is the name of the node type (such
|
||||
as stmt, expr, etc.). Each function receives a ``struct compiler *``
|
||||
and xx_ty where xx is the AST node type. Typically these functions
|
||||
consist of a large 'switch' statement, branching based on the kind of
|
||||
node type passed to it. Simple things are handled inline in the
|
||||
'switch' statement with more complex transformations farmed out to other
|
||||
functions named compiler_xx with xx being a descriptive name of what is
|
||||
being handled.
|
||||
|
||||
When transforming an arbitrary AST node, use the VISIT macro::
|
||||
|
||||
VISIT(struct compiler *, <node type>, <AST node>);
|
||||
|
||||
The appropriate compiler_visit_xx function is called, based on the value
|
||||
passed in for <node type> (so ``VISIT(c, expr, node)`` calls
|
||||
``compiler_visit_expr(c, node)``). The VISIT_SEQ macro is very similar,
|
||||
but is called on AST node sequences (those values that were created as
|
||||
arguments to a node that used the '*' modifier). There is also
|
||||
VISIT_SLICE just for handling slices::
|
||||
|
||||
VISIT_SLICE(struct compiler *, slice_ty, expr_context_ty);
|
||||
|
||||
Emission of bytecode is handled by the following macros:
|
||||
|
||||
- ``ADDOP(struct compiler *c, int op)``
|
||||
add 'op' as an opcode
|
||||
- ``ADDOP_I(struct compiler *c, int op, int oparg)``
|
||||
add 'op' with an 'oparg' argument
|
||||
- ``ADDOP_O(struct compiler *c, int op, PyObject *type, PyObject *obj)``
|
||||
add 'op' with the proper argument based on the position of obj in
|
||||
'type', but with no handling of mangled names; used for when you
|
||||
need to do named lookups of objects such as globals, consts, or
|
||||
parameters where name mangling is not possible and the scope of the
|
||||
name is known
|
||||
- ``ADDOP_NAME(struct compiler *, int, PyObject *, PyObject *)``
|
||||
just like ADDOP_O, but name mangling is also handled; used for
|
||||
attribute loading or importing based on name
|
||||
- ``ADDOP_JABS(struct compiling *c, int op, basicblock b)``
|
||||
create an absolute jump to the basic block 'b'
|
||||
- ``ADDOP_JREL(struct compiling *c, int op, basicblock b)``
|
||||
create a relative jump to the basic block 'b'
|
||||
|
||||
Several helper functions that will emit bytecode and are named
|
||||
compiler_xx() where xx is what the function helps with (list, boolop
|
||||
etc.). A rather useful one is::
|
||||
|
||||
static int compiler_nameop(struct compiler *, identifier,
|
||||
expr_context_ty);
|
||||
|
||||
This function looks up the scope of a variable and, based on the
|
||||
expression context, emits the proper opcode to load, store, or delete
|
||||
the variable.
|
||||
|
||||
As for handling the line number on which a statement is defined, is
|
||||
handled by compiler_visit_stmt() and thus is not a worry.
|
||||
|
||||
In addition to emitting bytecode based on the AST node, handling the
|
||||
creation of basic blocks must be done. Below are the macros and
|
||||
functions used for managing basic blocks:
|
||||
|
||||
- ``NEW_BLOCK(struct compiler *)``
|
||||
create block and set it as current
|
||||
- ``NEXT_BLOCK(struct compiler *)``
|
||||
basically NEW_BLOCK() plus jump from current block
|
||||
- ``compiler_new_block(struct compiler *)``
|
||||
create a block but don't use it (used for generating jumps)
|
||||
|
||||
Once the CFG is created, it must be flattened and then final emission of
|
||||
bytecode occurs. Flattening is handled using a post-order depth-first
|
||||
search. Once flattened, jump offsets are backpatched based on the
|
||||
flattening and then a PyCodeObject file is created. All of this is
|
||||
handled by calling::
|
||||
|
||||
PyCodeObject * assemble(struct compiler *, int);
|
||||
|
||||
*** NOTE: if you make a change here that can affect the output of bytecode that
|
||||
is already in existence, make sure to delete your old .py(c|o) files! Running
|
||||
``find . -name '*.py[co]' -exec rm -f {} ';'`` should do the trick.
|
||||
|
||||
|
||||
Code Objects
|
||||
------------
|
||||
|
||||
In the end, one ends up with a PyCodeObject which is defined in
|
||||
Include/code.h . And with that you now have executable Python bytecode!
|
||||
|
||||
|
||||
Modified Files
|
||||
--------------
|
||||
|
||||
+ Parser/
|
||||
|
||||
- Python.asdl
|
||||
ASDL syntax file
|
||||
|
||||
- asdl.py
|
||||
"An implementation of the Zephyr Abstract Syntax Definition
|
||||
Language." Uses SPARK_ to parse the ASDL files.
|
||||
|
||||
- asdl_c.py
|
||||
"Generate C code from an ASDL description." Generates
|
||||
../Python/Python-ast.c and ../Include/Python-ast.h .
|
||||
|
||||
- spark.py
|
||||
SPARK_ parser generator
|
||||
|
||||
+ Python/
|
||||
|
||||
- Python-ast.c
|
||||
Creates C structs corresponding to the ASDL types. Also
|
||||
contains code for marshaling AST nodes (core ASDL types have
|
||||
marshaling code in asdl.c). "File automatically generated by
|
||||
../Parser/asdl_c.py".
|
||||
|
||||
- asdl.c
|
||||
Contains code to handle the ASDL sequence type. Also has code
|
||||
to handle marshalling the core ASDL types, such as number and
|
||||
identifier. used by Python-ast.c for marshaling AST nodes.
|
||||
|
||||
- ast.c
|
||||
Converts Python's parse tree into the abstract syntax tree.
|
||||
|
||||
- compile.txt
|
||||
This file.
|
||||
|
||||
- newcompile.c
|
||||
New version of compile.c that handles the emitting of bytecode.
|
||||
|
||||
- symtable.c
|
||||
Generates symbol table from AST.
|
||||
|
||||
|
||||
+ Include/
|
||||
|
||||
- Python-ast.h
|
||||
Contains the actual definitions of the C structs as generated by
|
||||
../Python/Python-ast.c .
|
||||
"Automatically generated by ../Parser/asdl_c.py".
|
||||
|
||||
- asdl.h
|
||||
Header for the corresponding ../Python/ast.c .
|
||||
|
||||
- ast.h
|
||||
Declares PyAST_FromNode() external (from ../Python/ast.c).
|
||||
|
||||
- code.h
|
||||
Header file for ../Objects/codeobject.c; contains definition of
|
||||
PyCodeObject.
|
||||
|
||||
- symtable.h
|
||||
Header for ../Python/symtable.c . struct symtable and
|
||||
PySTEntryObject are defined here.
|
||||
|
||||
+ Objects/
|
||||
|
||||
- codeobject.c
|
||||
Contains PyCodeObject-related code (originally in
|
||||
../Python/compile.c).
|
||||
|
||||
|
||||
ToDo
|
||||
----
|
||||
*** NOTE: all bugs and patches should be filed on SF under the group
|
||||
"AST" for easy searching. It also does not hurt to put
|
||||
"[AST]" at the beginning of the subject line of the tracker
|
||||
item.
|
||||
|
||||
+ Stdlib support
|
||||
- AST->Python access?
|
||||
- rewrite compiler package to mirror AST structure?
|
||||
+ Documentation
|
||||
- flesh out this doc
|
||||
* byte stream output
|
||||
* explanation of how the symbol table pass works
|
||||
* code object (PyCodeObject)
|
||||
+ Universal
|
||||
- make sure entire test suite passes
|
||||
- fix memory leaks
|
||||
- make sure return types are properly checked for errors
|
||||
- no gcc warnings
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
.. [Aho86] Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman.
|
||||
`Compilers: Principles, Techniques, and Tools`,
|
||||
http://www.amazon.com/exec/obidos/tg/detail/-/0201100886/104-0162389-6419108
|
||||
|
||||
.. [Wang97] Daniel C. Wang, Andrew W. Appel, Jeff L. Korn, and Chris
|
||||
S. Serra. `The Zephyr Abstract Syntax Description Language.`_
|
||||
In Proceedings of the Conference on Domain-Specific Languages, pp.
|
||||
213--227, 1997.
|
||||
|
||||
.. _The Zephyr Abstract Syntax Description Language.:
|
||||
http://www.cs.princeton.edu/~danwang/Papers/dsl97/dsl97.html
|
||||
|
||||
.. _SPARK: http://pages.cpsc.ucalgary.ca/~aycock/spark/
|
||||
|
Loading…
Reference in New Issue