1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# 5# Copyright 2017, Data61 6# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 7# ABN 41 687 119 230. 8# 9# This software may be distributed and modified according to the terms of 10# the BSD 2-Clause license. Note that NO WARRANTY is provided. 11# See "LICENSE_BSD2.txt" for details. 12# 13# @TAG(DATA61_BSD) 14# 15 16'''Entry point for the runner (template instantiator).''' 17from __future__ import absolute_import, division, print_function, \ 18 unicode_literals 19 20import \ 21 six, string, sys, traceback, pickle 22import re 23import os 24import numbers 25import locale 26import jinja2 27import functools 28import argparse 29from capdl import ObjectType, ObjectAllocator, CSpaceAllocator, \ 30 lookup_architecture, AddressSpaceAllocator 31from camkes.runner.Renderer import Renderer 32from capdl.Allocator import RenderState 33from camkes.internal.exception import CAmkESError 34import camkes.internal.log as log 35from camkes.templates import TemplateError 36from simpleeval import simple_eval 37 38import yaml 39 40from camkes.internal.seven import zip 41 42# Excuse this horrible prelude. When running under a different interpreter we 43# have an import path that doesn't include dependencies like elftools and 44# Jinja. We need to juggle the import path prior to importing them. Note, this 45# code has no effect when running under the standard Python interpreter. 46import platform 47import subprocess 48import sys 49 50from capdl.Object import register_object_sizes 51 52if platform.python_implementation() != 'CPython': 53 path = eval(subprocess.check_output(['python', '-c', 54 'import sys; sys.stdout.write(\'%s\' % sys.path)'], 55 universal_newlines=True)) 56 for p in path: 57 if p not in sys.path: 58 sys.path.append(p) 59 60 61def _die(options, message): 62 63 if isinstance(message, (list, tuple)): 64 for line in message: 65 log.error(line) 66 else: 67 log.error(message) 68 69 tb = traceback.format_exc() 70 log.debug('\n --- Python traceback ---\n%s ------------------------\n' % tb) 71 sys.exit(-1) 72 73 74def parse_args(argv, out, err): 75 parser = argparse.ArgumentParser(prog='python -m camkes.runner', 76 description='instantiate templates based on a CAmkES specification') 77 parser.add_argument('--quiet', '-q', help='No diagnostics.', dest='verbosity', 78 default=1, action='store_const', const=0) 79 parser.add_argument('--verbose', '-v', help='Verbose diagnostics.', 80 dest='verbosity', action='store_const', const=2) 81 parser.add_argument('--debug', '-D', help='Extra verbose diagnostics.', 82 dest='verbosity', action='store_const', const=3) 83 parser.add_argument('--outfile', '-O', help='Output to the given file.', 84 type=argparse.FileType('w'), required=True, action='append', default=[]) 85 parser.add_argument('--verification-base-name', type=str, 86 help='Prefix to use when generating Isabelle theory files.') 87 parser.add_argument('--item', '-T', help='AST entity to produce code for.', 88 required=True, action='append', default=[]) 89 parser.add_argument('--template', help='template to use to produce code.', 90 required=True, action='append', default=[]) 91 parser.add_argument('--templates', '-t', help='Extra directories to ' 92 'search for templates (before builtin templates).', action='append', 93 default=[]) 94 parser.add_argument('--frpc-lock-elision', action='store_true', 95 default=True, help='Enable lock elision optimisation in seL4RPC ' 96 'connector.') 97 parser.add_argument('--fno-rpc-lock-elision', action='store_false', 98 dest='frpc_lock_elision', help='Disable lock elision optimisation in ' 99 'seL4RPC connector.') 100 parser.add_argument('--fprovide-tcb-caps', action='store_true', 101 default=True, help='Hand out TCB caps to components, allowing them to ' 102 'exit cleanly.') 103 parser.add_argument('--fno-provide-tcb-caps', action='store_false', 104 dest='fprovide_tcb_caps', help='Do not hand out TCB caps, causing ' 105 'components to fault on exiting.') 106 parser.add_argument('--default-priority', type=int, default=254, 107 help='Default component thread priority.') 108 parser.add_argument('--default-max-priority', type=int, default=254, 109 help='Default component thread maximum priority.') 110 parser.add_argument('--default-affinity', type=int, default=0, 111 help='Default component thread affinity.') 112 parser.add_argument('--default-period', type=int, default=10000, 113 help='Default component thread scheduling context period.') 114 parser.add_argument('--default-budget', type=int, default=10000, 115 help='Default component thread scheduling context budget.') 116 parser.add_argument('--default-data', type=int, default=0, 117 help='Default component thread scheduling context data.') 118 parser.add_argument('--default-size_bits', type=int, default=8, 119 help='Default scheduling context size bits.') 120 parser.add_argument('--default-stack-size', type=int, default=16384, 121 help='Default stack size of each thread.') 122 parser.add_argument('--largeframe', action='store_true', 123 help='Use large frames (for non-DMA pools) when possible.') 124 parser.add_argument('--architecture', '--arch', default='aarch32', 125 type=lambda x: type('')(x).lower(), 126 choices=('aarch32', 'arm_hyp', 'ia32', 'x86_64', 127 'aarch64', 'riscv32', 'riscv64'), 128 help='Target architecture.') 129 parser.add_argument('--makefile-dependencies', '-MD', 130 type=argparse.FileType('w'), help='Write Makefile dependency rule to ' 131 'FILE') 132 parser.add_argument('--debug-fault-handlers', action='store_true', 133 help='Provide fault handlers to decode cap and VM faults for ' 134 'debugging purposes.') 135 parser.add_argument('--largeframe-dma', action='store_true', 136 help='Use large frames for DMA pools when possible.') 137 parser.add_argument('--realtime', action='store_true', 138 help='Target realtime seL4.') 139 parser.add_argument('--object-sizes', type=argparse.FileType('r'), 140 help='YAML file specifying the object sizes for any seL4 objects ' 141 'used in this invocation of the runner.') 142 143 object_state_group = parser.add_mutually_exclusive_group() 144 object_state_group.add_argument('--load-object-state', type=argparse.FileType('rb'), 145 help='Load previously-generated cap and object state.') 146 object_state_group.add_argument('--save-object-state', type=argparse.FileType('wb'), 147 help='Save generated cap and object state to this file.') 148 149 # To get the AST, there should be either a pickled AST or a file to parse 150 parser.add_argument('--load-ast', type=argparse.FileType('rb'), 151 help='Load specification AST from this file.', required=True) 152 153 # Juggle the standard streams either side of parsing command-line arguments 154 # because argparse provides no mechanism to control this. 155 old_out = sys.stdout 156 old_err = sys.stderr 157 sys.stdout = out 158 sys.stderr = err 159 options = parser.parse_args(argv[1:]) 160 161 sys.stdout = old_out 162 sys.stderr = old_err 163 164 # Check that verification_base_name would be a valid identifer before 165 # our templates try to use it 166 if options.verification_base_name is not None: 167 if not re.match(r'[a-zA-Z][a-zA-Z0-9_]*$', options.verification_base_name): 168 parser.error('Not a valid identifer for --verification-base-name: %r' % 169 options.verification_base_name) 170 171 return options 172 173 174def rendering_error(item, exn): 175 """Helper to format an error message for template rendering errors.""" 176 tb = '\n'.join(traceback.format_tb(sys.exc_info()[2])) 177 return (['While rendering %s: %s' % (item, line) for line in exn.args] + 178 ''.join(tb).splitlines()) 179 180 181def main(argv, out, err): 182 183 # We need a UTF-8 locale, so bail out if we don't have one. More 184 # specifically, things like the version() computation traverse the file 185 # system and, if they hit a UTF-8 filename, they try to decode it into your 186 # preferred encoding and trigger an exception. 187 encoding = locale.getpreferredencoding().lower() 188 if encoding not in ('utf-8', 'utf8'): 189 err.write('CAmkES uses UTF-8 encoding, but your locale\'s preferred ' 190 'encoding is %s. You can override your locale with the LANG ' 191 'environment variable.\n' % encoding) 192 return -1 193 194 options = parse_args(argv, out, err) 195 196 # register object sizes with loader 197 if options.object_sizes: 198 register_object_sizes(yaml.load(options.object_sizes, Loader=yaml.FullLoader)) 199 200 # Ensure we were supplied equal items, outfiles and templates 201 if len(options.outfile) != len(options.item) != len(options.template): 202 err.write('Different number of items and outfiles. Required one outfile location ' 203 'per item requested.\n') 204 return -1 205 206 # No duplicates in outfiles 207 if len(set(options.outfile)) != len(options.outfile): 208 err.write('Duplicate outfiles requrested through --outfile.\n') 209 return -1 210 211 # Save us having to pass debugging everywhere. 212 die = functools.partial(_die, options) 213 214 log.set_verbosity(options.verbosity) 215 216 ast = pickle.load(options.load_ast) 217 218 # Locate the assembly. 219 assembly = ast.assembly 220 if assembly is None: 221 die('No assembly found') 222 223 # Do some extra checks if the user asked for verbose output. 224 if options.verbosity >= 2: 225 226 # Try to catch type mismatches in attribute settings. Note that it is 227 # not possible to conclusively evaluate type correctness because the 228 # attributes' type system is (deliberately) too loose. That is, the 229 # type of an attribute can be an uninterpreted C type the user will 230 # provide post hoc. 231 for i in assembly.composition.instances: 232 for a in i.type.attributes: 233 value = assembly.configuration[i.name].get(a.name) 234 if value is not None: 235 if a.type == 'string' and not \ 236 isinstance(value, six.string_types): 237 log.warning('attribute %s.%s has type string but is ' 238 'set to a value that is not a string' % (i.name, 239 a.name)) 240 elif a.type == 'int' and not \ 241 isinstance(value, numbers.Number): 242 log.warning('attribute %s.%s has type int but is set ' 243 'to a value that is not an integer' % (i.name, 244 a.name)) 245 246 try: 247 r = Renderer(options.templates) 248 except jinja2.exceptions.TemplateSyntaxError as e: 249 die('template syntax error: %s' % e) 250 251 if options.load_object_state is not None: 252 render_state = pickle.load(options.load_object_state) 253 elif options.save_object_state is None: 254 render_state = None 255 else: 256 obj_space = ObjectAllocator() 257 obj_space.spec.arch = options.architecture 258 render_state = RenderState(obj_space=obj_space) 259 260 for i in assembly.composition.instances: 261 # Don't generate any code for hardware components. 262 if i.type.hardware: 263 continue 264 265 key = i.address_space 266 267 if key not in render_state.cspaces: 268 cnode = render_state.obj_space.alloc(ObjectType.seL4_CapTableObject, 269 name="%s_cnode" % key, label=key) 270 render_state.cspaces[key] = CSpaceAllocator(cnode) 271 pd = obj_space.alloc(lookup_architecture(options.architecture).vspace().object, name="%s_group_bin_pd" % key, 272 label=key) 273 addr_space = AddressSpaceAllocator( 274 re.sub(r'[^A-Za-z0-9]', '_', "%s_group_bin" % key), pd) 275 render_state.pds[key] = pd 276 render_state.addr_spaces[key] = addr_space 277 278 for (item, outfile, template) in zip(options.item, options.outfile, options.template): 279 key = item.split("/") 280 if key[0] == "component": 281 i = [x for x in assembly.composition.instances if x.name == key[1]][0] 282 obj_key = i.address_space 283 elif key[0] == "connector": 284 c = [c for c in assembly.composition.connections if c.name == key[1]][0] 285 if key[2] == "to": 286 i = c.to_ends[int(key[3])] 287 elif key[2] == "from": 288 i = c.from_ends[int(key[3])] 289 else: 290 die("Invalid connector end") 291 obj_key = i.instance.address_space 292 elif key[0] == "assembly": 293 i = assembly 294 obj_key = None 295 else: 296 die("item: \"%s\" does not have the correct formatting to render." % item) 297 298 try: 299 g = r.render(i, assembly, template, render_state, obj_key, 300 outfile_name=outfile.name, options=options, my_pd=render_state.pds[obj_key] if obj_key else None) 301 outfile.write(g) 302 outfile.close() 303 except TemplateError as inst: 304 if hasattr(i, 'name'): 305 die(rendering_error(i.name, inst)) 306 else: 307 die(rendering_error(i.parent.name, inst)) 308 309 read = r.get_files_used() 310 # Write a Makefile dependency rule if requested. 311 if options.makefile_dependencies is not None: 312 options.makefile_dependencies.write('%s: \\\n %s\n' % 313 (options.outfile[0].name, ' \\\n '.join(sorted(read)))) 314 315 if options.save_object_state is not None: 316 # Write the render_state to the supplied outfile 317 pickle.dump(render_state, options.save_object_state) 318 319 sys.exit(0) 320 321 322if __name__ == '__main__': 323 sys.exit(main(sys.argv, sys.stdout, sys.stderr)) 324