Raisonance
CodeCompressor technology - Using Peephole scripts raisonance
Raisonance - Embedded Development Tools
Download free software Access our Support Forum
sitemap




access the Price List

 

 CodeCompressor Peephole scripts 

In addition to the standard optimizations it performs, CodeCompressor offers the possibility to users to describe and apply their own local optimizations via the Raisonance Peephole Script (RPS) language.
RPS is a language that is loosely similar to C; it is much more limited (syntax, data types), but it adds some specific controls and data structures to make it as easy as possible to scan the hex code (represented in assembler-like syntax) and perform local optimizations on it (delete lines, replace lines with other ones). If an RPS script is specified (see picture below), CodeCompressor will execute it after all the other compression steps (inlining and factorization), as a part of the peephole process. CodeCompressor will first execute the "standard" peephole optimizations defined for the architecture (for example, replacing LJMP by SJMP when possible, for the 8051) and then apply the script (one macro at a time) at each block (see the definition below) to see if user defined optimizations can be applied.

What can it do? Who should use it?

The main purpose of CodeCompressor scripts and the RPS language is to further improve the CodeCompressor capability to reduce code size by allowing the user to define and apply compression techniques that are specific to the architecture/application/user.
The RPS language was designed with code size reduction in mind, but, as at the end of the game it is just an instruction replacement tool (under certain conditions), it can also be used to achieve other purposes, such as eliminating completely some of the instructions (for derivatives which do not support them).
RPS scripts are tightly integrated into CodeCompressor, which means that, when describing your optimizations you don't have to worry about some "side effects" that would be heavy to deal with using an external tool:

  • when you replace sequences of instructions with shorter ones, CodeCompressor will automatically adjust all the jumps and calls in your program
  • CodeCompressor will only try to apply your optimizations within blocks, that is, you don't have to worry about JUMPs or CALLs pointing in the middle of the sequence you are trying to optimize
Note that the scope of RPS scripts is limited: they are executed in the frame of the peephole optimization pass, and, as explained above, although they are applied to the whole code, this is done only one block at a time. The purpose of these scripts is typically to optimize very local sequences of instructions that would look almost "stupid" to an experienced assembler programmer, but that can exist in the code due to the high level optimization algorithms used in the compiler combined with the the previous CodeCompressor passes. The kind of optimizations that can be performed with RPS are completely independent from the program purpose and from any optimization applied before: it's like scanning the whole code through a very narrow hole only letting you see a few instructions at a time (hence the expression "peephole") to check if some low level instructions can be replaced with other low level instructions regardless of the purpose of the instructions themselves.
In order to use RPS, you must be an experienced assembler programmer and know the architecture and all its details in such a way that you make sure that the optimizations you introduce don't alter the exectution of the program (even just potentially, by changing some flags that could be tested later on).

Structure of an RPS script.

This section shows the structure of an RPS script. It is intended to be general, but the code snippets used to show some concepts are from the 8051 world.
More details about RPS scripts, including examples specific to each architecture, are provided in the CodeCompressor manual.
Before explaining how RPS scripts work, we must introduce the concept of code blocks. Before doing any optimization, CodeCompressor splits the code into blocks: blocks of code can have different features, but they all share the same property that, in the whole application, there's no jump (or call) in the middle of a block. In other words, a block will always be executed sequentially, and this makes it safe to change the code in it with other code that do the same thing.
RPS scripts are plain text files that contain collections of macros. Each macro describes a different optimization. When the script is executed, CodeCompressor applies the first macro to each block of code (reorganizing all the other blocks if the macro makes changes that require adjusting the addresses), then the second macro to each block of code, and so on until the last macro.
An RPS macro is structured as follows:


Macro_name Macro_attributes
{
  declarations;
  search for instructions to be optimized;
  optimization to be applied;  
}
The most complex thing in the macro is the part specifying what should be optimized ("search for instructions to be optimized"). Supposing you want to perform the following optimization (8051 world): if DPTR is loaded with an immediate value and then is reloaded with another immediate value (without modifications between the two loads, including in subroutines) that is equal to the first-1 and is <256, then the second DPTR load could be replaced by a DEC DPL.
Before showing how to represent this rather complex condition in RPS, we should introduce the data structure RPS uses to represent instructions. For each assembler instruction RPS creates a structure containing (details can vary from one architecture to another)
  • Code:The hexadecimal code of the instruction
  • Mnemo:The mnemonic of the instruction (MOV, ADD etecetera)
  • op1,op2:The two operands
  • opx.type:The type of the operand (Reg, Data etcetera)
  • opx.reg:the register used (if applicable)
  • opx.index:the index value (if applicable)
  • opx.value:the immediate value (if applicable)
  • opx.offset:the offset value (if applicable)
Armed with this information let's see how the "DEC DPTR" macro would look like:

"Search MOV DPTR"
{
   MOV
   {
      // Search for a MOV DPTR, #imm
      SEARCH
      {
         MOV.mnemo    == "MOV";
         MOV.op1.reg  == "DPTR";
         MOV.op2.value < 0xFF;
      }
      // DPTR must not be modified (even in subroutines).
      NEXT
      {
         return( !IsWritten( MOV.op1.reg ) && !IsCall() );
      }
   }
   // The second MOV DPTR, #imm where imm
   reMOV
   {
      SEARCH
      {
         reMOV.mnemo     == "MOV";
         reMOV.op1.reg   == "DPTR";
         reMOV.op2.value == MOV.op2.value - 1;
      }
   }
   DO
   {
      printf( "DPTR reload optimized with DEC DPL at %.4X", Address( reMOV ) );
      // MOV DPTR, #xx -> DEC DPL
      Replace( reMOV, 0x1582 );
   }
}
As you see this script contains only one macro, called "Search MOV DPTR" that accomplishes the function described above. In order to do this the macro searches for an instruction of the kind MOV DPTR,#immediate (that it calls "MOV" for easier reference) and another instructions of the same kind (that it calls "reMOV"), while at the same time verifying that DPTR is not modified between the two, including in subroutines (that is the role of the "NEXT" section, that uses external subroutines not reported here).
This example is quite complex in order to give you an idea of the possibilities of the language, but very simple tasks are easy to describe with macros; the next example shows how to replace all MOVX A,#0 instructions with CLR A (one byte instead of two):

"CLARA"
{
   clra
   {
      SEARCH
      {
         clra.mnemo == "MOV";
         clra.op1.reg  == "A";
         clra.op2.type == "Data";
         clra.op2.value == 0x00;
     }
   }
   DO
   {
      printf( "MOV A,#00 replaced by CLR A at %.4X", Address( clra ) );
      Replace( clra, 0xE4 );
   }
}

Home | Company Profile | News & Events | Products | Support | Download | Buy on line | Contact