Assemble a set of :class:`Op` and :class:`Label` instance back into
    bytecode.

    Arguments:
        ops(list): A list of opcodes and labels (as returned by
            :func:`disassemble`).
        target: The opcode specification of the targeted python
            version. If this is ``None`` the specification of the currently
            running python version will be used.

    Returns:
        bytes: The assembled bytecode.

def assemble(ops, target=None):
    """
    Assemble a set of :class:`Op` and :class:`Label` instance back into
    bytecode.

    Arguments:
        ops(list): A list of opcodes and labels (as returned by
            :func:`disassemble`).
        target: The opcode specification of the targeted python
            version. If this is ``None`` the specification of the currently
            running python version will be used.

    Returns:
        bytes: The assembled bytecode.
    """


    target = get_py_internals(target)

    opmap = target['opmap']
    hasjrel = target['hasjrel']
    hasjabs = target['hasjabs']
    hasjump = set(hasjrel) | set(hasjabs)
    have_argument = target['have_argument']
    extended_arg = target['extended_arg']
    wordcode = target['wordcode']

    if not wordcode:
        def encode_op(output, op_code, op_arg=None):
            n = 1
            if op_arg is None:
                output.append(op_code)
            else:
                n += 2
                ext_arg = op_arg >> 16
                if ext_arg:
                    n += 3
                    output.extend([extended_arg, ext_arg & 255, ext_arg >> 8])
                    op_arg &= 65535
                output.extend([op_code, op_arg & 255, op_arg >> 8])
            return n
    else:
        def encode_op(output, op_code, op_arg=None):
            n = 2
            if op_arg is None:
                output.extend([op_code, 0])
            else:
                ext_arg = op_arg >> 8
                if ext_arg:
                    n += encode_op(extended_arg, ext_arg)
                output.extend([op_code, op_arg & 255])
            return n

    # A bit of a chicken and egg problem: The address of a label depends on the instructions before it. However,
    # the instructions before a label might depend on the label itself: For very large functions, jumps may
    # require an EXTENDED_ARG opcode if the jump destination is far away. Which we only know when the label
    # has materialized, which means the address of the label will change on the next pass, which might mean
    # a different jump offset might become larger, etc... We run passes until no label changes address.

    label_address = {}
    while True:
        retry = False
        output = bytearray()
        address = 0

        for op in ops:
            if isinstance(op, Label):
                if label_address.get(op) != address:
                    retry = True
                    label_address[op] = address
                continue

            op_code = opmap[op.name]
            op_arg = op.arg

            if op_code >= have_argument and op_arg is None:
                # Sanity check.
                raise ValueError('Opcode %s requires argument.' % op)
            elif op_code < have_argument and op_arg is not None:
                # Sanity check.
                raise ValueError('Opcode %s should not have an argument.' % op)
            elif isinstance(op_arg, Label):
                if op_code not in hasjump:
                    # Sanity check.
                    raise ValueError('Did not expect label as argument for opcode %s.' % op)

                if op_arg not in ops:
                    # Sanity check.
                    raise ValueError('Label is not part of this op list.')

                # Try to turn the label argument into an address.
                op_arg = label_address.get(op_arg)
                if op_arg is None:
                    # Label hasn't materialized yet, we'll catch it on the next pass.
                    address += encode_op(output, op_code, 0)
                    continue

                if op_code in hasjrel:
                    op_arg -= address
            elif op_code in hasjump:
                # Sanity check.
                raise ValueError('Expected label as argument for opcode %s.' % op)

            # Encode the opcode and the argument.
            n = encode_op(output, op_code, op_arg)
            address += n

            if op_code in hasjrel:
                if not wordcode:
                    op_arg = output[-2] + (output[-1] << 8)
                    if op_arg < n:
                        ext_arg = output[-5] + (output[-4] << 8) - 1
                        output[-5], output[-4] = ext_arg & 255, ext_arg >> 8
                        op_arg += 65536
                    op_arg -= n
                    output[-2], output[-1] = op_arg & 255, op_arg >> 8
                else:
                    for i in itertools.count(1, 2):
                        if n <= output[-i]:
                            output[-i] -= n
                            break
                        output[-i] += 256 - n
                        n = 1

        if not retry:
            return bytes(output)