Introduce per-isolate cache for compile time constants

Compile-time constant values are maintained in a cache, keyed
by script and token position. When the same code is compiled
again later, e.g. by the optimizing compiler, the value is
found in the cache and does not need to be computed again.

In this version, the key is a string concatenated from the
script url and the token position. That’s probably more overhead
than we want.

Added two compiler stat counters for number of cached constants
and number of cache hits. Running dart2js to compile a hello world
program results in about 1400 cached values and 85 cache hits.

BUG=
R=srdjan@google.com

Review URL: https://codereview.chromium.org//1308073005 .
This commit is contained in:
Matthias Hausner 2015-08-26 16:17:20 -07:00
parent c0fe214c47
commit 62e9d05668
8 changed files with 143 additions and 39 deletions

View file

@ -73,6 +73,8 @@ CompilerStats::CompilerStats(Isolate* isolate)
num_tokens_consumed(0),
num_token_checks(0),
num_tokens_lookahead(0),
num_cached_consts(0),
num_const_cache_hits(0),
num_classes_compiled(0),
num_functions_compiled(0),
num_implicit_final_getters(0),
@ -111,6 +113,8 @@ void CompilerStats::Print() {
OS::Print("Token lookahead: %" Pd64 " (%" Pd64 "%% of tokens checked)\n",
num_tokens_lookahead,
(100 * num_tokens_lookahead) / num_token_checks);
OS::Print("Consts cached: %" Pd64 "\n", num_cached_consts);
OS::Print("Consts cache hits: %" Pd64 "\n", num_const_cache_hits);
OS::Print("Classes parsed: %" Pd64 "\n", num_classes_compiled);
OS::Print("Functions compiled: %" Pd64 "\n", num_functions_compiled);

View file

@ -50,6 +50,8 @@ class CompilerStats {
int64_t num_tokens_consumed;
int64_t num_token_checks;
int64_t num_tokens_lookahead;
int64_t num_cached_consts;
int64_t num_const_cache_hits;
int64_t num_classes_compiled;
int64_t num_functions_compiled;

View file

@ -18142,6 +18142,11 @@ static intptr_t HashImpl(const T* characters, intptr_t len) {
}
intptr_t String::Hash(const char* characters, intptr_t len) {
return HashImpl(characters, len);
}
intptr_t String::Hash(const uint8_t* characters, intptr_t len) {
return HashImpl(characters, len);
}

View file

@ -5943,7 +5943,7 @@ class String : public Instance {
static intptr_t hash_offset() { return OFFSET_OF(RawString, hash_); }
static intptr_t Hash(const String& str, intptr_t begin_index, intptr_t len);
static intptr_t HashLatin1(const uint8_t* characters, intptr_t len);
static intptr_t Hash(const char* characters, intptr_t len);
static intptr_t Hash(const uint16_t* characters, intptr_t len);
static intptr_t Hash(const int32_t* characters, intptr_t len);
static intptr_t HashRawSymbol(const RawString* symbol) {

View file

@ -87,7 +87,8 @@ ObjectStore::ObjectStore()
lookup_port_handler_(Function::null()),
empty_uint32_array_(TypedData::null()),
handle_message_function_(Function::null()),
library_load_error_table_(Array::null()) {
library_load_error_table_(Array::null()),
compile_time_constants_(Array::null()) {
for (RawObject** current = from(); current <= to(); current++) {
ASSERT(*current == Object::null());
}

View file

@ -426,6 +426,13 @@ class ObjectStore {
return OFFSET_OF(ObjectStore, library_load_error_table_);
}
RawArray* compile_time_constants() const {
return compile_time_constants_;
}
void set_compile_time_constants(const Array& value) {
compile_time_constants_ = value.raw();
}
// Visit all object pointers.
void VisitObjectPointers(ObjectPointerVisitor* visitor);
@ -515,8 +522,9 @@ class ObjectStore {
RawTypedData* empty_uint32_array_;
RawFunction* handle_message_function_;
RawArray* library_load_error_table_;
RawArray* compile_time_constants_;
RawObject** to() {
return reinterpret_cast<RawObject**>(&library_load_error_table_);
return reinterpret_cast<RawObject**>(&compile_time_constants_);
}
friend class SnapshotReader;

View file

@ -16,6 +16,7 @@
#include "vm/flags.h"
#include "vm/growable_array.h"
#include "vm/handles.h"
#include "vm/hash_table.h"
#include "vm/heap.h"
#include "vm/isolate.h"
#include "vm/longjump.h"
@ -11900,6 +11901,66 @@ bool Parser::IsInstantiatorRequired() const {
}
class ConstMapKeyEqualsTraits {
public:
static bool IsMatch(const Object& a, const Object& b) {
return String::Cast(a).Equals(String::Cast(b));
}
static bool IsMatch(const char* key, const Object& b) {
return String::Cast(b).Equals(key);
}
static uword Hash(const Object& obj) {
return String::Cast(obj).Hash();
}
static uword Hash(const char* key) {
return String::Hash(key, strlen(key));
}
};
typedef UnorderedHashMap<ConstMapKeyEqualsTraits> ConstantsMap;
void Parser::CacheConstantValue(intptr_t token_pos, const Instance& value) {
String& key = String::Handle(Z, script_.url());
String& suffix =
String::Handle(Z, String::NewFormatted("_%" Pd "", token_pos));
key = Symbols::FromConcat(key, suffix);
if (isolate()->object_store()->compile_time_constants() == Array::null()) {
const intptr_t kInitialConstMapSize = 16;
isolate()->object_store()->set_compile_time_constants(
Array::Handle(Z, HashTables::New<ConstantsMap>(kInitialConstMapSize,
Heap::kNew)));
}
ConstantsMap constants(isolate()->object_store()->compile_time_constants());
constants.UpdateOrInsert(key, value);
if (FLAG_compiler_stats) {
isolate_->compiler_stats()->num_cached_consts = constants.NumOccupied();
}
isolate()->object_store()->set_compile_time_constants(constants.Release());
}
bool Parser::GetCachedConstant(intptr_t token_pos, Instance* value) {
if (isolate()->object_store()->compile_time_constants() == Array::null()) {
return false;
}
// We don't want to allocate anything in the heap here since this code
// is called from the optimizing compiler in the background thread. Allocate
// the key value in the zone instead.
const char* key = Z->PrintToString("%s_%" Pd "",
String::Handle(Z, script_.url()).ToCString(),
token_pos);
ConstantsMap constants(isolate()->object_store()->compile_time_constants());
bool is_present = false;
*value ^= constants.GetOrNull<const char *>(key, &is_present);
ASSERT(constants.Release().raw() ==
isolate()->object_store()->compile_time_constants());
if (FLAG_compiler_stats && is_present) {
isolate_->compiler_stats()->num_const_cache_hits++;
}
return is_present;
}
RawInstance* Parser::TryCanonicalize(const Instance& instance,
intptr_t token_pos) {
if (instance.IsNull()) {
@ -12522,6 +12583,15 @@ AstNode* Parser::ParseListLiteral(intptr_t type_pos,
ASSERT(type_pos >= 0);
ASSERT(CurrentToken() == Token::kLBRACK || CurrentToken() == Token::kINDEX);
const intptr_t literal_pos = TokenPos();
if (is_const) {
Instance& existing_const = Instance::ZoneHandle(Z);
if (GetCachedConstant(literal_pos, &existing_const)) {
SkipListLiteral();
return new(Z) LiteralNode(literal_pos, existing_const);
}
}
bool is_empty_literal = CurrentToken() == Token::kINDEX;
ConsumeToken();
@ -12589,8 +12659,8 @@ AstNode* Parser::ParseListLiteral(intptr_t type_pos,
if (is_const) {
// Allocate and initialize the const list at compile time.
Array& const_list =
Array::ZoneHandle(Z, Array::New(element_list.length(), Heap::kOld));
Array& const_list = Array::ZoneHandle(Z,
Array::New(element_list.length(), Heap::kOld));
const_list.SetTypeArguments(
TypeArguments::Handle(Z, list_type_arguments.Canonicalize()));
Error& malformed_error = Error::Handle(Z);
@ -12622,6 +12692,7 @@ AstNode* Parser::ParseListLiteral(intptr_t type_pos,
}
const_list.MakeImmutable();
const_list ^= TryCanonicalize(const_list, literal_pos);
CacheConstantValue(literal_pos, const_list);
return new(Z) LiteralNode(literal_pos, const_list);
} else {
// Factory call at runtime.
@ -12715,8 +12786,16 @@ AstNode* Parser::ParseMapLiteral(intptr_t type_pos,
ASSERT(type_pos >= 0);
ASSERT(CurrentToken() == Token::kLBRACE);
const intptr_t literal_pos = TokenPos();
ConsumeToken();
if (is_const) {
Instance& existing_const = Instance::ZoneHandle(Z);
if (GetCachedConstant(literal_pos, &existing_const)) {
SkipMapLiteral();
return new(Z) LiteralNode(literal_pos, existing_const);
}
}
ConsumeToken(); // Opening brace.
AbstractType& key_type = Type::ZoneHandle(Z, Type::DynamicType());
AbstractType& value_type = Type::ZoneHandle(Z, Type::DynamicType());
TypeArguments& map_type_arguments =
@ -12873,6 +12952,7 @@ AstNode* Parser::ParseMapLiteral(intptr_t type_pos,
"error executing const Map constructor");
} else {
const Instance& const_instance = Instance::Cast(constructor_result);
CacheConstantValue(literal_pos, const_instance);
return new(Z) LiteralNode(
literal_pos, Instance::ZoneHandle(Z, const_instance.raw()));
}
@ -13415,41 +13495,43 @@ AstNode* Parser::ParseNewOperator(Token::Kind op_kind) {
"const object creation",
external_constructor_name.ToCString());
}
const Object& constructor_result = Object::Handle(Z,
EvaluateConstConstructorCall(type_class,
type_arguments,
constructor,
arguments));
if (constructor_result.IsUnhandledException()) {
// It's a compile-time error if invocation of a const constructor
// call fails.
ReportErrors(Error::Cast(constructor_result),
script_, new_pos,
"error while evaluating const constructor");
Instance& const_instance = Instance::ZoneHandle(Z);
if (GetCachedConstant(new_pos, &const_instance)) {
// Cache hit, nothing else to do.
} else {
// Const constructors can return null in the case where a const native
// factory returns a null value. Thus we cannot use a Instance::Cast here.
Instance& const_instance = Instance::Handle(Z);
const_instance ^= constructor_result.raw();
new_object = new(Z) LiteralNode(
new_pos, Instance::ZoneHandle(Z, const_instance.raw()));
if (!type_bound.IsNull()) {
ASSERT(!type_bound.IsMalformed());
Error& malformed_error = Error::Handle(Z);
ASSERT(!is_top_level_); // We cannot check unresolved types.
if (!const_instance.IsInstanceOf(type_bound,
TypeArguments::Handle(Z),
&malformed_error)) {
type_bound = ClassFinalizer::NewFinalizedMalformedType(
malformed_error,
script_,
new_pos,
"const factory result is not an instance of '%s'",
String::Handle(Z, type_bound.UserVisibleName()).ToCString());
new_object = ThrowTypeError(new_pos, type_bound);
}
type_bound = AbstractType::null();
Object& constructor_result = Object::Handle(Z,
EvaluateConstConstructorCall(type_class,
type_arguments,
constructor,
arguments));
if (constructor_result.IsUnhandledException()) {
// It's a compile-time error if invocation of a const constructor
// call fails.
ReportErrors(Error::Cast(constructor_result),
script_, new_pos,
"error while evaluating const constructor");
}
const_instance ^= constructor_result.raw();
CacheConstantValue(new_pos, const_instance);
}
new_object = new(Z) LiteralNode(new_pos, const_instance);
if (!type_bound.IsNull()) {
ASSERT(!type_bound.IsMalformed());
Error& malformed_error = Error::Handle(Z);
ASSERT(!is_top_level_); // We cannot check unresolved types.
if (!const_instance.IsInstanceOf(type_bound,
TypeArguments::Handle(Z),
&malformed_error)) {
type_bound = ClassFinalizer::NewFinalizedMalformedType(
malformed_error,
script_,
new_pos,
"const factory result is not an instance of '%s'",
String::Handle(Z, type_bound.UserVisibleName()).ToCString());
new_object = ThrowTypeError(new_pos, type_bound);
}
type_bound = AbstractType::null();
}
} else {
CheckConstructorCallTypeArguments(new_pos, constructor, type_arguments);

View file

@ -820,6 +820,8 @@ class Parser : public ValueObject {
ArgumentListNode* arguments);
RawInstance* TryCanonicalize(const Instance& instance, intptr_t token_pos);
void CacheConstantValue(intptr_t token_pos, const Instance& value);
bool GetCachedConstant(intptr_t token_pos, Instance* value);
Thread* thread() const { return thread_; }
Isolate* isolate() const { return isolate_; }