Services and Modeling for Embedded Software Development
Embecosm divider strip
Prev  Next

6.3. Assembly Backend

The assembly backend is responsible for manipulating fixup values, replacing them with values where information is available. The class for this backend inherits from the MCAsmBackend.

[Note]Note

Information about the MCAsmBackend class can be found in LLVM's documentation at llvm.org/docs/doxygen/html/classllvm_1_1MCAsmBackend.html

The getNumFixupKinds function returns the number of fixups which the backend supports. This was defined as part of the Fixups enum, so this function simply returns this value.

unsigned getNumFixupKinds() const { return OR1K::NumTargetFixupKinds; }
        

The applyFixup function takes a fixup and provided data value and applies it to a given instruction.

To aid in this, a support function adjustFixupValue is created and called which manipulates the fixup's value before it is applied. This is done for example where a branch instruction does not store the exact location to branch to but that value without the first two bits. In this case, the value would be bitshifted by two before being applied.

With the fixup value adjusted appropriately, the instruction it is to be applied to is then reconstructed as a 64-bit unsigned integer. The fixup value is then shifted and masked into the correct location in the instruction before being applied. Once done, the now modified instruction is written back to the original data field.

The following example is from the OpenRISC 1000  implementation, with the data being loaded and manipulated in a big endian fashion.

void OR1KAsmBackend::applyFixup(const MCFixup &Fixup, char *Data,
                                unsigned DataSize, uint64_t Value) const {
  MCFixupKind Kind = Fixup.getKind();
  Value = adjustFixupValue((unsigned)Kind, Value);

  if (!Value)
    return; // This value doesn't change the encoding

  // Where in the object and where the number of bytes that need
  // fixing up
  unsigned Offset = Fixup.getOffset();
  unsigned NumBytes = (getFixupKindInfo(Kind).TargetSize + 7) / 8;
  unsigned FullSize;

  switch((unsigned)Kind) {
    default:
      FullSize = 4;
      break;
  }

  // Grab current value, if any, from bits.
  uint64_t CurVal = 0;

  // Load instruction and apply value
  for (unsigned i = 0; i != NumBytes; ++i) {
    unsigned Idx = (FullSize - 1 - i);
    CurVal |= (uint64_t)((uint8_t)Data[Offset + Idx]) << (i*8);
  }

  uint64_t Mask = ((uint64_t)(-1) >>
                   (64 - getFixupKindInfo(Kind).TargetSize));
  CurVal |= Value & Mask;

  // Write out the fixed up bytes back to the code/data bits.
  for (unsigned i = 0; i != NumBytes; ++i) {
    unsigned Idx = (FullSize - 1 - i);
    Data[Offset + Idx] = (uint8_t)((CurVal >> (i*8)) & 0xff);
  }
}
        

Where there are spaces in an instruction stream that need filling with executable instructions, a series of NOPs should be inserted. This is done via the writeNopData function, which specifies the size of memory that needs filling. If valid instructions can be placed into the instruction stream they are then created and emitted via the provided MCObjectWriter.

In the case of the OpenRISC 1000  implementation, the only NOP instruction is 32-bits long. Therefore if the space to fill is not a multiple of 4 bytes then the function returns false to indicate that it can't be filled. Otherwise for each set of four bytes, the encoding of a NOP is emitted via the ObjectWriters Write32 function.

bool OR1KAsmBackend::writeNopData(uint64_t Count, MCObjectWriter *OW) const {
  if ((Count % 4) != 0)
    return false;

  for (uint64_t i = 0; i < Count; i += 4)
    OW->Write32(0x15000000);

  return true;
}
        

Another function which needs implementing is relaxInstruction, which takes an instruction and relaxes it to a longer instruction with the same effects.

For targets where no instruction ever needs relaxation (e.g. all instructions are the same size), this function simply returns. Otherwise the longer instruction is created, copying and formatting operands as appropriate.

Likewise the mayNeedRelaxation function returns true/false depending on if the instruction may need to be relaxed. In the case of the OpenRISC 1000 , no instruction ever needs relaxing, therefore the function always returns false.

The fixupNeedsRelaxation returns whether an instruction needs to be relaxed based on the given fixup. As the case with the previous two functions, if no instruction needs to be relaxed this function will also always return false.

Finally, the getFixupKindInfo function needs overriding to provide information about target specific fixups, including their offset, size and flags. This function starts with a table containing details on each fixup.

If the fixup type provided is not target specific, the overridden function is called to get the result. Otherwise the entry is looked up in the table specified above and the relevant entry returned. If no entry exists in either table, then an error is raised.

[Note]Note

The entries in this table must be in the same order as specified in archFixupKinds.h.

const MCFixupKindInfo &OR1KAsmBackend::getFixupKindInfo(MCFixupKind Kind) const{
  const static MCFixupKindInfo Infos[OR1K::NumTargetFixupKinds] = {
    // This table *must* be in same the order of fixup_* kinds in
    // OR1KFixupKinds.h.
    //
    // name                    offset  bits  flags
    { "fixup_OR1K_32",          0,      32,   0 },
    { "fixup_OR1K_16",          0,      16,   0 },
    ... other fixups not shown ...
  };

  if (Kind < FirstTargetFixupKind)
     return MCAsmBackend::getFixupKindInfo(Kind);

  assert(unsigned(Kind - FirstTargetFixupKind) < getNumFixupKinds() &&
         "Invalid kind!");
  return Infos[Kind - FirstTargetFixupKind];
}
        

To enable and create the assembly backend, createarchAsmBackend is defined and returns a new archAsmBackend object, based on a given target and triple.

MCAsmBackend *llvm::createarchAsmBackend(const Target &T, StringRef TT) {
  Triple TheTriple(TT);
  return new archAsmBackend(T, Triple(TT).getOS());
}
        

Finally this function is set up with the MC target registry, associating the assembly backend with the target.

In archMCTargetDesc.cpp, the assembly backend is added in the same way as other components.

  // Register the ASM Backend
  TargetRegistry::RegisterMCAsmBackend(ThearchTarget,
                                       createarchAsmBackend);
        
Embecosm divider strip