| Index: test/cctest/test-log.cc
|
| diff --git a/test/cctest/test-log.cc b/test/cctest/test-log.cc
|
| index 6a7e54f1d11af69765201f6d62a48881dc9b2291..bae56d6994a9c6c92b5bd063a5dae44002809b13 100644
|
| --- a/test/cctest/test-log.cc
|
| +++ b/test/cctest/test-log.cc
|
| @@ -7,15 +7,17 @@
|
| #include "v8.h"
|
|
|
| #include "log.h"
|
| -
|
| #include "cctest.h"
|
|
|
| +using v8::internal::Address;
|
| using v8::internal::Logger;
|
|
|
| +namespace i = v8::internal;
|
| +
|
| static void SetUp() {
|
| // Log to memory buffer.
|
| - v8::internal::FLAG_logfile = "*";
|
| - v8::internal::FLAG_log = true;
|
| + i::FLAG_logfile = "*";
|
| + i::FLAG_log = true;
|
| Logger::Setup();
|
| }
|
|
|
| @@ -103,8 +105,8 @@ TEST(BeyondWritePosition) {
|
|
|
| TEST(MemoryLoggingTurnedOff) {
|
| // Log to stdout
|
| - v8::internal::FLAG_logfile = "-";
|
| - v8::internal::FLAG_log = true;
|
| + i::FLAG_logfile = "-";
|
| + i::FLAG_log = true;
|
| Logger::Setup();
|
| CHECK_EQ(0, Logger::GetLogLines(0, NULL, 0));
|
| CHECK_EQ(0, Logger::GetLogLines(100, NULL, 0));
|
| @@ -114,4 +116,373 @@ TEST(MemoryLoggingTurnedOff) {
|
| }
|
|
|
|
|
| +static inline bool IsStringEqualTo(const char* r, const char* s) {
|
| + return strncmp(r, s, strlen(r)) == 0;
|
| +}
|
| +
|
| +
|
| +static bool Consume(const char* str, char** buf) {
|
| + if (IsStringEqualTo(str, *buf)) {
|
| + *buf += strlen(str);
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +
|
| +static void ParseAddress(char* start, Address* min_addr, Address* max_addr) {
|
| + Address addr = reinterpret_cast<Address>(strtoll(start, NULL, 16));
|
| + if (addr < *min_addr) *min_addr = addr;
|
| + if (addr > *max_addr) *max_addr = addr;
|
| +}
|
| +
|
| +
|
| +static Address ConsumeAddress(
|
| + char** start, Address min_addr, Address max_addr) {
|
| + char* end_ptr;
|
| + Address addr = reinterpret_cast<Address>(strtoll(*start, &end_ptr, 16));
|
| + CHECK_GE(addr, min_addr);
|
| + CHECK_GE(max_addr, addr);
|
| + *start = end_ptr;
|
| + return addr;
|
| +}
|
| +
|
| +
|
| +namespace {
|
| +
|
| +// A code entity is a pointer to a position of code-creation event in buffer log
|
| +// offset to a point where entity size begins, i.e.: '255,"func"\n'. This makes
|
| +// comparing code entities pretty easy.
|
| +typedef char* CodeEntityInfo;
|
| +
|
| +// A structure used to return log parsing results.
|
| +class ParseLogResult {
|
| + public:
|
| + ParseLogResult()
|
| + : min_addr(reinterpret_cast<Address>(-1)),
|
| + max_addr(reinterpret_cast<Address>(0)),
|
| + entities_map(NULL), entities(NULL),
|
| + max_entities(0) {};
|
| +
|
| + ~ParseLogResult() {
|
| + // See allocation code below.
|
| + if (entities_map != NULL) {
|
| + i::DeleteArray(entities_map - 1);
|
| + }
|
| + i::DeleteArray(entities);
|
| + }
|
| +
|
| + void AllocateEntities() {
|
| + // Make sure that the test doesn't operate on a bogus log.
|
| + CHECK_GT(max_entities, 0);
|
| + CHECK_GT(min_addr, 0);
|
| + CHECK_GT(max_addr, min_addr);
|
| +
|
| + entities = i::NewArray<CodeEntityInfo>(max_entities);
|
| + for (int i = 0; i < max_entities; ++i) {
|
| + entities[i] = NULL;
|
| + }
|
| + // We're adding fake items at [-1] and [size + 1] to simplify
|
| + // comparison code.
|
| + const int map_length = max_addr - min_addr + 1 + 2; // 2 fakes.
|
| + entities_map = i::NewArray<int>(map_length);
|
| + for (int i = 0; i < map_length; ++i) {
|
| + entities_map[i] = -1;
|
| + }
|
| + entities_map += 1; // Hide the -1 item, this is compensated on delete.
|
| + }
|
| +
|
| + // Minimal code entity address.
|
| + Address min_addr;
|
| + // Maximal code entity address.
|
| + Address max_addr;
|
| + // Memory map of entities start addresses. Biased by min_addr.
|
| + int* entities_map;
|
| + // An array of code entities.
|
| + CodeEntityInfo* entities;
|
| + // Maximal entities count. Actual entities count can be lower,
|
| + // empty entity slots are pointing to NULL.
|
| + int max_entities;
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +
|
| +typedef void (*ParserBlock)(char* start, char* end, ParseLogResult* result);
|
| +
|
| +static void ParserCycle(
|
| + char* start, char* end, ParseLogResult* result,
|
| + ParserBlock block_creation, ParserBlock block_delete,
|
| + ParserBlock block_move) {
|
| +
|
| + const char* code_creation = "code-creation,";
|
| + const char* code_delete = "code-delete,";
|
| + const char* code_move = "code-move,";
|
| +
|
| + const char* lazy_compile = "LazyCompile,";
|
| + const char* script = "Script,";
|
| + const char* function = "Function,";
|
| +
|
| + while (start < end) {
|
| + if (Consume(code_creation, &start)) {
|
| + if (Consume(lazy_compile, &start)
|
| + || Consume(script, &start)
|
| + || Consume(function, &start)) {
|
| + block_creation(start, end, result);
|
| + }
|
| + } else if (Consume(code_delete, &start)) {
|
| + block_delete(start, end, result);
|
| + } else if (Consume(code_move, &start)) {
|
| + block_move(start, end, result);
|
| + }
|
| + while (start < end && *start != '\n') ++start;
|
| + ++start;
|
| + }
|
| +}
|
| +
|
| +
|
| +static void Pass1CodeCreation(char* start, char* end, ParseLogResult* result) {
|
| + ParseAddress(start, &result->min_addr, &result->max_addr);
|
| + ++result->max_entities;
|
| +}
|
| +
|
| +
|
| +static void Pass1CodeDelete(char* start, char* end, ParseLogResult* result) {
|
| + ParseAddress(start, &result->min_addr, &result->max_addr);
|
| +}
|
| +
|
| +
|
| +static void Pass1CodeMove(char* start, char* end, ParseLogResult* result) {
|
| + // Skip old address.
|
| + while (start < end && *start != ',') ++start;
|
| + CHECK_GT(end, start);
|
| + ++start; // Skip ','.
|
| + ParseAddress(start, &result->min_addr, &result->max_addr);
|
| +}
|
| +
|
| +
|
| +static void Pass2CodeCreation(char* start, char* end, ParseLogResult* result) {
|
| + Address addr = ConsumeAddress(&start, result->min_addr, result->max_addr);
|
| + CHECK_GT(end, start);
|
| + ++start; // Skip ','.
|
| +
|
| + int idx = addr - result->min_addr;
|
| + result->entities_map[idx] = -1;
|
| + for (int i = 0; i < result->max_entities; ++i) {
|
| + // Find an empty slot and fill it.
|
| + if (result->entities[i] == NULL) {
|
| + result->entities[i] = start;
|
| + result->entities_map[idx] = i;
|
| + break;
|
| + }
|
| + }
|
| + // Make sure that a slot was found.
|
| + CHECK_GE(result->entities_map[idx], 0);
|
| +}
|
| +
|
| +
|
| +static void Pass2CodeDelete(char* start, char* end, ParseLogResult* result) {
|
| + Address addr = ConsumeAddress(&start, result->min_addr, result->max_addr);
|
| + int idx = addr - result->min_addr;
|
| + // There can be code deletes that are not related to JS code.
|
| + if (result->entities_map[idx] >= 0) {
|
| + result->entities[result->entities_map[idx]] = NULL;
|
| + result->entities_map[idx] = -1;
|
| + }
|
| +}
|
| +
|
| +
|
| +static void Pass2CodeMove(char* start, char* end, ParseLogResult* result) {
|
| + Address from_addr = ConsumeAddress(
|
| + &start, result->min_addr, result->max_addr);
|
| + CHECK_GT(end, start);
|
| + ++start; // Skip ','.
|
| + Address to_addr = ConsumeAddress(&start, result->min_addr, result->max_addr);
|
| + CHECK_GT(end, start);
|
| +
|
| + int from_idx = from_addr - result->min_addr;
|
| + int to_idx = to_addr - result->min_addr;
|
| + // There can be code moves that are not related to JS code.
|
| + if (from_idx != to_idx && result->entities_map[from_idx] >= 0) {
|
| + CHECK_EQ(-1, result->entities_map[to_idx]);
|
| + result->entities_map[to_idx] = result->entities_map[from_idx];
|
| + result->entities_map[from_idx] = -1;
|
| + };
|
| +}
|
| +
|
| +
|
| +static void ParseLog(char* start, char* end, ParseLogResult* result) {
|
| + // Pass 1: Calculate boundaries of addresses and entities count.
|
| + ParserCycle(start, end, result,
|
| + Pass1CodeCreation, Pass1CodeDelete, Pass1CodeMove);
|
| +
|
| + printf("min_addr: %p, max_addr: %p, entities: %d\n",
|
| + result->min_addr, result->max_addr, result->max_entities);
|
| +
|
| + result->AllocateEntities();
|
| +
|
| + // Pass 2: Fill in code entries data.
|
| + ParserCycle(start, end, result,
|
| + Pass2CodeCreation, Pass2CodeDelete, Pass2CodeMove);
|
| +}
|
| +
|
| +
|
| +static inline void PrintCodeEntityInfo(CodeEntityInfo entity) {
|
| + const int max_len = 50;
|
| + if (entity != NULL) {
|
| + char* eol = strchr(entity, '\n');
|
| + int len = eol - entity;
|
| + len = len <= max_len ? len : max_len;
|
| + printf("%-*.*s ", max_len, len, entity);
|
| + } else {
|
| + printf("%*s", max_len + 1, "");
|
| + }
|
| +}
|
| +
|
| +
|
| +static void PrintCodeEntitiesInfo(
|
| + bool is_equal, Address addr,
|
| + CodeEntityInfo l_entity, CodeEntityInfo r_entity) {
|
| + printf("%c %p ", is_equal ? ' ' : '*', addr);
|
| + PrintCodeEntityInfo(l_entity);
|
| + PrintCodeEntityInfo(r_entity);
|
| + printf("\n");
|
| +}
|
| +
|
| +
|
| +static inline int StrChrLen(const char* s, char c) {
|
| + return strchr(s, c) - s;
|
| +}
|
| +
|
| +
|
| +static bool AreFuncSizesEqual(CodeEntityInfo ref_s, CodeEntityInfo new_s) {
|
| + int ref_len = StrChrLen(ref_s, ',');
|
| + int new_len = StrChrLen(new_s, ',');
|
| + return ref_len == new_len && strncmp(ref_s, new_s, ref_len) == 0;
|
| +}
|
| +
|
| +
|
| +static bool AreFuncNamesEqual(CodeEntityInfo ref_s, CodeEntityInfo new_s) {
|
| + // Skip size.
|
| + ref_s = strchr(ref_s, ',') + 1;
|
| + new_s = strchr(new_s, ',') + 1;
|
| + int ref_len = StrChrLen(ref_s, '\n');
|
| + int new_len = StrChrLen(new_s, '\n');
|
| + // If reference is anonymous (""), it's OK to have anything in new.
|
| + if (ref_len == 2) return true;
|
| + // A special case for ErrorPrototype. Haven't yet figured out why they
|
| + // are different.
|
| + const char* error_prototype = "\"ErrorPrototype";
|
| + if (IsStringEqualTo(error_prototype, ref_s)
|
| + && IsStringEqualTo(error_prototype, new_s)) {
|
| + return true;
|
| + }
|
| + return ref_len == new_len && strncmp(ref_s, new_s, ref_len) == 0;
|
| +}
|
| +
|
| +
|
| +static bool AreEntitiesEqual(CodeEntityInfo ref_e, CodeEntityInfo new_e) {
|
| + if (ref_e == NULL && new_e != NULL) return true;
|
| + if (ref_e != NULL && new_e != NULL) {
|
| + return AreFuncSizesEqual(ref_e, new_e) && AreFuncNamesEqual(ref_e, new_e);
|
| + }
|
| + if (ref_e != NULL && new_e == NULL) {
|
| + // args_count entities (argument adapters) are not found by heap traversal,
|
| + // but they are not needed because they doesn't contain any code.
|
| + ref_e = strchr(ref_e, ',') + 1;
|
| + const char* args_count = "\"args_count:";
|
| + return IsStringEqualTo(args_count, ref_e);
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +
|
| +// Test that logging of code create / move / delete events
|
| +// is equivalent to traversal of a resulting heap.
|
| +TEST(EquivalenceOfLoggingAndTraversal) {
|
| + i::FLAG_logfile = "*";
|
| + i::FLAG_log = true;
|
| + i::FLAG_log_code = true;
|
| +
|
| + // Make sure objects move.
|
| + bool saved_always_compact = i::FLAG_always_compact;
|
| + if (!i::FLAG_never_compact) {
|
| + i::FLAG_always_compact = true;
|
| + }
|
| +
|
| + v8::Persistent<v8::Context> env = v8::Context::New();
|
| + v8::HandleScope scope;
|
| + env->Enter();
|
| +
|
| + // Compile and run a function that creates other functions.
|
| + v8::Script::Compile(v8::String::New(
|
| + "(function f() {\n"
|
| + " var rets = [];\n"
|
| + " for (var i = 0; i < 100; ++i) {\n"
|
| + " rets.push((function inc(n) { return n + 1; })(i));\n"
|
| + " }\n"
|
| + "})();"))->Run();
|
| + i::Heap::CollectAllGarbage();
|
| +
|
| + i::EmbeddedVector<char,204800> buffer;
|
| + int log_size;
|
| + ParseLogResult ref_result;
|
| +
|
| + // Retrieve the log.
|
| + {
|
| + // Make sure that no GCs occur prior to LogCompiledFunctions call.
|
| + i::AssertNoAllocation no_alloc;
|
| +
|
| + log_size = Logger::GetLogLines(0, buffer.start(), buffer.length());
|
| + CHECK_GT(log_size, 0);
|
| + CHECK_GT(buffer.length(), log_size);
|
| +
|
| + // Fill a map of compiled code objects.
|
| + ParseLog(buffer.start(), buffer.start() + log_size, &ref_result);
|
| + }
|
| +
|
| + // Iterate heap to find compiled functions, will write to log.
|
| + i::Logger::LogCompiledFunctions();
|
| + char* new_log_start = buffer.start() + log_size;
|
| + const int new_log_size = Logger::GetLogLines(
|
| + log_size, new_log_start, buffer.length() - log_size);
|
| + CHECK_GT(new_log_size, 0);
|
| + CHECK_GT(buffer.length(), log_size + new_log_size);
|
| +
|
| + // Fill an equivalent map of compiled code objects.
|
| + ParseLogResult new_result;
|
| + ParseLog(new_log_start, new_log_start + new_log_size, &new_result);
|
| +
|
| + // Test their actual equivalence.
|
| + bool results_equal = true;
|
| + int ref_idx = -1, new_idx = -1, ref_inc = 1, new_inc = 1;
|
| + while (ref_inc > 0 || new_inc > 0) {
|
| + const Address ref_addr = ref_result.min_addr + ref_idx;
|
| + const Address new_addr = new_result.min_addr + new_idx;
|
| + ref_inc = ref_addr <= ref_result.max_addr && ref_addr <= new_addr ? 1 : 0;
|
| + new_inc = new_addr <= new_result.max_addr && new_addr <= ref_addr ? 1 : 0;
|
| + const int ref_item = ref_result.entities_map[ref_idx];
|
| + const int new_item = new_result.entities_map[new_idx];
|
| + if (ref_item != -1 || new_item != -1) {
|
| + CodeEntityInfo ref_entity =
|
| + ref_item != -1 ? ref_result.entities[ref_item] : NULL;
|
| + CodeEntityInfo new_entity =
|
| + new_item != -1 ? new_result.entities[new_item] : NULL;
|
| + const bool equal = AreEntitiesEqual(ref_entity, new_entity);
|
| + if (!equal) results_equal = false;
|
| + PrintCodeEntitiesInfo(
|
| + equal, ref_inc != 0 ? ref_addr : new_addr,
|
| + ref_entity, new_entity);
|
| + }
|
| + ref_idx += ref_inc;
|
| + new_idx += new_inc;
|
| + }
|
| + CHECK(results_equal);
|
| +
|
| + env->Exit();
|
| + v8::V8::Dispose();
|
| + i::FLAG_always_compact = saved_always_compact;
|
| +}
|
| +
|
| +
|
| #endif // ENABLE_LOGGING_AND_PROFILING
|
|
|