"""

Linker
======

Each Machine Definition must provide for linking code that is being
generated to code that has not yet been generated.  The most obvious
place this comes up is within a single function when generating loops.
Another obvious place is function calls between generated code: the function
you are calling may not have been generated yet.

This package includes a utility classes to deal with Linking; it is
called LinkPos.  You instantiate one of these objects with all the
information about the spot in your generated assembly where a value
that cannot be determined yet needs to be filled in; for example, if
you had to generate a branch to an unknown address, you might
initially fill in an offset field of '0' and set up a link pos that
points at that offset field.  Then when the offset field is known the
link pos method write() will be called with the data and your value
will be patched in.

[Actually, the way the LinkPos class works, it is often more
convenient to simply leave space for the branch instruction but
currently use all zeroes.  Then you pass in the bits of the branch
with the target as 0 into the LinkPos class as the parameter 'union',
and use the 'shift' parameter to shift the offset when it is known
into its proper place in the instruction.  This is useful because the
LinkPos can only write at byte boundaries.  See the class description
for more details.] """

from chelpers import RJBuffer

class LinkPos:

    """ A class that encapsulates a pointer into a chelpers.RJBuffer;
    when you create it you specify where you want to write data and
    how you want it written.

    It's quite flexible.  In addition to specify the size of the
    write (8, 16, or 32) and the offset from the start of the buffer, you
    can specify that you want to shift the data and OR it with some other
    value.

    The shift/OR option is useful for when you need to patch some bits
    that are in an ackward place in the middle of a branch
    instruction.  In that case, you simply compute the rest of the
    bits from the branch instruction and specify those as the "or"
    option.  Then specify a shift so that the data gets shifted to the
    right position. """
    
    def __init__ (self, buffer, offset, size, shift, union):

        """ See LinkPos description for more details.  Here is a short
        reminder:
        
          'buffer': the RJBuffer
          'offset': offset from start of RJBuffer where the data
          should be written, measured in bytes
          'size':   8, 16, or 32; amount of total data to write at offset
          'shift':  we will shift the data to be written left by this amount
          before writing
          'union':  a value that will be ORed with the data after it is
          shifted; this allows you to frame the data """
        
        self.offset = buffer.size() - offset   # store as offset from end
        self.size = size
        self.union = union
        self.shift = shift
        self.buffer = buffer
        assert size == 32 or size == 16 or size == 8
        pass

    def get_buffer (self):
        return self.buffer

    def write (self, data):
        # Frame the incoming data appropriately:
        shdata = data << self.shift
        shdata |= self.union

        # Now write the final word out:
        writeloc = self.buffer.size() - self.offset
        if self.size == 32:
            self.buffer.set32 (writeloc, shdata)
        elif self.size == 16:
            self.buffer.set16 (writeloc, shdata)
        else:
            self.buffer.set8 (writeloc, shdata)
            pass
        return

    pass

class OffsetLinkPos (LinkPos):

    """ This is a LinkPos object that is specialized; rather than
    allowing for arbitrary data to be written at the given location,
    it expects to fill in an offset from the current position at some
    later date.  This is used for intra-function jumps. """

    def __init__ (self, buffer, offset, size, shift, union, calcoffset):

        """

        The arguments 'buffer', 'offset', 'size', 'shift, and 'union'
        mean the same thing as for LinkPos: together they specify where and
        what to write when the data is later available.

        The new argument, 'calcoffset', indicates the offset from the
        beginning of the RJBuffer where the eventual data should be
        computed from.

        For a typical RISC instruction set with 4 byte instructions, one
        would usually reserve the 4 bytes for the instruction on the
        RJBuffer by calling grow().  Then, you would create the bits for
        the instruction, leaving the target blank, and create an
        OffsetLinkPos() object with the following parameters:

        offset = 0    : put the data at what is currently the beginning
        size = 32     : write 32 bits of data
        shift = X     : whatever shift is appropriate to place the final offset
        union = Y     : the instruction with a '0' in place of the final offset
        calcoffset = 4: calculate the offset from the end of the instruction
        
        """

        LinkPos.__init__ (self, buffer, offset, size, shift, union)
        self.calcoffset = offset
        return

    def patch (self, offset):

        """ Points the target at a position 'offset' bytes after the beginning
        of the buffer. """

        # Calculate the position of the branch instruction as an offset
        # counting backwards from the end of the buffer
        brpos = self.offset + self.calcoffset

        # Calculate the current position as an offset counting backwards
        # from the end of the buffer
        curpos = self.buffer.size() - offset

        # Compute the offset and write it.  To convince yourself that
        # this is the correct ordering, consider the case where the
        # branch is the first instruction added to the buffer, so that
        # would mean 'brpos' is 0 --- since typically the offset is
        # computed from the byte after the instruction, and the
        # instruction terminates at the end of the buffer.  Now,
        # imagine there is one instruction which precedes the branch,
        # so the buffer has a total of 8 bytes (2 instructions), and
        # that offset is zero (thus pointing at the beginning of the
        # second instruction).  This mean curpos will be '8'.  The
        # correct answer for the branch offset is -8.
        #
        # Another way to think about is that brpos will always be <
        # curpos, and this class is only used for backwards branches.
        return self.write (brpos - curpos)

    pass
