Skip to content

Commit

Permalink
Generalize bf.py to do both ahead-of-time and JIT compilation
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmalcolm committed Mar 27, 2015
1 parent fc7e303 commit 9633591
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 31 deletions.
52 changes: 32 additions & 20 deletions doc/tutorial/bf.rst
Expand Up @@ -81,44 +81,56 @@ We write simple code to populate a :py:class:`gccjit.Context`.

Compiling a context to a file
*****************************

Unlike the previous examples, this time we'll compile the context
directly to an executable, using :py:meth:`gccjit.Context.compile_to_file`:

.. code-block:: python
def compile_to_file(self, output_path):
self.ctxt.compile_to_file(gccjit.OutputKind.EXECUTABLE,
output_path)
Here's the top-level of the compiler, which is what actually calls into
:c:func:`gcc_jit_context_compile_to_file`:
In previous examples, we compiled and ran the generated machine code
in-process. We can do that:

.. literalinclude:: ../../examples/bf.py
:start-after: # Entrypoint
:start-after: # Running the generated code in-process
:end-before: # Entrypoint
:language: python

Note how once the context is populated you could trivially instead compile
it to memory using :py:meth:`gccjit.Context.compile` and run it in-process
as in the previous examples.
but this time we'll also provide a way to compile the context directly
to an executable, using :py:meth:`gccjit.Context.compile_to_file`.

To create an executable, we need to export a ``main`` function. A helper
To do so, we need to export a ``main`` function. A helper
function for doing so is provided by the JIT API:

.. literalinclude:: ../../gccjit/__init__.py
:start-after: # Make it easy to make a "main" function:
:language: python

which we can use (as ``gccjit.make_main``) to compile the function
to an executable:

.. literalinclude:: ../../examples/bf.py
:start-after: # Compiling to an executable
:end-before: # Running the generated code in-process
:language: python

Finally, here's the top-level of the program:

.. literalinclude:: ../../examples/bf.py
:start-after: # Entrypoint
:language: python

The overall script `examples/bf.py` is thus a bf-to-machine-code compiler,
which we can use to compile .bf files into machine code executables:
which we can use to compile .bf files, either to run in-process,

.. code-block:: console
$ PYTHONPATH=. python examples/bf.py \
emit-alphabet.bf
ABCDEFGHIJKLMNOPQRSTUVWXYZ
or to compile into machine code executables:

.. code-block:: console
$ PYTHONPATH=. python examples/bf.py \
emit-alphabet.bf \
a.out
-o a.out
which we can run directly:
which we can run independently:

.. code-block:: console
Expand Down
48 changes: 39 additions & 9 deletions examples/bf.py
Expand Up @@ -69,7 +69,8 @@ def __init__(self): #, filename):
b"putchar",
[self.ctxt.new_param(self.int_type,
b"c")]))
self.func, argv, argv = gccjit.make_main(self.ctxt)
self.func = self.ctxt.new_function(gccjit.FunctionKind.EXPORTED,
self.void_type, b'func', [])
self.curblock = self.func.new_block(b"initial")
self.int_zero = self.ctxt.zero(self.int_type)
self.int_one = self.ctxt.one(self.int_type)
Expand Down Expand Up @@ -189,9 +190,9 @@ def compile_char(self, ch):
self.column += 1


def compile_into_ctxt(self, filename):
def parse_into_ctxt(self, filename):
"""
Compile the given .bf file into the gccjit.Context, containing a
Parse the given .bf file into the gccjit.Context, containing a
single "main" function suitable for compiling into an executable.
"""
self.filename = filename;
Expand All @@ -200,20 +201,49 @@ def compile_into_ctxt(self, filename):
with open(filename) as f_in:
for ch in f_in.read():
self.compile_char(ch)
self.curblock.end_with_return(self.int_zero)
self.curblock.end_with_void_return()

# Compiling to an executable

def compile_to_file(self, output_path):
# Wrap "func" up in a "main" function
mainfunc, argv, argv = gccjit.make_main(self.ctxt)
block = mainfunc.new_block()
block.add_eval(self.ctxt.new_call(self.func, []))
block.end_with_return(self.int_zero)
self.ctxt.compile_to_file(gccjit.OutputKind.EXECUTABLE,
output_path)

# Running the generated code in-process

def run(self):
import ctypes
result = self.ctxt.compile()
py_func_type = ctypes.CFUNCTYPE(None)
py_func = py_func_type(result.get_code(b'func'))
py_func()

# Entrypoint

def main(argv):
srcfile = argv[1]
outfile = argv[2]
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-o", "--output", dest="outputfile",
help="compile to FILE", metavar="FILE")
(options, args) = parser.parse_args()
if len(args) != 1:
raise ValueError('No input file')
inputfile = args[0]
c = Compiler()
c.compile_into_ctxt(srcfile)
c.compile_to_file(outfile)
c.parse_into_ctxt(inputfile)
if options.outputfile:
c.compile_to_file(options.outputfile)
else:
c.run()

if __name__ == '__main__':
main(sys.argv)
try:
main(sys.argv)
except Exception, exc:
print(exc)
sys.exit(1)
14 changes: 12 additions & 2 deletions tests/test.py
Expand Up @@ -142,11 +142,21 @@ def test_union(self):
u = ctxt.new_union(b'u', [as_int, as_float])
self.assertEqual(str(u), 'union u')

def test_bf(self):
def test_bf_aot(self):
from examples import bf
from subprocess import Popen, PIPE
c = bf.Compiler()
c.compile_into_ctxt(b'examples/emit-alphabet.bf')
c.parse_into_ctxt(b'examples/emit-alphabet.bf')
c.compile_to_file(b'emit-alphabet.exe')
p = Popen(b'./emit-alphabet.exe', stdout=PIPE)
out, err = p.communicate()
self.assertEqual(out, b'ABCDEFGHIJKLMNOPQRSTUVWXYZ')

def test_bf_jit(self):
from examples import bf
c = bf.Compiler()
c.parse_into_ctxt(b'examples/emit-alphabet.bf')
c.run()

def test_dump_reproducer(self):
from examples.sum_of_squares import populate_ctxt
Expand Down

0 comments on commit 9633591

Please sign in to comment.