blob: 8b21a648a9dc0758a39e820b95cea4a2b3fa60f0 [file] [edit]
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
#
# SPDX-License-Identifier: BSD-2-Clause
#
#
'''Entry point for the runner (template instantiator).'''
from __future__ import absolute_import, division, print_function, \
unicode_literals
import \
six, string, sys, traceback, pickle
import re
import os
import numbers
import locale
import jinja2
import functools
import argparse
from capdl import ObjectType, ObjectAllocator, CSpaceAllocator, \
lookup_architecture, AddressSpaceAllocator
from camkes.runner.Renderer import Renderer
from capdl.Allocator import RenderState
from camkes.internal.exception import CAmkESError
import camkes.internal.log as log
from camkes.templates import TemplateError
import yaml
from camkes.internal.seven import zip
# Excuse this horrible prelude. When running under a different interpreter we
# have an import path that doesn't include dependencies like elftools and
# Jinja. We need to juggle the import path prior to importing them. Note, this
# code has no effect when running under the standard Python interpreter.
import platform
import subprocess
import sys
from capdl.Object import register_object_sizes
if platform.python_implementation() != 'CPython':
path = eval(subprocess.check_output(['python', '-c',
'import sys; sys.stdout.write(\'%s\' % sys.path)'],
universal_newlines=True))
for p in path:
if p not in sys.path:
sys.path.append(p)
def _die(options, message):
if isinstance(message, (list, tuple)):
for line in message:
log.error(line)
else:
log.error(message)
tb = traceback.format_exc()
log.debug('\n --- Python traceback ---\n%s ------------------------\n' % tb)
sys.exit(-1)
def parse_args(argv, out, err):
parser = argparse.ArgumentParser(prog='python -m camkes.runner',
description='instantiate templates based on a CAmkES specification')
parser.add_argument('--quiet', '-q', help='No diagnostics.', dest='verbosity',
default=1, action='store_const', const=0)
parser.add_argument('--verbose', '-v', help='Verbose diagnostics.',
dest='verbosity', action='store_const', const=2)
parser.add_argument('--debug', '-D', help='Extra verbose diagnostics.',
dest='verbosity', action='store_const', const=3)
parser.add_argument('--outfile', '-O', help='Output to the given file.',
type=argparse.FileType('w'), required=True, action='append', default=[])
parser.add_argument('--verification-base-name', type=str,
help='Prefix to use when generating Isabelle theory files.')
parser.add_argument('--item', '-T', help='AST entity to produce code for.',
required=True, action='append', default=[])
parser.add_argument('--template', help='template to use to produce code.',
required=True, action='append', default=[])
parser.add_argument('--templates', '-t', help='Extra directories to '
'search for templates (before builtin templates).', action='append',
default=[])
parser.add_argument('--frpc-lock-elision', action='store_true',
default=True, help='Enable lock elision optimisation in seL4RPC '
'connector.')
parser.add_argument('--fno-rpc-lock-elision', action='store_false',
dest='frpc_lock_elision', help='Disable lock elision optimisation in '
'seL4RPC connector.')
parser.add_argument('--fprovide-tcb-caps', action='store_true',
default=True, help='Hand out TCB caps to components, allowing them to '
'exit cleanly.')
parser.add_argument('--fno-provide-tcb-caps', action='store_false',
dest='fprovide_tcb_caps', help='Do not hand out TCB caps, causing '
'components to fault on exiting.')
parser.add_argument('--default-priority', type=int, default=254,
help='Default component thread priority.')
parser.add_argument('--default-max-priority', type=int, default=254,
help='Default component thread maximum priority.')
parser.add_argument('--default-affinity', type=int, default=0,
help='Default component thread affinity.')
parser.add_argument('--default-period', type=int, default=10000,
help='Default component thread scheduling context period.')
parser.add_argument('--default-budget', type=int, default=10000,
help='Default component thread scheduling context budget.')
parser.add_argument('--default-data', type=int, default=0,
help='Default component thread scheduling context data.')
parser.add_argument('--default-size_bits', type=int, default=8,
help='Default scheduling context size bits.')
parser.add_argument('--default-stack-size', type=int, default=16384,
help='Default stack size of each thread.')
parser.add_argument('--largeframe', action='store_true',
help='Use large frames (for non-DMA pools) when possible.')
parser.add_argument('--architecture', '--arch', default='aarch32',
type=lambda x: type('')(x).lower(),
choices=('aarch32', 'arm_hyp', 'ia32', 'x86_64',
'aarch64', 'riscv32', 'riscv64'),
help='Target architecture.')
parser.add_argument('--makefile-dependencies', '-MD',
type=argparse.FileType('w'), help='Write Makefile dependency rule to '
'FILE')
parser.add_argument('--debug-fault-handlers', action='store_true',
help='Provide fault handlers to decode cap and VM faults for '
'debugging purposes.')
parser.add_argument('--largeframe-dma', action='store_true',
help='Use large frames for DMA pools when possible.')
parser.add_argument('--realtime', action='store_true',
help='Target realtime seL4.')
parser.add_argument('--object-sizes', type=argparse.FileType('r'),
help='YAML file specifying the object sizes for any seL4 objects '
'used in this invocation of the runner.')
object_state_group = parser.add_mutually_exclusive_group()
object_state_group.add_argument('--load-object-state', type=argparse.FileType('rb'),
help='Load previously-generated cap and object state.')
object_state_group.add_argument('--save-object-state', type=argparse.FileType('wb'),
help='Save generated cap and object state to this file.')
# To get the AST, there should be either a pickled AST or a file to parse
parser.add_argument('--load-ast', type=argparse.FileType('rb'),
help='Load specification AST from this file.', required=True)
# Juggle the standard streams either side of parsing command-line arguments
# because argparse provides no mechanism to control this.
old_out = sys.stdout
old_err = sys.stderr
sys.stdout = out
sys.stderr = err
options = parser.parse_args(argv[1:])
sys.stdout = old_out
sys.stderr = old_err
# Check that verification_base_name would be a valid identifer before
# our templates try to use it
if options.verification_base_name is not None:
if not re.match(r'[a-zA-Z][a-zA-Z0-9_]*$', options.verification_base_name):
parser.error('Not a valid identifer for --verification-base-name: %r' %
options.verification_base_name)
return options
def rendering_error(item, exn):
"""Helper to format an error message for template rendering errors."""
tb = '\n'.join(traceback.format_tb(sys.exc_info()[2]))
return (['While rendering %s: %s' % (item, line) for line in exn.args] +
''.join(tb).splitlines())
def main(argv, out, err):
# We need a UTF-8 locale, so bail out if we don't have one. More
# specifically, things like the version() computation traverse the file
# system and, if they hit a UTF-8 filename, they try to decode it into your
# preferred encoding and trigger an exception.
encoding = locale.getpreferredencoding().lower()
if encoding not in ('utf-8', 'utf8'):
err.write('CAmkES uses UTF-8 encoding, but your locale\'s preferred '
'encoding is %s. You can override your locale with the LANG '
'environment variable.\n' % encoding)
return -1
options = parse_args(argv, out, err)
# register object sizes with loader
if options.object_sizes:
register_object_sizes(yaml.load(options.object_sizes, Loader=yaml.FullLoader))
# Ensure we were supplied equal items, outfiles and templates
if len(options.outfile) != len(options.item) != len(options.template):
err.write('Different number of items and outfiles. Required one outfile location '
'per item requested.\n')
return -1
# No duplicates in outfiles
if len(set(options.outfile)) != len(options.outfile):
err.write('Duplicate outfiles requrested through --outfile.\n')
return -1
# Save us having to pass debugging everywhere.
die = functools.partial(_die, options)
log.set_verbosity(options.verbosity)
ast = pickle.load(options.load_ast)
# Locate the assembly.
assembly = ast.assembly
if assembly is None:
die('No assembly found')
# Do some extra checks if the user asked for verbose output.
if options.verbosity >= 2:
# Try to catch type mismatches in attribute settings. Note that it is
# not possible to conclusively evaluate type correctness because the
# attributes' type system is (deliberately) too loose. That is, the
# type of an attribute can be an uninterpreted C type the user will
# provide post hoc.
for i in assembly.composition.instances:
for a in i.type.attributes:
value = assembly.configuration[i.name].get(a.name)
if value is not None:
if a.type == 'string' and not \
isinstance(value, six.string_types):
log.warning('attribute %s.%s has type string but is '
'set to a value that is not a string' % (i.name,
a.name))
elif a.type == 'int' and not \
isinstance(value, numbers.Number):
log.warning('attribute %s.%s has type int but is set '
'to a value that is not an integer' % (i.name,
a.name))
try:
r = Renderer(options.templates)
except jinja2.exceptions.TemplateSyntaxError as e:
die('template syntax error: %s' % e)
if options.load_object_state is not None:
render_state = pickle.load(options.load_object_state)
elif options.save_object_state is None:
render_state = None
else:
obj_space = ObjectAllocator()
obj_space.spec.arch = options.architecture
render_state = RenderState(obj_space=obj_space)
for i in assembly.composition.instances:
# Don't generate any code for hardware components.
if i.type.hardware:
continue
key = i.address_space
if key not in render_state.cspaces:
cnode = render_state.obj_space.alloc(ObjectType.seL4_CapTableObject,
name="%s_cnode" % key, label=key)
render_state.cspaces[key] = CSpaceAllocator(cnode)
pd = obj_space.alloc(lookup_architecture(options.architecture).vspace().object, name="%s_group_bin_pd" % key,
label=key)
addr_space = AddressSpaceAllocator(
re.sub(r'[^A-Za-z0-9]', '_', "%s_group_bin" % key), pd)
render_state.pds[key] = pd
render_state.addr_spaces[key] = addr_space
for (item, outfile, template) in zip(options.item, options.outfile, options.template):
key = item.split("/")
if key[0] == "component":
i = [x for x in assembly.composition.instances if x.name == key[1]][0]
obj_key = i.address_space
elif key[0] == "connector":
c = [c for c in assembly.composition.connections if c.name == key[1]][0]
if key[2] == "to":
i = c.to_ends[int(key[3])]
elif key[2] == "from":
i = c.from_ends[int(key[3])]
else:
die("Invalid connector end")
obj_key = i.instance.address_space
elif key[0] == "assembly":
i = assembly
obj_key = None
else:
die("item: \"%s\" does not have the correct formatting to render." % item)
try:
g = r.render(i, assembly, template, render_state, obj_key,
outfile_name=outfile.name, options=options, my_pd=render_state.pds[obj_key] if obj_key else None)
outfile.write(g)
outfile.close()
except TemplateError as inst:
if hasattr(i, 'name'):
die(rendering_error(i.name, inst))
else:
die(rendering_error(i.parent.name, inst))
read = r.get_files_used()
# Write a Makefile dependency rule if requested.
if options.makefile_dependencies is not None:
options.makefile_dependencies.write('%s: \\\n %s\n' %
(options.outfile[0].name, ' \\\n '.join(sorted(read))))
if options.save_object_state is not None:
# Write the render_state to the supplied outfile
pickle.dump(render_state, options.save_object_state)
sys.exit(0)
if __name__ == '__main__':
sys.exit(main(sys.argv, sys.stdout, sys.stderr))