Initially this was implemented by directly passing through trace events to the MemoryImporter, keeping a record of conditional jumps and opcodes, and UPDATEing all inserted rows in a second pass when the MemoryImporter is finished. Unfortunately, UPDATE is very slow, and keeping all information in memory till the end doesn't scale indefinitely. Therefore the implementation now delays passing memory access events upwards to the MemoryImporter only until enough branch history is aggregated, and taps into Importer's database operations with a set of new virtual functions that are called downwards. Change-Id: I159b2533932087087fb3049f4ff07a5f17a25a00
172 lines
5.9 KiB
C++
172 lines
5.9 KiB
C++
#include <algorithm>
|
|
#include "AdvancedMemoryImporter.hpp"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::object;
|
|
using namespace fail;
|
|
|
|
static fail::Logger LOG("AdvancedMemoryImporter");
|
|
|
|
std::string AdvancedMemoryImporter::database_additional_columns()
|
|
{
|
|
return MemoryImporter::database_additional_columns() +
|
|
"opcode INT UNSIGNED NULL, "
|
|
"duration BIGINT UNSIGNED AS (time2 - time1 + 1) PERSISTENT, "
|
|
"jumphistory INT UNSIGNED NULL, ";
|
|
}
|
|
|
|
void AdvancedMemoryImporter::database_insert_columns(std::string& sql, unsigned& num_columns)
|
|
{
|
|
// FIXME upcall?
|
|
sql = ", opcode, jumphistory";
|
|
num_columns = 2;
|
|
}
|
|
|
|
//#include <google/protobuf/text_format.h>
|
|
|
|
bool AdvancedMemoryImporter::database_insert_data(Trace_Event &ev, MYSQL_BIND *bind, unsigned num_columns, bool is_fake)
|
|
{
|
|
static my_bool null = true;
|
|
// FIXME upcall?
|
|
assert(num_columns == 2);
|
|
#if 0
|
|
// sanity check
|
|
if (!is_fake && delayed_entries.size() > 0 && ev.ip() != delayed_entries.front().ev.ip()) {
|
|
std::string out;
|
|
google::protobuf::TextFormat::PrintToString(ev, &out);
|
|
std::cout << "ev: " << out << std::endl;
|
|
google::protobuf::TextFormat::PrintToString(delayed_entries.front().ev, &out);
|
|
std::cout << "delayed_entries.front.ev: " << out << std::endl;
|
|
}
|
|
#endif
|
|
assert(is_fake || delayed_entries.size() == 0 || ev.ip() == delayed_entries.front().ev.ip());
|
|
bind[0].buffer_type = MYSQL_TYPE_LONG;
|
|
bind[0].is_unsigned = 1;
|
|
bind[0].buffer = &delayed_entries.front().opcode;
|
|
bind[1].buffer_type = MYSQL_TYPE_LONG;
|
|
bind[1].is_unsigned = 1;
|
|
bind[1].buffer = &m_cur_branchmask;
|
|
if (is_fake) {
|
|
bind[0].is_null = &null;
|
|
bind[1].is_null = &null;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AdvancedMemoryImporter::insert_delayed_entries(bool finalizing)
|
|
{
|
|
unsigned branchmask;
|
|
unsigned last_branches_before = UINT_MAX;
|
|
// If we don't know enough future, and there's a chance we'll learn more,
|
|
// delay further.
|
|
for (std::deque<DelayedTraceEntry>::iterator it = delayed_entries.begin();
|
|
it != delayed_entries.end() &&
|
|
(it->branches_before + BRANCH_WINDOW_SIZE <= branches_taken.size() ||
|
|
finalizing);
|
|
it = delayed_entries.erase(it)) {
|
|
// determine branche decisions before / after this mem event
|
|
if (it->branches_before != last_branches_before) {
|
|
branchmask = 0;
|
|
int pos = std::max(-(signed)BRANCH_WINDOW_SIZE, - (signed) it->branches_before);
|
|
int maxpos = std::min(BRANCH_WINDOW_SIZE, branches_taken.size() - it->branches_before);
|
|
for (; pos < maxpos; ++pos) {
|
|
branchmask |=
|
|
((unsigned) branches_taken[it->branches_before + pos])
|
|
<< (16 - pos - 1);
|
|
}
|
|
m_cur_branchmask = branchmask;
|
|
}
|
|
|
|
//LOG << "AdvancedMemoryImporter::insert_delayed_entries instr = " << it->instr << " data_address = " << it->ev.memaddr() << std::endl;
|
|
|
|
// trigger INSERT
|
|
// (will call back via database_insert_data() and ask for additional data)
|
|
MemoryImporter::handle_mem_event(it->curtime, it->instr, it->ev);
|
|
}
|
|
|
|
// FIXME branches_taken could be shrunk here to stay within a bounded
|
|
// memory footprint
|
|
}
|
|
|
|
bool AdvancedMemoryImporter::handle_ip_event(fail::simtime_t curtime, instruction_count_t instr,
|
|
Trace_Event &ev)
|
|
{
|
|
// Previous instruction was a branch, check and remember whether it was taken
|
|
if (m_last_was_conditional_branch) {
|
|
m_last_was_conditional_branch = false;
|
|
branches_taken.push_back(ev.ip() != m_ip_jump_not_taken);
|
|
}
|
|
|
|
// Check whether we know enough branch-taken future to INSERT a few more
|
|
// (delayed) trace entries
|
|
insert_delayed_entries(false);
|
|
|
|
if (!binary) {
|
|
/* Disassemble the binary if necessary */
|
|
llvm::InitializeAllTargetInfos();
|
|
llvm::InitializeAllTargetMCs();
|
|
llvm::InitializeAllDisassemblers();
|
|
|
|
if (error_code ec = createBinary(m_elf->getFilename(), binary)) {
|
|
LOG << m_elf->getFilename() << "': " << ec.message() << ".\n";
|
|
return false;
|
|
}
|
|
|
|
// necessary due to an AspectC++ bug triggered by LLVM 3.3's dyn_cast()
|
|
#ifndef __puma
|
|
ObjectFile *obj = dyn_cast<ObjectFile>(binary.get());
|
|
disas.reset(new LLVMDisassembler(obj));
|
|
#endif
|
|
disas->disassemble();
|
|
LLVMDisassembler::InstrMap &instr_map = disas->getInstrMap();
|
|
LOG << "instructions disassembled: " << std::dec << instr_map.size() << " Triple: " << disas->GetTriple() << std::endl;
|
|
#if 0
|
|
for (LLVMDisassembler::InstrMap::const_iterator it = instr_map.begin();
|
|
it != instr_map.end(); ++it) {
|
|
LOG << "DIS " << std::hex << it->second.address << " " << (int) it->second.length << std::endl;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const LLVMDisassembler::InstrMap &instr_map = disas->getInstrMap();
|
|
const LLVMDisassembler::InstrMap::const_iterator it = instr_map.find(ev.ip());
|
|
if (it == instr_map.end()) {
|
|
LOG << "WARNING: LLVMDisassembler hasn't disassembled instruction at 0x"
|
|
<< ev.ip() << " -- are you using LLVM < 3.3?" << std::endl;
|
|
return true; // probably weird things will happen now
|
|
}
|
|
const LLVMDisassembler::Instr &opcode = it->second;
|
|
|
|
/* Now we've got the opcode and know whether it's a conditional branch. If
|
|
* it is, the next IP event will tell us whether it was taken or not. */
|
|
if (opcode.conditional_branch) {
|
|
m_last_was_conditional_branch = true;
|
|
m_ip_jump_not_taken = opcode.address + opcode.length;
|
|
}
|
|
|
|
// IP events may need to be delayed, too, if the parent Importer draws any
|
|
// information from them. MemoryImporter does not, though.
|
|
//return MemoryImporter::handle_ip_event(curtime, instr, ev);
|
|
return true;
|
|
}
|
|
|
|
bool AdvancedMemoryImporter::handle_mem_event(fail::simtime_t curtime, instruction_count_t instr,
|
|
Trace_Event &ev)
|
|
{
|
|
const LLVMDisassembler::InstrMap &instr_map = disas->getInstrMap();
|
|
const LLVMDisassembler::Instr &opcode = instr_map.at(ev.ip());
|
|
DelayedTraceEntry entry = { curtime, instr, ev, opcode.opcode, branches_taken.size() };
|
|
delayed_entries.push_back(entry);
|
|
|
|
// delay upcall to handle_mem_event until we know enough future branch decisions
|
|
return true;
|
|
}
|
|
|
|
bool AdvancedMemoryImporter::trace_end_reached()
|
|
{
|
|
LOG << "inserting remaining trace events ..." << std::endl;
|
|
// INSERT the remaining entries (with incomplete branch future)
|
|
insert_delayed_entries(true);
|
|
return true;
|
|
}
|