Exemplo n.º 1
0
def get_sequence_function(node,
                          func_name,
                          importer,
                          allocated_qregs,
                          intermediate_fout=None,
                          saveOutput=False,
                          filename=None,
                          setup=None):
    """
    Create a function that encapsulates the QGL code
    from the given AST node, which is presumed to already
    be fully pre-processed.

    TODO: we don't test that the node is fully pre-processed.
    TODO: each step of the preprocessor should mark the nodes
    so that we know whether or not they've been processed.
    """

    builder = SequenceExtractor(importer, allocated_qregs)

    builder.find_sequences(node)
    builder.find_imports(node)
    code = builder.emit_function(func_name, setup)
    if intermediate_fout:
        print(('#start function\n%s\n#end function' % code),
              file=intermediate_fout,
              flush=True)
    if saveOutput and filename:
        newf = os.path.abspath(filename[:-3] + "qgl1.py")
        with open(newf, 'w') as compiledFile:
            compiledFile.write(code)
        print("Saved compiled code to %s" % newf)

    NodeError.diag_msg(node, 'generated code:\n#start\n%s\n#end code' % code)

    # TODO: we might want to pass in elements of the local scope
    scratch_scope = dict()
    eval(compile(code, '<none>', mode='exec'), globals(), scratch_scope)
    NodeError.halt_on_error()

    return scratch_scope[func_name]
Exemplo n.º 2
0
def compile_function(filename,
                     main_name=None,
                     toplevel_bindings=None,
                     saveOutput=False,
                     intermediate_output=None):

    NodeError.reset()

    print('\n\nCOMPILING [%s] main %s' %
          (filename, main_name if main_name else '(default)'))

    # Use whether intermediate_output is None to decide
    # whether to call printout blocks at all
    # Old code set intermediate_output to /dev/null

    if intermediate_output:
        try:
            intermediate_fout = open(intermediate_output, 'w')
        except BaseException as exc:
            NodeError.fatal_msg(None,
                                ('cannot save intermediate output in [%s]' %
                                 intermediate_output))
    else:
        intermediate_fout = None

    # Process imports in the input file, and find the main.
    # If there's no main, then bail out right away.

    try:
        rel_path = os.path.relpath(filename)
        filename = rel_path
    except Exception as e:
        # If that wasn't a good path, give up immediately
        NodeError.error_msg(
            None, "Failed to make relpath from %s: %s" % (filename, e))

    NodeError.halt_on_error()

    print('%s: CALLING IMPORTER' % datetime.now())
    importer = NameSpaces(filename, main_name)
    if not importer.qglmain:
        NodeError.fatal_msg(None, 'no qglmain function found')

    NodeError.halt_on_error()

    ptree = importer.qglmain

    if intermediate_output:
        ast_text_orig = pyqgl2.ast_util.ast2str(ptree)
        print(('%s: ORIGINAL CODE:\n%s' % (datetime.now(), ast_text_orig)),
              file=intermediate_fout,
              flush=True)

    # When QGL2 flattens various kinds of control flow and runtime
    # computations it emits QGL1 instruction that the user may not
    # have imported.
    #
    # TODO: this is a hack, but the approach of adding these
    # blindly to the namespace is also a hack.  This is a
    # placeholder until we figure out a cleaner approach.

    required_imports = [
        'Wait', 'Barrier', 'Goto', 'LoadCmp', 'CmpEq', 'CmpNeq', 'CmpGt',
        'CmpLt', 'BlockLabel', 'Store'
    ]

    modname = ptree.qgl_fname
    for symbol in required_imports:
        if not add_import_from_as(importer, modname, 'qgl2.qgl1', symbol):
            NodeError.error_msg(ptree, 'Could not import %s' % symbol)
    NodeError.halt_on_error()

    ptree1 = ptree

    # We may need to iterate over the inlining processes a few times,
    # because inlining may expose new things to inline.
    #
    # TODO: as a stopgap, we're going to limit iterations to 20, which
    # is enough to handle fairly deeply-nested, complex non-recursive
    # programs.  What we do is iterate until we converge (the outcome
    # stops changing) or we hit this limit.  We should attempt at this
    # point to prove that the expansion is divergent, but we don't
    # do this, but instead assume the worst if the program is complex
    # enough to look like it's "probably" divergent.
    #

    print('%s: CALLING INLINER' % datetime.now())
    MAX_ITERS = 20
    for iteration in range(MAX_ITERS):

        print('%s: ITERATION %d' % (datetime.now(), iteration))

        inliner = Inliner(importer)
        ptree1 = inliner.inline_function(ptree1)
        NodeError.halt_on_error()

        if intermediate_output:
            print(('INLINED CODE (iteration %d):\n%s' %
                   (iteration, pyqgl2.ast_util.ast2str(ptree1))),
                  file=intermediate_fout,
                  flush=True)

        if inliner.change_cnt == 0:
            NodeError.diag_msg(
                None, ('expansion converged after iteration %d' % iteration))
            break

    if iteration == (MAX_ITERS - 1):
        NodeError.error_msg(
            None,
            ('expansion did not converge after %d iterations' % MAX_ITERS))

    # transform passed toplevel_bindings into a local_context dictionary

    # FIXME: If the qgl2main provides a default for an arg
    # that is 'missing', then don't count it as missing

    arg_names = [x.arg for x in ptree1.args.args]
    if isinstance(toplevel_bindings, tuple):
        if len(arg_names) != len(toplevel_bindings):
            NodeError.error_msg(
                None,
                'Invalid number of arguments supplied to qgl2main (got %d, expected %d)'
                % (len(toplevel_bindings), len(arg_names)))
        local_context = {
            name: quickcopy(value)
            for name, value in zip(arg_names, toplevel_bindings)
        }
    elif isinstance(toplevel_bindings, dict):
        invalid_args = toplevel_bindings.keys() - arg_names
        if len(invalid_args) > 0:
            NodeError.error_msg(
                None, 'Invalid arguments supplied to qgl2main: {}'.format(
                    invalid_args))
        missing_args = arg_names - toplevel_bindings.keys()
        if len(missing_args) > 0:
            NodeError.error_msg(
                None,
                'Missing arguments for qgl2main: {}'.format(missing_args))
        local_context = quickcopy(toplevel_bindings)
    elif toplevel_bindings:
        NodeError.error_msg(
            None, 'Unrecognized type for toplevel_bindings: {}'.format(
                type(toplevel_bindings)))
    else:
        local_context = None
    NodeError.halt_on_error()

    evaluator = EvalTransformer(SimpleEvaluator(importer, local_context))

    print('%s: CALLING EVALUATOR' % datetime.now())
    ptree1 = evaluator.visit(ptree1)
    NodeError.halt_on_error()

    if DebugMsg.ACTIVE_LEVEL < 3:
        print('%s: EVALUATOR RESULT:\n%s' %
              (datetime.now(), pyqgl2.ast_util.ast2str(ptree1)))
    # It's very hard to read the intermediate form, before the
    # QBIT names are added, so we don't save this right now.
    # print(('EVALUATOR RESULT:\n%s' % pyqgl2.ast_util.ast2str(ptree1)),
    #         file=intermediate_fout, flush=True)

    # Dump out all the variable bindings, for debugging purposes
    #
    # print('EV total state:')
    # evaluator.print_state()

    evaluator.replace_bindings(ptree1.body)

    if DebugMsg.ACTIVE_LEVEL < 3:
        print('%s: EVALUATOR REBINDINGS:\n%s' %
              (datetime.now(), pyqgl2.ast_util.ast2str(ptree1)))
    if intermediate_output:
        print(
            ('EVALUATOR + REBINDINGS:\n%s' % pyqgl2.ast_util.ast2str(ptree1)),
            file=intermediate_fout,
            flush=True)

    # base_namespace = importer.path2namespace[filename]

    # if intermediate_output:
    #     text = base_namespace.pretty_print()
    #     print(('EXPANDED NAMESPACE:\n%s' % text),
    #           file=intermediate_fout, flush=True)

    new_ptree1 = ptree1

    # Try to flatten out repeat, range, ifs
    flattener = Flattener()
    print('%s: CALLING FLATTENER' % datetime.now())
    new_ptree2 = flattener.visit(new_ptree1)
    NodeError.halt_on_error()
    if intermediate_output:
        print(('%s: FLATTENED CODE:\n%s' %
               (datetime.now(), pyqgl2.ast_util.ast2str(new_ptree2))),
              file=intermediate_fout,
              flush=True)

    # TODO Is it ever necessary to replace bindings again at this point?
    # evaluator.replace_bindings(new_ptree2.body)
    # evaluator.get_state()

    if intermediate_output:
        print(('Final qglmain: %s\n' % new_ptree2.name),
              file=intermediate_fout,
              flush=True)

    new_ptree3 = new_ptree2

    # Done. Time to generate the QGL1

    # Try to guess the proper function name
    fname = main_name
    if not fname:
        if isinstance(ptree, ast.FunctionDef):
            fname = ptree.name
        else:
            fname = "qgl1Main"

    # Get the QGL1 function that produces the proper sequences
    print('%s: GENERATING QGL1 SEQUENCE FUNCTION' % datetime.now())
    qgl1_main = get_sequence_function(new_ptree3,
                                      fname,
                                      importer,
                                      evaluator.allocated_qbits,
                                      intermediate_fout,
                                      saveOutput,
                                      filename,
                                      setup=evaluator.setup())
    NodeError.halt_on_error()
    return qgl1_main