Difference between revisions of "Arrange timed code.cpp"
From Just in Time
m |
m |
||
(2 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
+ | This is a small C++ program that will read AVR assembly instructions from a file. It will then chop the list of assembly instructions into blocks at each unconditional jump (RJMP) that is found. Pairs of adjacent nops will also be translated into RJMPs, which also are used to cut up the sources in smaller blocks. Then the program will try to arrange the blocks in such a way that all conditional branches (BRxx instructions) are close enough to their branch target. Close enough means within 64 instructions, which is the maximum jump that a conditional branch can make. | ||
+ | |||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
// | // | ||
− | // Copyright (c) 2013 Danny Havenith | + | // Copyright (c) 2013,2014 Danny Havenith |
// | // | ||
// Distributed under the Boost Software License, Version 1.0. (See accompanying | // Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
Line 8: | Line 10: | ||
#include <iostream> | #include <iostream> | ||
+ | #include <fstream> | ||
#include <string> | #include <string> | ||
#include <map> | #include <map> | ||
#include <vector> | #include <vector> | ||
+ | #include <list> | ||
#include <algorithm> | #include <algorithm> | ||
#include <limits> | #include <limits> | ||
− | #include <boost/ | + | #include <boost/regex.hpp> |
+ | |||
+ | using std::vector; | ||
+ | using std::string; | ||
+ | using std::list; | ||
+ | using std::map; | ||
− | |||
/// Simple struct that can be used as an input to describe blocks of assembly instructions with jumps and labels | /// Simple struct that can be used as an input to describe blocks of assembly instructions with jumps and labels | ||
− | + | struct Instruction { | |
− | |||
− | |||
− | |||
string label; | string label; | ||
− | string | + | string instruction; |
}; | }; | ||
+ | using Instructions = vector<Instruction>; | ||
+ | |||
/// a contiguous block of assembly instructions. | /// a contiguous block of assembly instructions. | ||
Line 29: | Line 36: | ||
/// all relative jumps that emanate from this block and all labels that | /// all relative jumps that emanate from this block and all labels that | ||
/// are defined in this block. | /// are defined in this block. | ||
− | + | class Block | |
{ | { | ||
− | + | public: | |
− | + | Block( const Instructions &code) | |
− | + | :code(code) | |
− | typedef map<string, int> | + | { |
− | + | update_labels(); | |
+ | } | ||
+ | |||
+ | |||
+ | typedef map<string, int> LabelMap; | ||
string name; | string name; | ||
− | + | LabelMap labels; | |
− | + | LabelMap jumps; | |
+ | Instructions code; | ||
+ | |||
+ | private: | ||
+ | void update_labels(); | ||
− | |||
− | |||
− | |||
− | |||
}; | }; | ||
− | + | using Blocks = list<Block>; | |
− | + | ||
− | + | void Block::update_labels() | |
− | |||
− | |||
{ | { | ||
− | + | static const boost::regex branch("BR..\\s+(\\S+)\\s*"); | |
− | |||
int current_line = 0; | int current_line = 0; | ||
− | + | for( const auto &ins: code) | |
− | |||
{ | { | ||
− | + | if (!ins.label.empty()) | |
− | + | { | |
− | + | if (current_line == 0) name = ins.label; | |
− | + | labels[ins.label] = current_line; | |
− | + | } | |
− | + | ||
− | + | boost::smatch match; | |
− | + | if (regex_match( ins.instruction, match, branch)) | |
− | + | { | |
− | + | jumps[ match[1]] = current_line; | |
− | + | } | |
− | + | ++current_line; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
− | |||
} | } | ||
/// Given a sequence of blocks, determine the absolute addresses of the labels that | /// Given a sequence of blocks, determine the absolute addresses of the labels that | ||
/// appear in each block. | /// appear in each block. | ||
− | + | Block::LabelMap map_labels( const Blocks& blocks) | |
{ | { | ||
− | + | Block::LabelMap label_addresses; | |
// determine absolute labels | // determine absolute labels | ||
int current_block_startaddress = 0; | int current_block_startaddress = 0; | ||
− | + | for( const auto &b: blocks) | |
{ | { | ||
− | + | for( const auto &label: b.labels) | |
{ | { | ||
label_addresses[label.first] = label.second | label_addresses[label.first] = label.second | ||
+ current_block_startaddress; | + current_block_startaddress; | ||
} | } | ||
− | current_block_startaddress += b.size; | + | current_block_startaddress += b.code.size(); |
} | } | ||
Line 109: | Line 106: | ||
/// This function is faster than the calculate_fit() function because it can terminate immediately if it finds a jump | /// This function is faster than the calculate_fit() function because it can terminate immediately if it finds a jump | ||
/// that does not qualify. | /// that does not qualify. | ||
− | bool is_fit( const | + | bool is_fit( const Blocks &blocks) |
{ | { | ||
− | + | Block::LabelMap label_addresses = map_labels(blocks); | |
// see if all jumps are in range. | // see if all jumps are in range. | ||
int current_block_startaddress = 0; | int current_block_startaddress = 0; | ||
− | + | for( const auto &b: blocks) | |
{ | { | ||
− | + | for( const auto &jump: b.jumps) | |
{ | { | ||
int offset = label_addresses[jump.first] - (jump.second + current_block_startaddress); | int offset = label_addresses[jump.first] - (jump.second + current_block_startaddress); | ||
if (offset > 64 || offset < -63) return false; | if (offset > 64 || offset < -63) return false; | ||
} | } | ||
− | current_block_startaddress += b.size; | + | current_block_startaddress += b.code.size(); |
} | } | ||
Line 128: | Line 125: | ||
} | } | ||
− | / | + | /** |
− | + | * | |
− | + | * This is largely a copy of the is_fit() function with some changes. Instead of returning a fit/not-fit boolean, | |
− | + | * this function returns a score that is calculated as the maximum (absolute) jump distance. | |
− | int calculate_fit( const | + | * This function is slightly less efficient than that is_fit() function, because it needss to complete the |
+ | * calculation, even if the score is higher than the fitness criterium. | ||
+ | * | ||
+ | */ | ||
+ | int calculate_fit( const Blocks &blocks) | ||
{ | { | ||
− | + | Block::LabelMap label_addresses = map_labels( blocks); | |
// calculate the maximum jump offset in this sequence of blocks. | // calculate the maximum jump offset in this sequence of blocks. | ||
int max_offset = 0; | int max_offset = 0; | ||
int current_block_startaddress = 0; | int current_block_startaddress = 0; | ||
− | + | for( const auto &b: blocks) | |
{ | { | ||
− | + | for ( const auto &jump: b.jumps) | |
{ | { | ||
int offset = label_addresses[jump.first] - (jump.second + current_block_startaddress); | int offset = label_addresses[jump.first] - (jump.second + current_block_startaddress); | ||
Line 147: | Line 148: | ||
if (-offset > max_offset) max_offset = -offset; | if (-offset > max_offset) max_offset = -offset; | ||
} | } | ||
− | current_block_startaddress += b.size; | + | current_block_startaddress += b.code.size(); |
} | } | ||
Line 153: | Line 154: | ||
} | } | ||
− | / | + | /** |
− | + | * Replace adjacent NOPs with single RJMP instructions. | |
− | + | * The replacement is only performed if the second NOP has no label. | |
− | + | */ | |
− | + | void optimize_nops( Instructions code, Blocks &results) | |
− | { | + | { |
− | + | static int label_counter = 0; | |
− | { | + | auto position = code.begin(); |
− | + | auto previous_position = position; | |
− | + | auto both_are_nop = [](const Instruction &left, const Instruction &right){ | |
− | + | return left.instruction == "NOP" && right.instruction == "NOP" && right.label.empty(); | |
− | + | }; | |
− | + | ||
− | + | //Instructions first_instructions; | |
− | + | // find adjacent NOPs and break up the block, using RJMP to jump over the second NOP | |
− | + | position = std::adjacent_find( previous_position, code.end(), both_are_nop); | |
− | { | + | while ( code.end() != position) |
− | + | { | |
− | + | Instructions first_instructions; | |
− | + | first_instructions.insert( first_instructions.end(), previous_position, position); | |
− | + | previous_position = position+2; | |
− | + | if (previous_position->label.empty()) previous_position->label = "brk" + std::to_string( label_counter++); | |
− | + | first_instructions.push_back({position->label, "RJMP " + previous_position->label}); | |
− | + | results.push_back( Block(first_instructions)); | |
− | + | position = std::adjacent_find( previous_position, code.end(), both_are_nop); | |
− | + | } | |
− | { | + | |
− | + | Instructions first_instructions; | |
− | + | first_instructions.insert( first_instructions.end(), previous_position, position); | |
− | + | results.push_back( Block(first_instructions)); | |
− | + | } | |
− | + | ||
− | { | + | |
− | + | ||
− | + | /** | |
− | + | * Read assembly instructions from a text file. | |
− | + | * This function assumes the following: | |
− | + | * - each line consists of an optional label, followed by (non-optional) whitespace, followed by an instruction | |
− | { | + | * - the text consists of blocks, each block starts with a line that has a label and ends with an RJMP instruction |
− | { | + | * - the text lines inbetween blocks can be ignored. |
− | + | * - a line with a label, but without instruction is considered to have a "NOP" instruction | |
− | + | * - lines with the instruction "^^^" (possibly surrounded by whitespace) are ignored. | |
− | + | * | |
− | { | + | * When a sequence of two adjacent NOPs is encountered, for which the second NOP has no label, |
− | + | * the two NOPs will be replaced by a single RJMP instruction. | |
− | + | */ | |
− | + | Blocks parse_code( std::istream &input) | |
− | + | { | |
− | + | // an instruction consists of an optional label, followed by whitespace, followed by the instruction. | |
− | + | static const boost::regex instruction_pattern("^([^\\s]*)\\s+(.*)$"); | |
− | + | static const boost::regex rjmp("RJMP\\s+(.*)"); | |
− | + | static const boost::regex continuation("\\s*\\^\\^\\^\\s*"); | |
− | { | + | |
− | + | bool in_block = false; // whether we've seen the first label of a block | |
− | + | std::string buffer; | |
− | + | Blocks result; | |
− | + | Instructions current_block; | |
− | + | while (getline( input, buffer)) | |
− | + | { | |
− | + | boost::smatch match; | |
− | + | regex_match( buffer, match, instruction_pattern); | |
− | + | ||
− | + | in_block = in_block || match[1].length() != 0; | |
− | { | + | if (in_block) |
− | + | { | |
− | { | + | string label = match[1]; |
− | + | string ins = match[2]; | |
− | + | if (ins.empty()) ins = "NOP"; | |
− | + | ||
− | + | if (!regex_match( ins, continuation)) | |
− | + | { | |
− | + | current_block.push_back( {label, ins}); | |
− | + | } | |
− | + | ||
− | + | if (regex_match( ins, rjmp)) | |
− | + | { | |
− | { | + | optimize_nops( current_block, result); |
− | + | in_block = false; | |
− | { | + | current_block.clear(); |
− | + | } | |
− | + | } | |
− | + | } | |
− | + | ||
− | + | if (!current_block.empty()) | |
− | { | + | { |
− | + | optimize_nops( current_block, result); | |
− | + | } | |
− | + | ||
− | + | return result; | |
− | { | + | } |
− | + | ||
− | + | /** | |
− | + | * print a single block of instructions. | |
− | + | */ | |
− | + | void print_block( const Block &block, std::ostream &out) | |
− | { | + | { |
− | + | for (const auto &ins: block.code) | |
− | + | { | |
− | + | string label = ins.label.empty()?"":ins.label + ":"; | |
− | + | out << '"' << label << string( 8-label.size(), ' ') << ins.instruction << string(40 - ins.instruction.size(), ' ') << "\\n\"" << '\n'; | |
− | + | } | |
− | + | } | |
− | + | ||
− | { | + | /** |
− | + | * print all blocks in a vector of blocks, in the order in which they appear in the vector. | |
− | + | */ | |
− | + | void print_blocks( const Blocks &blocks, std::ostream &out) | |
− | + | { | |
− | + | for (const auto &b: blocks) | |
− | + | { | |
− | + | print_block( b, out); | |
− | + | } | |
− | + | } | |
− | + | ||
− | { | + | void add_block_at_optimum( Blocks &blocks, Block block) |
− | + | { | |
− | + | auto optimum = std::numeric_limits<int>::max(); | |
− | + | Blocks result; | |
− | { | + | |
− | + | for (auto it = blocks.begin(); it != blocks.end();++it) | |
− | + | { | |
− | + | Blocks candidate( blocks.begin(), it); | |