To build the plugin, run:
make plugin
To build the plugin and run the selftests, run:
make
You can also use:
make demo
to demonstrate the new compiler errors.
There isn’t a well-defined process yet for installing the plugin (though the rpm specfile in the source tree contains some work-in-progress towards this).
Some notes on GCC plugins can be seen at http://gcc.gnu.org/wiki/plugins and http://gcc.gnu.org/onlinedocs/gccint/Plugins.html
Once you’ve built the plugin, you can invoke a Python script like this:
gcc -fplugin=python.so -fplugin-arg-python-script=PATH_TO_SCRIPT.py OTHER_ARGS
and have it run your script as the plugin starts up.
The plugin automatically adds the absolute path to its own directory to the end of its sys.path, so that it can find support modules, such as gccutils.py and libcpychecker.
There is also a helper script, gcc-with-python, which expects a python script as its first argument, then regular gcc arguments:
./gcc-with-python PATH_TO_SCRIPT.py other args follow
For example, this command will use graphviz to draw how GCC “sees” the internals of each function in test.c (within its SSA representation):
./gcc-with-python show-ssa.py test.c
Most of the rest of this document describes the Python API visible for scripting.
The plugin GCC’s various types as Python objects, within a “gcc” module. You can see the API by running the following within a script:
import gcc
help(gcc)
To make this easier, there’s a script to do this for you:
./gcc-python-docs
from where you can review the built-in documentation strings (this document may be easier to follow though).
The exact API is still in flux: and may well change (this is an early version of the code; we may have to change things as GCC changes in future releases also).
You can place a forced breakpoint in your script using this standard Python one-liner:
import pdb; pdb.set_trace()
If Python reaches this location it will interrupt the compile and put you within the pdb interactive debugger, from where you can investigate.
See http://docs.python.org/library/pdb.html#debugger-commands for more information.
Exposes the arguments passed to the plugin as a dictionary.
For example, running:
gcc -fplugin=python.so \
-fplugin-arg-python-script=test.py \
-fplugin-arg-python-foo=bar
with test.py containing:
import gcc
print(gcc.argument_dict)
has output:
{'script': 'test.py', 'foo': 'bar'}
Exposes the arguments passed to the plugin as a tuple of (key, value) pairs, so you have ordering. (Probably worth removing, and replacing argument_dict with an OrderedDict instead; what about duplicate args though?)
Hopefully we’ll eventually have the ability to write new GCC passes in Python. In the meantime, the main way to write scripts is to register callback functions to be called when various events happen during compilation, such as using gcc.PLUGIN_PASS_EXECUTION to piggyback off of an existing GCC pass.
Wire up a python function as a callback. It will be called when the given event occurs during compilation. For some events, the callback will be called just once; for other events, the callback is called once per function within the source code being compiled. In the latter case, the plugin passes a gcc.Function instance as a parameter to your callback, so that you can work on it:
def my_pass_execution_callback(*args, **kwargs):
print('my_pass_execution_callback was called: args=%r kwargs=%r'
% (args, kwargs))
import gcc
gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
my_pass_execution_callback)
You can pass additional arguments when registering the callback - they will be passed to the callback after any normal arguments. This is denoted in the descriptions of events below by *extraargs.
You can also supply keyword arguments: they will be passed on as keyword arguments to the callback. This is denoted in the description of events below by **kwargs.
The various events are exposed as constants within the gcc module and directly wrap GCC’s plugin mechanism. The exact arguments you get aren’t well-documented there, and may be subject to change. I’ve tried to document what I’ve seen in GCC 4.6 here, but it’s worth experimenting and printing args and kwargs as shown above.
If an exception occurs during a callback, and isn’t handled by a try/except before returning into the plugin, the plugin prints the traceback to stderr and treats it as a fatal error, terminating the compile:
Traceback (most recent call last):
File "test.py", line 38, in my_pass_execution_callback
dot = gccutils.tree_to_dot(fun)
NameError: global name 'gccutils' is not defined
/home/david/test.c: In function ‘main’:
/home/david/test.c:28:1: fatal error: Unhandled Python exception raised within callback
compilation terminated.
The bug is not reproducible, so it is likely a hardware or OS problem.
(Obviously the error message above could be improved: the final line is incorrect and misleading)
Called when GCC runs one of its passes on a function
Arguments passed to the callback are:
(ps, fun, *extraargs, **kwargs)
where ps is a gcc.Pass and fun is a gcc.Function. Your callback will typically be called many times: there are many passes, and each can be invoked zero or more times per function (in the code being compiled)
Arguments passed to the callback are:
(fndecl, *extraargs, **kwargs)
where fndecl is a gcc.Tree representing a function declaration within the source code being compiled.
Called when GCC has finished compiling a particular translation unit.
Arguments passed to the callback are:
(*extraargs, **kwargs)
The following may need work before they’re meaningfully usable from Python scripts:
However, it seems at this point to have initialized these:
static const struct attribute_spec *attribute_tables[4];
static htab_t attribute_hash;
gcc_data=0x0 Called from: c_common_init () at ../../gcc/c-family/c-opts.c:1052
gcc_data=0x0 Called from: compile_file () at ../../gcc/toplev.c:573
gcc_data is: tree fndecl; Called from: finish_function () at ../../gcc/c-decl.c:8323
gcc_data:
&gate_status
bool gate_status;
Called from : execute_one_pass (pass=0x1011340) at ../../gcc/passes.c:1520
gcc_data=0x0 Called from: ipa_passes () at ../../gcc/cgraphunit.c:1779
gcc_data=0x0 Called from: execute_ipa_pass_list (pass=0x1011fa0) at ../../gcc/passes.c:1927
gcc_data=0x0 Called from: execute_ipa_pass_list (pass=0x1011fa0) at ../../gcc/passes.c:1930
gcc_data=0x0 Called from: ipa_passes () at ../../gcc/cgraphunit.c:1821
gcc_data=0x0 Called from: tree_rest_of_compilation (fndecl=0x7ffff16b1f00) at ../../gcc/tree-optimize.c:420
gcc_data=0x0 Called from: tree_rest_of_compilation (fndecl=0x7ffff16b1f00) at ../../gcc/tree-optimize.c:425
gcc_data=0x0 Called from: toplev_main (argc=17, argv=0x7fffffffdfc8) at ../../gcc/toplev.c:1970
gcc_data=tree Called from c_parser_declspecs (parser=0x7fffef559730, specs=0x15296d0, scspec_ok=1 ‘001’, typespec_ok=1 ‘001’, start_attr_ok=<optimized out>, la=cla_nonabstract_decl) at ../../gcc/c-parser.c:2111
gcc_data=0x0 Called from: init_pragma at ../../gcc/c-family/c-pragma.c:1321 to “Allow plugins to register their own pragmas.”
This wraps one of GCC’s struct opt_pass *, but the wrapper class is still a work-in-progress. Hopefully we’ll eventually be able to subclass this and allow creating custom passes written in Python.
Beware: “pass” is a reserved word in Python, so use e.g. ps as a variable name for an instance of gcc.Pass
The name of the pass, as a string
Currently these are int bitfields.
There are four subclasses of gcc.Pass:
reflecting the internal data layouts within GCC’s implementation of the classes, but these don’t do anything different yet at the Python level.
Emits a compiler warning at the given gcc.Location.
The warning is controlled by the given gcc.Option.
For example, given this Python code:
gcc.warning(func.start, gcc.Option('-Wformat'), 'Incorrect formatting')
if the given warning is enabled, a warning will be printed to stderr:
$ ./gcc-with-python script.py input.c
input.c:25:1: warning: incorrect formatting [-Wformat]
If the given warning is being treated as an error (through the usage of -Werror), then an error will be printed:
$ ./gcc-with-python -Werror script.py input.c
input.c:25:1: error: incorrect formatting [-Werror=format]
cc1: all warnings being treated as errors
$ ./gcc-with-python -Werror=format script.py input.c
input.c:25:1: error: incorrect formatting [-Werror=format]
cc1: some warnings being treated as errors
If the given warning is disabled, the warning will not be printed:
$ ./gcc-with-python -Wno-format script.py input.c
Note
Due to the way GCC implements some options, it’s not always possible for the plugin to fully disable some warnings. See gcc.Option.is_enabled for more information.
The function returns a boolean, indicating whether or not anything was actually printed.
Emits a compiler error at the given gcc.Location.
For example:
gcc.error(func.start, 'something bad was detected')
would lead to this error being printed to stderr:
$ ./gcc-with-python script.py input.c
input.c:25:1: error: something bad was detected
This is a wrapper around GCC’s permerror function.
Expects an instance of gcc.Location (not None) and a string
Emit a “permissive” error at that location, intended for things that really ought to be errors, but might be present in legacy code.
In theory it’s suppressable using “-fpermissive” at the GCC command line (which turns it into a warning), but this only seems to be legal for C++ source files.
Returns True if the warning was actually printed, False otherwise
This is a wrapper around GCC’s inform function.
Expects an instance of gcc.Location (not None) and a string
Emit an informational message at that location.
For example:
gcc.inform(stmt.loc, 'this is where X was defined')
would lead to this informational message being printed to stderr:
$ ./gcc-with-python script.py input.c
input.c:23:3: note: this is where X was defined
Get all variables in this compilation unit as a list of gcc.Variable
Get a dictionary of all variables, where the keys are the variable names (as strings), and the values are instances of gcc.Variable
Get the gcc.IdentifierNode with this name, if it exists, otherwise None. (However, after the front-end has run, the identifier node may no longer point at anything useful to you; see gccutils.get_global_typedef() for an example of working around this)
Get a list of all gcc.TranslationUnitDecl for the compilation units within this invocation of GCC (that’s “source code files” for the layperson).
Given a string name, look for a C/C++ typedef in global scope with that name, returning it as a gcc.TypeDecl, or None if it wasn’t found
Given a gcc.Location, get the source line as a string (without trailing whitespace or newlines)
Wrapper around GCC’s location_t, representing a location within the source code. Use gccutils.get_src_for_loc() to get at the line of actual source code.
The output from __repr__ looks like this:
gcc.Location(file='./src/test.c', line=42)
The output from__str__ looks like this:
./src/test.c:42
(string) Name of the source file (or header file)
(int) Line number within source file (starting at 1, not 0)
(int) Column number within source file (starting at 1, not 0)