示例#1
0
    def update_next_block(self):
        """ If the last instruction of this block is a JP, JR or RET (with no
        conditions) then goes_to set contains just a
        single block
        """
        last = self.mem[-1]
        if last.inst not in {
                'djnz', 'jp', 'jr', 'call', 'ret', 'reti', 'retn', 'rst'
        }:
            return

        if last.inst in {'reti', 'retn'}:
            if self.next is not None:
                self.next.delete_comes_from(self)
            return

        if self.next is not None and last.condition_flag is None:  # jp NNN, call NNN, rst, jr NNNN, ret
            self.next.delete_comes_from(self)

        if last.inst == 'ret':
            return

        if last.opers[0] not in LABELS.keys():
            __DEBUG__(
                "INFO: %s is not defined. No optimization is done." %
                last.opers[0], 2)
            LABELS[last.opers[0]] = LabelInfo(
                last.opers[0], 0, DummyBasicBlock(ALL_REGS, ALL_REGS))

        n_block = LABELS[last.opers[0]].basic_block
        self.add_goes_to(n_block)
示例#2
0
def p_namespace(p):
    """ asm : NAMESPACE ID
    """
    global NAMESPACE

    NAMESPACE = normalize_namespace(p[2])
    __DEBUG__('Setting namespace to ' + (NAMESPACE or DOT), level=1)
示例#3
0
def p_def_label(p):
    """ line : ID EQU expr NEWLINE
             | ID EQU pexpr NEWLINE
    """
    p[0] = None
    __DEBUG__("Declaring '%s%s' in %i" % (NAMESPACE, p[1], p.lineno(1)))
    MEMORY.declare_label(p[1], p.lineno(1), p[3])
示例#4
0
    def declare_label(self,
                      label: str,
                      lineno: int,
                      value: int = None,
                      local: bool = False,
                      namespace: Optional[str] = None):
        """ Sets a label with the given value or with the current address (org)
        if no value is passed.

        Exits with error if label already set, otherwise return the label object
        """
        ex_label, namespace = Memory.id_name(label, namespace)

        is_address = value is None
        if value is None:
            value = self.org

        if is_address:
            __DEBUG__(
                f"Declaring '{ex_label}' (value {'%04Xh' % value}) in {lineno}"
            )
        else:
            __DEBUG__(f"Declaring '{ex_label}' in {lineno}")

        if ex_label in self.local_labels[-1].keys():
            self.local_labels[-1][ex_label].define(value, lineno)
            self.local_labels[-1][ex_label].is_address = is_address
        else:
            self.local_labels[-1][ex_label] = Label(ex_label, lineno, value,
                                                    local, namespace,
                                                    is_address)

        self.set_memory_slot()

        return self.local_labels[-1][ex_label]
示例#5
0
    def exit_proc(self, lineno: int):
        """ Exits current procedure. Local labels are transferred to global
        scope unless they have been marked as local ones.

        Raises an error if no current local context (stack underflow)
        """
        __DEBUG__('Exiting current scope from lineno %i' % lineno)

        if len(self.local_labels) <= 1:
            error(lineno, 'ENDP in global scope (with no PROC)')
            return

        for label in self.local_labels[-1].values():
            if label.local:
                if not label.defined:
                    error(lineno, "Undefined LOCAL label '%s'" % label.name)
                    return
                continue

            name = label.name
            _lineno = label.lineno
            value = label.value

            if name not in self.global_labels.keys():
                self.global_labels[name] = label
            else:
                self.global_labels[name].define(value, _lineno)

        self.local_labels.pop()  # Removes current context
        self.scopes.pop()
示例#6
0
 def enter_proc(self, lineno: int):
     """ Enters (pushes) a new context
     """
     self.local_labels.append({})  # Add a new context
     self.scopes.append(lineno)
     __DEBUG__('Entering scope level %i at line %i' %
               (len(self.scopes), lineno))
示例#7
0
def p_LOCAL(p):
    """ asm : LOCAL id_list
    """
    p[0] = None
    for label, line in p[2]:
        __DEBUG__("Setting label '%s' as local at line %i" % (label, line))

        MEMORY.set_label(label, line, local=True)
示例#8
0
    def _visit(self, node):
        self.norm_attr()
        if isinstance(node, Symbol):
            __DEBUG__('Visiting {}'.format(node.token), 1)
            if node.token in self.ATTR_TMP:
                return self.visit_ATTR_TMP(node)

        return TranslatorInstVisitor._visit(self, node)
示例#9
0
    def _visit(self, node):
        if node.obj is None:
            return None

        __DEBUG__("Optimizer: Visiting node {}".format(str(node.obj)), 1)
        methname = 'visit_' + node.obj.token
        meth = getattr(self, methname, None)
        if meth is None:
            meth = self.generic_visit

        return meth(node.obj)
示例#10
0
def p_push_namespace(p):
    """ asm : PUSH NAMESPACE
            | PUSH NAMESPACE ID
    """
    global NAMESPACE

    NAMESPACE_STACK.append(NAMESPACE)
    NAMESPACE = normalize_namespace(p[3] if len(p) == 4 else NAMESPACE)

    if NAMESPACE != NAMESPACE_STACK[-1]:
        __DEBUG__('Setting namespace to ' + (NAMESPACE or DOT), level=1)
示例#11
0
    def add_instruction(self, instr):
        """ This will insert an asm instruction at the current memory position
        in a t-uple as (mnemonic, params).

        It will also insert the opcodes at the memory_bytes
        """
        if gl.has_errors:
            return

        __DEBUG__('%04Xh [%04Xh] ASM: %s' %
                  (self.org, self.org - self.ORG, instr.asm))
        self.set_memory_slot()
        self.orgs[self.org] += (instr, )

        for byte in instr.bytes():
            self.__set_byte(byte, instr.lineno)
示例#12
0
def main(args=None, emitter=None):
    """ Entry point when executed from command line.
    You can use zxbc.py as a module with import, and this
    function won't be executed.
    """
    # region [Initialization]
    config.init()
    zxbpp.init()
    zxbparser.init()
    arch.target.backend.init()
    arch.target.Translator.reset()
    asmparse.init()
    # endregion

    parser = args_parser.parser()
    options = parser.parse_args(args=args)

    # ------------------------------------------------------------
    # Setting of internal parameters according to command line
    # ------------------------------------------------------------
    OPTIONS.Debug = options.debug
    OPTIONS.optimization = options.optimize
    OPTIONS.outputFileName = options.output_file
    OPTIONS.StdErrFileName = options.stderr
    OPTIONS.array_base = options.array_base
    OPTIONS.string_base = options.string_base
    OPTIONS.Sinclair = options.sinclair
    OPTIONS.heap_size = options.heap_size
    OPTIONS.memoryCheck = options.debug_memory
    OPTIONS.strictBool = options.strict_bool or OPTIONS.Sinclair
    OPTIONS.arrayCheck = options.debug_array
    OPTIONS.emitBackend = options.emit_backend
    OPTIONS.enableBreak = options.enable_break
    OPTIONS.explicit = options.explicit
    OPTIONS.memory_map = options.memory_map
    OPTIONS.strict = options.strict
    OPTIONS.headerless = options.headerless
    OPTIONS.zxnext = options.zxnext
    OPTIONS.expect_warnings = gl.EXPECTED_WARNINGS = options.expect_warnings
    OPTIONS.hide_warning_codes = options.hide_warning_codes

    if options.arch not in arch.AVAILABLE_ARCHITECTURES:
        parser.error(f"Invalid architecture '{options.arch}'")
        return 2

    OPTIONS.architecture = options.arch
    arch.set_target_arch(options.arch)
    backend = arch.target.backend

    # region [Enable/Disable Warnings]
    enabled_warnings = set(options.enable_warning or [])
    disabled_warnings = set(options.disable_warning or [])
    duplicated_options = [f"W{x}" for x in enabled_warnings.intersection(disabled_warnings)]

    if duplicated_options:
        parser.error(f"Warning(s) {', '.join(duplicated_options)} cannot be enabled "
                     f"and disabled simultaneously")
        return 2

    for warn_code in enabled_warnings:
        errmsg.enable_warning(warn_code)

    for warn_code in disabled_warnings:
        errmsg.disable_warning(warn_code)

    # endregion

    OPTIONS.org = src.api.utils.parse_int(options.org)
    if OPTIONS.org is None:
        parser.error("Invalid --org option '{}'".format(options.org))

    if options.defines:
        for i in options.defines:
            macro = list(i.split('=', 1))
            name = macro[0]
            val = ''.join(macro[1:])
            OPTIONS.__DEFINES[name] = val
            zxbpp.ID_TABLE.define(name, value=val, lineno=0)

    if OPTIONS.Sinclair:
        OPTIONS.array_base = 1
        OPTIONS.string_base = 1
        OPTIONS.strictBool = True
        OPTIONS.case_insensitive = True

    if options.ignore_case:
        OPTIONS.case_insensitive = True

    debug.ENABLED = OPTIONS.Debug

    if int(options.tzx) + int(options.tap) + int(options.asm) + int(options.emit_backend) + \
            int(options.parse_only) > 1:
        parser.error("Options --tap, --tzx, --emit-backend, --parse-only and --asm are mutually exclusive")
        return 3

    if options.basic and not options.tzx and not options.tap:
        parser.error('Option --BASIC and --autorun requires --tzx or tap format')
        return 4

    if options.append_binary and not options.tzx and not options.tap:
        parser.error('Option --append-binary needs either --tap or --tzx')
        return 5

    if options.asm and options.memory_map:
        parser.error('Option --asm and --mmap cannot be used together')
        return 6

    OPTIONS.use_loader = options.basic
    OPTIONS.autorun = options.autorun

    if options.tzx:
        OPTIONS.output_file_type = 'tzx'
    elif options.tap:
        OPTIONS.output_file_type = 'tap'
    elif options.asm:
        OPTIONS.output_file_type = 'asm'
    elif options.emit_backend:
        OPTIONS.output_file_type = 'ic'

    args = [options.PROGRAM]
    if not os.path.exists(options.PROGRAM):
        parser.error("No such file or directory: '%s'" % args[0])
        return 2

    if OPTIONS.memoryCheck:
        OPTIONS.__DEFINES['__MEMORY_CHECK__'] = ''
        zxbpp.ID_TABLE.define('__MEMORY_CHECK__', lineno=0)

    if OPTIONS.arrayCheck:
        OPTIONS.__DEFINES['__CHECK_ARRAY_BOUNDARY__'] = ''
        zxbpp.ID_TABLE.define('__CHECK_ARRAY_BOUNDARY__', lineno=0)

    if OPTIONS.enableBreak:
        OPTIONS.__DEFINES['__ENABLE_BREAK__'] = ''
        zxbpp.ID_TABLE.define('__ENABLE_BREAK__', lineno=0)

    OPTIONS.include_path = options.include_path
    OPTIONS.inputFileName = zxbparser.FILENAME = \
        os.path.basename(args[0])

    if not OPTIONS.outputFileName:
        OPTIONS.outputFileName = \
            os.path.splitext(os.path.basename(OPTIONS.inputFileName))[0] + os.path.extsep + \
            OPTIONS.output_file_type

    if OPTIONS.StdErrFileName:
        OPTIONS.stderr = open_file(OPTIONS.StdErrFileName, 'wt', 'utf-8')

    zxbpp.setMode('basic')
    zxbpp.main(args)

    if gl.has_errors:
        debug.__DEBUG__("exiting due to errors.")
        return 1  # Exit with errors

    input_ = zxbpp.OUTPUT
    zxbparser.parser.parse(input_, lexer=zxblex.lexer, tracking=True,
                           debug=(OPTIONS.Debug > 1))
    if gl.has_errors:
        debug.__DEBUG__("exiting due to errors.")
        return 1  # Exit with errors

    # Unreachable code removal
    unreachable_code_visitor = src.api.optimize.UnreachableCodeVisitor()
    unreachable_code_visitor.visit(zxbparser.ast)

    # Function calls graph
    func_call_visitor = src.api.optimize.FunctionGraphVisitor()
    func_call_visitor.visit(zxbparser.ast)

    # Optimizations
    optimizer = src.api.optimize.OptimizerVisitor()
    optimizer.visit(zxbparser.ast)

    # Emits intermediate code
    translator = arch.target.Translator()
    translator.visit(zxbparser.ast)

    if gl.DATA_IS_USED:
        gl.FUNCTIONS.extend(gl.DATA_FUNCTIONS)

    # This will fill MEMORY with pending functions
    func_visitor = arch.target.FunctionTranslator(gl.FUNCTIONS)
    func_visitor.start()

    # Emits data lines
    translator.emit_data_blocks()
    # Emits default constant strings
    translator.emit_strings()
    # Emits jump tables
    translator.emit_jump_tables()
    # Signals end of user code
    translator.ic_inline(';; --- end of user code ---')

    if OPTIONS.emitBackend:
        with open_file(OPTIONS.outputFileName, 'wt', 'utf-8') as output_file:
            for quad in translator.dumpMemory(backend.MEMORY):
                output_file.write(str(quad) + '\n')

            backend.MEMORY[:] = []  # Empties memory
            # This will fill MEMORY with global declared variables
            translator = arch.target.VarTranslator()
            translator.visit(zxbparser.data_ast)

            for quad in translator.dumpMemory(backend.MEMORY):
                output_file.write(str(quad) + '\n')
        return 0  # Exit success

    # Join all lines into a single string and ensures an INTRO at end of file
    asm_output = backend.emit(backend.MEMORY, optimize=OPTIONS.optimization > 0)
    asm_output = arch.target.optimizer.optimize(asm_output) + '\n'  # invoke the -O3

    asm_output = asm_output.split('\n')
    for i in range(len(asm_output)):
        tmp = backend.ASMS.get(asm_output[i], None)
        if tmp is not None:
            asm_output[i] = '\n'.join(tmp)

    asm_output = '\n'.join(asm_output)

    # Now filter them against the preprocessor again
    zxbpp.setMode('asm')
    zxbpp.OUTPUT = ''
    zxbpp.filter_(asm_output, args[0])

    # Now output the result
    asm_output = zxbpp.OUTPUT.split('\n')
    get_inits(asm_output)  # Find out remaining inits
    backend.MEMORY[:] = []

    # This will fill MEMORY with global declared variables
    var_checker = src.api.optimize.VariableVisitor()
    var_checker.visit(zxbparser.data_ast)
    translator = arch.target.VarTranslator()
    translator.visit(zxbparser.data_ast)
    if gl.has_errors:
        debug.__DEBUG__("exiting due to errors.")
        return 1  # Exit with errors

    tmp = [x for x in backend.emit(backend.MEMORY, optimize=False) if x.strip()[0] != '#']
    asm_output = backend.emit_start() + tmp \
                                      + ['%s:' % backend.DATA_END_LABEL, '%s:' % backend.MAIN_LABEL] \
                                      + asm_output + backend.emit_end()

    if options.asm:  # Only output assembler file
        with open_file(OPTIONS.outputFileName, 'wt', 'utf-8') as output_file:
            output(asm_output, output_file)
    elif not options.parse_only:
        fout = StringIO()
        output(asm_output, fout)
        asmparse.assemble(fout.getvalue())
        fout.close()
        asmparse.generate_binary(OPTIONS.outputFileName, OPTIONS.output_file_type,
                                 binary_files=options.append_binary,
                                 headless_binary_files=options.append_headless_binary,
                                 emitter=emitter)
        if gl.has_errors:
            return 5  # Error in assembly

    if OPTIONS.memory_map:
        if asmparse.MEMORY is not None:
            with open_file(OPTIONS.memory_map, 'wt', 'utf-8') as f:
                f.write(asmparse.MEMORY.memory_map)

    return gl.has_errors  # Exit success
示例#13
0
def optimize(initial_memory):
    """ This will remove useless instructions
    """
    global BLOCKS
    global PROC_COUNTER

    del MEMORY[:]
    PROC_COUNTER = 0

    cleanupmem(initial_memory)
    if OPTIONS.optimization <= 2:  # if -O2 or lower, do nothing and return
        return '\n'.join(x for x in initial_memory if not RE_PRAGMA.match(x))

    basicblock.BasicBlock.clean_asm_args = OPTIONS.optimization > 3
    bb = basicblock.BasicBlock(initial_memory)
    cleanup_local_labels(bb)
    initialize_memory(bb)

    BLOCKS = basic_blocks = basicblock.get_basic_blocks(
        bb)  # 1st partition the Basic Blocks

    for b in basic_blocks:
        __DEBUG__('--- BASIC BLOCK: {} ---'.format(b.id), 1)
        __DEBUG__('Code:\n' + '\n'.join('    {}'.format(x) for x in b.code), 1)
        __DEBUG__('Requires: {}'.format(b.requires()), 1)
        __DEBUG__('Destroys: {}'.format(b.destroys()), 1)
        __DEBUG__('Label goes: {}'.format(b.label_goes), 1)
        __DEBUG__('Comes from: {}'.format([x.id for x in b.comes_from]), 1)
        __DEBUG__('Goes to: {}'.format([x.id for x in b.goes_to]), 1)
        __DEBUG__('Next: {}'.format(b.next.id if b.next is not None else None),
                  1)
        __DEBUG__('Size: {}  Time: {}'.format(b.sizeof, b.max_tstates), 1)
        __DEBUG__('--- END ---', 1)

    LABELS['*START*'].basic_block.add_goes_to(basic_blocks[0])
    LABELS['*START*'].basic_block.next = basic_blocks[0]

    basic_blocks[0].prev = LABELS['*START*'].basic_block
    if END_PROGRAM_LABEL in LABELS:
        LABELS[END_PROGRAM_LABEL].basic_block.add_goes_to(
            LABELS['*__END_PROGRAM*'].basic_block)

    # In O3 we simplify the graph by reducing jumps over jumps
    for label in JUMP_LABELS:
        block = LABELS[label].basic_block
        if isinstance(block, DummyBasicBlock):
            continue

        # The instruction that starts this block must be one of jr / jp
        first = block.get_next_exec_instruction()
        if first is None or first.inst not in ('jp', 'jr'):
            continue

        for blk in list(LABELS[label].used_by):
            if not first.condition_flag or blk[
                    -1].condition_flag == first.condition_flag:
                new_label = first.opers[0]
                blk[-1].asm = blk[-1].code.replace(label, new_label)
                block.delete_comes_from(blk)
                LABELS[label].used_by.remove(blk)
                LABELS[new_label].used_by.add(blk)
                blk.add_goes_to(LABELS[new_label].basic_block)

    for x in basic_blocks:
        x.compute_cpu_state()

    filtered_patterns_list = [
        p for p in engine.PATTERNS if OPTIONS.optimization >= p.level >= 3
    ]
    for x in basic_blocks:
        x.optimize(filtered_patterns_list)

    for x in basic_blocks:
        if x.comes_from == [] and len(
            [y for y in JUMP_LABELS if x is LABELS[y].basic_block]):
            x.ignored = True

    return '\n'.join([
        y
        for y in flatten_list([x.code for x in basic_blocks if not x.ignored])
        if not RE_PRAGMA.match(y)
    ])
示例#14
0
 def emit(*args):
     """ Convert the given args to a Quad (3 address code) instruction
     """
     quad = Quad(*args)
     __DEBUG__('EMIT ' + str(quad))
     MEMORY.append(quad)
示例#15
0
def main(args=None, emitter=None):
    """ Entry point when executed from command line.
    zxbc can be used as python module. If so, bear in mind this function
    won't be executed unless explicitly called.
    """
    # region [Initialization]
    config.init()
    zxbpp.init()
    zxbparser.init()
    arch.target.backend.init()
    arch.target.Translator.reset()
    asmparse.init()
    # endregion

    options = parse_options(args)
    arch.set_target_arch(OPTIONS.architecture)
    backend = arch.target.backend
    args = [
        options.PROGRAM
    ]  # Strip out other options, because they're already set in the OPTIONS container
    input_filename = options.PROGRAM

    zxbpp.setMode('basic')
    zxbpp.main(args)

    if gl.has_errors:
        debug.__DEBUG__("exiting due to errors.")
        return 1  # Exit with errors

    input_ = zxbpp.OUTPUT
    zxbparser.parser.parse(input_,
                           lexer=zxblex.lexer,
                           tracking=True,
                           debug=(OPTIONS.debug_level > 1))
    if gl.has_errors:
        debug.__DEBUG__("exiting due to errors.")
        return 1  # Exit with errors

    # Unreachable code removal
    unreachable_code_visitor = src.api.optimize.UnreachableCodeVisitor()
    unreachable_code_visitor.visit(zxbparser.ast)

    # Function calls graph
    func_call_visitor = src.api.optimize.FunctionGraphVisitor()
    func_call_visitor.visit(zxbparser.ast)

    # Optimizations
    optimizer = src.api.optimize.OptimizerVisitor()
    optimizer.visit(zxbparser.ast)

    # Emits intermediate code
    translator = arch.target.Translator()
    translator.visit(zxbparser.ast)

    if gl.DATA_IS_USED:
        gl.FUNCTIONS.extend(gl.DATA_FUNCTIONS)

    # This will fill MEMORY with pending functions
    func_visitor = arch.target.FunctionTranslator(gl.FUNCTIONS)
    func_visitor.start()

    # Emits data lines
    translator.emit_data_blocks()
    # Emits default constant strings
    translator.emit_strings()
    # Emits jump tables
    translator.emit_jump_tables()
    # Signals end of user code
    translator.ic_inline(';; --- end of user code ---')

    if OPTIONS.emit_backend:
        with open_file(OPTIONS.output_filename, 'wt', 'utf-8') as output_file:
            for quad in translator.dumpMemory(backend.MEMORY):
                output_file.write(str(quad) + '\n')

            backend.MEMORY[:] = []  # Empties memory
            # This will fill MEMORY with global declared variables
            translator = arch.target.VarTranslator()
            translator.visit(zxbparser.data_ast)

            for quad in translator.dumpMemory(backend.MEMORY):
                output_file.write(str(quad) + '\n')
        return 0  # Exit success

    # Join all lines into a single string and ensures an INTRO at end of file
    asm_output = backend.emit(backend.MEMORY,
                              optimize=OPTIONS.optimization_level > 0)
    asm_output = arch.target.optimizer.optimize(
        asm_output) + '\n'  # invoke the -O3

    asm_output = asm_output.split('\n')
    for i in range(len(asm_output)):
        tmp = backend.ASMS.get(asm_output[i], None)
        if tmp is not None:
            asm_output[i] = '\n'.join(tmp)

    asm_output = '\n'.join(asm_output)

    # Now filter them against the preprocessor again
    zxbpp.setMode('asm')
    zxbpp.OUTPUT = ''
    zxbpp.filter_(asm_output, filename=input_filename)

    # Now output the result
    asm_output = zxbpp.OUTPUT.split('\n')
    get_inits(asm_output)  # Find out remaining inits
    backend.MEMORY[:] = []

    # This will fill MEMORY with global declared variables
    var_checker = src.api.optimize.VariableVisitor()
    var_checker.visit(zxbparser.data_ast)
    translator = arch.target.VarTranslator()
    translator.visit(zxbparser.data_ast)
    if gl.has_errors:
        debug.__DEBUG__("exiting due to errors.")
        return 1  # Exit with errors

    tmp = [
        x for x in backend.emit(backend.MEMORY, optimize=False)
        if x.strip()[0] != '#'
    ]
    asm_output = backend.emit_start() + tmp \
                                      + ['%s:' % backend.DATA_END_LABEL, '%s:' % backend.MAIN_LABEL] \
                                      + asm_output + backend.emit_end()

    if OPTIONS.output_file_type == FileType.ASM:  # Only output assembler file
        with open_file(OPTIONS.output_filename, 'wt', 'utf-8') as output_file:
            output(asm_output, output_file)
    elif not options.parse_only:
        fout = StringIO()
        output(asm_output, fout)
        asmparse.assemble(fout.getvalue())
        fout.close()
        asmparse.generate_binary(
            OPTIONS.output_filename,
            OPTIONS.output_file_type,
            binary_files=options.append_binary,
            headless_binary_files=options.append_headless_binary,
            emitter=emitter)
        if gl.has_errors:
            return 5  # Error in assembly

    if OPTIONS.memory_map:
        if asmparse.MEMORY is not None:
            with open_file(OPTIONS.memory_map, 'wt', 'utf-8') as f:
                f.write(asmparse.MEMORY.memory_map)

    if not gl.has_errors and options.save_config:
        src.api.config.save_config_into_file(
            options.save_config, src.api.config.ConfigSections.ZXBC)

    return gl.has_errors  # Exit success
示例#16
0
def get_basic_blocks(block):
    """ If a block is not partitionable, returns a list with the same block.
    Otherwise, returns a list with the resulting blocks, recursively.
    """
    result = []
    EDP = END_PROGRAM_LABEL + ':'

    new_block = block
    while new_block:
        block = new_block
        new_block = None

        for i, mem in enumerate(block):
            if i and mem.code == EDP:  # END_PROGRAM label always starts a basic block
                block, new_block = block_partition(block, i - 1)
                LABELS[END_PROGRAM_LABEL].basic_block = new_block
                break

            if mem.is_ender:
                block, new_block = block_partition(block, i)
                if not mem.condition_flag:
                    block.delete_goes_to(new_block)

                for l in mem.opers:
                    if l in LABELS:
                        JUMP_LABELS.add(l)
                        block.label_goes.append(l)
                break

            if mem.is_label and mem.code[:-1] not in LABELS:
                raise OptimizerError(
                    "Missing label '{}' in labels list".format(mem.code[:-1]))

            if mem.code in src.arch.zx48k.backend.ASMS:  # An inline ASM block
                block, new_block = block_partition(block, max(0, i - 1))
                break

        result.append(block)

    for label in JUMP_LABELS:
        blk = LABELS[label].basic_block
        if isinstance(blk, DummyBasicBlock):
            continue

        must_partition = False
        # This label must point to the beginning of blk, just before the code
        # Otherwise we must partition it (must_partition = True)
        for i, cell in enumerate(blk):
            if cell.inst == label:
                break  # already starts with this label

            if cell.is_label:
                continue  # It's another label

            if cell.is_ender:
                raise OptimizerInvalidBasicBlockError(blk)

            must_partition = True
        else:
            __DEBUG__("Label {} not found in BasicBlock {}".format(
                label, blk.id))
            continue

        if must_partition:
            j = result.index(blk)
            block_, new_block_ = block_partition(blk, i - 1)
            LABELS[label].basic_block = new_block_
            result.pop(j)
            result.insert(j, block_)
            result.insert(j + 1, new_block_)

    for b in result:
        b.update_goes_and_comes()

    return result
示例#17
0
    def update_goes_and_comes(self):
        """ Once the block is a Basic one, check the last instruction and updates
        goes_to and comes_from set of the receivers.
        Note: jp, jr and ret are already done in update_next_block()
        """
        if not len(self):
            return

        last = self.mem[-1]
        inst = last.inst
        oper = last.opers
        cond = last.condition_flag

        if not last.is_ender:
            return

        if cond is None:
            self.delete_goes_to(self.next)

        if last.inst in {'ret', 'reti', 'retn'} and cond is None:
            return  # subroutine returns are updated from CALLer blocks

        if oper and oper[0]:
            if oper[0] not in LABELS:
                __DEBUG__(
                    "INFO: %s is not defined. No optimization is done." %
                    oper[0], 1)
                LABELS[oper[0]] = LabelInfo(
                    oper[0], 0, DummyBasicBlock(ALL_REGS, ALL_REGS))

            LABELS[oper[0]].used_by.add(self)
            self.add_goes_to(LABELS[oper[0]].basic_block)

        if inst in {'djnz', 'jp', 'jr'}:
            return

        assert inst in ('call', 'rst')

        if self.next is None:
            raise OptimizerError("Unexpected NULL next block")

        final_blk = self.next  # The block all the final returns should go to
        stack = [LABELS[oper[0]].basic_block]
        bbset = IdentitySet()

        while stack:
            bb = stack.pop(0)
            while True:
                if bb is None:
                    bb = DummyBasicBlock(ALL_REGS, ALL_REGS)

                if bb in bbset:
                    break

                bbset.add(bb)

                if isinstance(bb, DummyBasicBlock):
                    bb.add_goes_to(final_blk)
                    break

                if bb:
                    bb1 = bb[-1]
                    if bb1.inst in {'ret', 'reti', 'retn'}:
                        bb.add_goes_to(final_blk)
                        if bb1.condition_flag is None:  # 'ret'
                            break
                    elif bb1.inst in (
                            'jp', 'jr'
                    ) and bb1.condition_flag is not None:  # jp/jr nc/nz/.. LABEL
                        if bb1.opers[
                                0] in LABELS:  # some labels does not exist (e.g. immediate numeric addresses)
                            stack.append(LABELS[bb1.opers[0]].basic_block)
                        else:
                            raise OptimizerError(
                                "Unknown block label '{}'".format(
                                    bb1.opers[0]))

                bb = bb.next  # next contiguous block
示例#18
0
    def __call__(self, table, macro: MacroCall) -> str:
        __DEBUG__("evaluating id '%s'" % self.name, DEBUG_LEVEL)
        if self.value is None:
            __DEBUG__("undefined (null) value. BUG?", DEBUG_LEVEL)
            return ''

        if self.evaluating:
            result = self.name
            if self.hasArgs:
                result += '('
                result += ', '.join(
                    table[arg.name]
                    (table, self) if isinstance(arg, ID) else str(arg)
                    for arg in self.args)
                result += ')'
            return result

        self.evaluating = True

        result = ''
        for token in self.value:
            __DEBUG__("evaluating token '%s'" % str(token), DEBUG_LEVEL)
            if isinstance(token, MacroCall):
                if isinstance(token.id_, MacroCall):
                    token.id_ = token.id_(table)
                __DEBUG__(
                    "token '%s'(%s) is a MacroCall" % (token.id_, str(token)),
                    DEBUG_LEVEL)
                if table.defined(token.id_):
                    tmp = table[token.id_]
                    __DEBUG__(
                        "'%s' is defined in the symbol table as '%s'" %
                        (token.id_, tmp.name), DEBUG_LEVEL)

                    if isinstance(tmp, ID) and not tmp.hasArgs:
                        __DEBUG__("'%s' is an ID" % tmp.name, DEBUG_LEVEL)
                        token = copy.deepcopy(token)
                        token.id_ = tmp(table, macro)
                        __DEBUG__("'%s' is the new id" % token.id_,
                                  DEBUG_LEVEL)

                __DEBUG__("executing MacroCall '%s'" % token.id_, DEBUG_LEVEL)
                tmp = token(table)
            else:
                if isinstance(token, ID):
                    __DEBUG__("token '%s' is an ID" % token.name, DEBUG_LEVEL)
                    token = token(table, macro)
                tmp = token

            __DEBUG__("token got value '%s'" % tmp, DEBUG_LEVEL)
            result += tmp

        self.evaluating = False
        return result
示例#19
0
def p_asm_label(p):
    """ asm : ID
    """
    __DEBUG__("Declaring '%s%s' (value %04Xh) in %i" %
              (NAMESPACE, p[1], MEMORY.org, p.lineno(1)))
    MEMORY.declare_label(p[1], p.lineno(1))
示例#20
0
 def visit_BLOCK(self, node):
     __DEBUG__('BLOCK', 2)
     for child in node.children:
         yield child
示例#21
0
    def __call__(self, symbolTable: 'prepro.DefinesTable' = None) -> str:
        """ Execute the macro call using LAZY evaluation
        """
        if isinstance(self.id_, MacroCall):
            self.id_ = self.id_()

        __DEBUG__("evaluating '%s'" % self.id_, DEBUG_LEVEL)
        if symbolTable is None:
            symbolTable = self.table

        # The macro is not defined => returned as is
        if not self.is_defined(symbolTable):
            __DEBUG__("macro '%s' not defined" % self.id_, DEBUG_LEVEL)
            tmp = self.id_
            if self.callargs is not None:
                tmp += str(self.callargs)
            __DEBUG__("evaluation result: %s" % tmp, DEBUG_LEVEL)
            return tmp

        # The macro is defined
        __DEBUG__("macro '%s' defined" % self.id_, DEBUG_LEVEL)
        table = copy.deepcopy(symbolTable)
        id_ = table[self.id_]  # Get the defined macro
        assert isinstance(id_, prepro.ID)
        if id_.hasArgs and self.callargs is None:
            return self.id_  # If no args passed, returned as is

        args = []
        if self.callargs:  # has args. Evaluate them removing spaces
            __DEBUG__("'%s' has args defined" % self.id_, DEBUG_LEVEL)
            __DEBUG__(
                "evaluating %i arg(s) for '%s'" %
                (len(self.callargs), self.id_), DEBUG_LEVEL)
            args = [x(table).strip() for x in self.callargs]
            __DEBUG__(
                "macro call: %s%s" % (self.id_, '(' + ', '.join(args) + ')'),
                DEBUG_LEVEL)

        if not id_.hasArgs:  # The macro doesn't need args
            __DEBUG__("'%s' has no args defined" % self.id_, DEBUG_LEVEL)
            tmp = id_(table)  # If no args passed, returned as is
            if self.callargs is not None:
                tmp += '(' + ', '.join(args) + ')'

            __DEBUG__("evaluation result: %s" % tmp, DEBUG_LEVEL)
            return tmp

        # Now ensure both args and callargs have the same length
        if len(self.callargs) != len(id_.args):
            raise PreprocError(
                'Macro "%s" expected %i params, got %i' %
                (str(self.id_), len(id_.args), len(self.callargs)),
                self.lineno)

        # Carry out unification
        __DEBUG__('carrying out args unification', DEBUG_LEVEL)
        for i in range(len(self.callargs)):
            __DEBUG__("arg '%s' = '%s'" % (id_.args[i].name, args[i]),
                      DEBUG_LEVEL)
            table.set(id_.args[i].name, self.lineno, args[i])

        tmp = id_(table)
        return tmp