mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 20:51:50 +00:00
b01318a160
Follow-up to efa1e18428
.
Issue dartbug.com/36097.
TEST=vm\dart_2\type_feedback_test.dart
Change-Id: I6619adc17b89b1c63293954bcf8da592e3d4f919
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/180262
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
1029 lines
34 KiB
C++
1029 lines
34 KiB
C++
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/compilation_trace.h"
|
|
|
|
#include "vm/closure_functions_cache.h"
|
|
#include "vm/compiler/jit/compiler.h"
|
|
#include "vm/globals.h"
|
|
#include "vm/log.h"
|
|
#include "vm/longjump.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/resolver.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/version.h"
|
|
|
|
namespace dart {
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
DEFINE_FLAG(bool, trace_compilation_trace, false, "Trace compilation trace.");
|
|
|
|
CompilationTraceSaver::CompilationTraceSaver(Zone* zone)
|
|
: buf_(zone, 1 * MB),
|
|
func_name_(String::Handle(zone)),
|
|
cls_(Class::Handle(zone)),
|
|
cls_name_(String::Handle(zone)),
|
|
lib_(Library::Handle(zone)),
|
|
uri_(String::Handle(zone)) {}
|
|
|
|
void CompilationTraceSaver::VisitFunction(const Function& function) {
|
|
if (!function.HasCode()) {
|
|
return; // Not compiled.
|
|
}
|
|
if (function.parent_function() != Function::null()) {
|
|
// Lookup works poorly for local functions. We compile all local functions
|
|
// in a compiled function instead.
|
|
return;
|
|
}
|
|
|
|
func_name_ = function.name();
|
|
func_name_ = String::RemovePrivateKey(func_name_);
|
|
cls_ = function.Owner();
|
|
cls_name_ = cls_.Name();
|
|
cls_name_ = String::RemovePrivateKey(cls_name_);
|
|
lib_ = cls_.library();
|
|
uri_ = lib_.url();
|
|
buf_.Printf("%s,%s,%s\n", uri_.ToCString(), cls_name_.ToCString(),
|
|
func_name_.ToCString());
|
|
}
|
|
|
|
CompilationTraceLoader::CompilationTraceLoader(Thread* thread)
|
|
: thread_(thread),
|
|
zone_(thread->zone()),
|
|
uri_(String::Handle(zone_)),
|
|
class_name_(String::Handle(zone_)),
|
|
function_name_(String::Handle(zone_)),
|
|
function_name2_(String::Handle(zone_)),
|
|
lib_(Library::Handle(zone_)),
|
|
cls_(Class::Handle(zone_)),
|
|
function_(Function::Handle(zone_)),
|
|
function2_(Function::Handle(zone_)),
|
|
field_(Field::Handle(zone_)),
|
|
sites_(Array::Handle(zone_)),
|
|
site_(ICData::Handle(zone_)),
|
|
static_type_(AbstractType::Handle(zone_)),
|
|
receiver_cls_(Class::Handle(zone_)),
|
|
target_(Function::Handle(zone_)),
|
|
selector_(String::Handle(zone_)),
|
|
args_desc_(Array::Handle(zone_)),
|
|
error_(Object::Handle(zone_)) {}
|
|
|
|
static char* FindCharacter(char* str, char goal, char* limit) {
|
|
while (str < limit) {
|
|
if (*str == goal) {
|
|
return str;
|
|
}
|
|
str++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
ObjectPtr CompilationTraceLoader::CompileTrace(uint8_t* buffer, intptr_t size) {
|
|
// First compile functions named in the trace.
|
|
char* cursor = reinterpret_cast<char*>(buffer);
|
|
char* limit = cursor + size;
|
|
while (cursor < limit) {
|
|
char* uri = cursor;
|
|
char* comma1 = FindCharacter(uri, ',', limit);
|
|
if (comma1 == NULL) {
|
|
break;
|
|
}
|
|
*comma1 = 0;
|
|
char* cls_name = comma1 + 1;
|
|
char* comma2 = FindCharacter(cls_name, ',', limit);
|
|
if (comma2 == NULL) {
|
|
break;
|
|
}
|
|
*comma2 = 0;
|
|
char* func_name = comma2 + 1;
|
|
char* newline = FindCharacter(func_name, '\n', limit);
|
|
if (newline == NULL) {
|
|
break;
|
|
}
|
|
*newline = 0;
|
|
error_ = CompileTriple(uri, cls_name, func_name);
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
cursor = newline + 1;
|
|
}
|
|
|
|
// Next, compile common dispatchers. These aren't found with the normal
|
|
// lookup above because they have irregular lookup that depends on the
|
|
// arguments descriptor (e.g. call() versus call(x)).
|
|
const Class& closure_class = Class::Handle(
|
|
zone_, thread_->isolate_group()->object_store()->closure_class());
|
|
Array& arguments_descriptor = Array::Handle(zone_);
|
|
Function& dispatcher = Function::Handle(zone_);
|
|
for (intptr_t argc = 1; argc <= 4; argc++) {
|
|
const intptr_t kTypeArgsLen = 0;
|
|
|
|
// TODO(dartbug.com/33549): Update this code to use the size of the
|
|
// parameters when supporting calls to closures with unboxed parameters.
|
|
arguments_descriptor = ArgumentsDescriptor::NewBoxed(kTypeArgsLen, argc);
|
|
dispatcher = closure_class.GetInvocationDispatcher(
|
|
Symbols::Call(), arguments_descriptor,
|
|
UntaggedFunction::kInvokeFieldDispatcher, true /* create_if_absent */);
|
|
error_ = CompileFunction(dispatcher);
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
}
|
|
|
|
// Finally, compile closures in all compiled functions. Note: We rely on the
|
|
// fact that parent functions are visited before children.
|
|
error_ = Object::null();
|
|
auto& result = Object::Handle(zone_);
|
|
ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& func) {
|
|
function2_ = func.parent_function();
|
|
if (function2_.HasCode()) {
|
|
result = CompileFunction(function_);
|
|
if (result.IsError()) {
|
|
error_ = result.ptr();
|
|
return false; // Stop iteration.
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
return Object::null();
|
|
}
|
|
|
|
// Use a fuzzy match to find the right function to compile. This allows a
|
|
// compilation trace to remain mostly valid in the face of program changes, and
|
|
// deals with implicit/dispatcher functions that don't have proper names.
|
|
// - Ignore private name mangling
|
|
// - If looking for a getter and we only have the corresponding regular method,
|
|
// compile the regular method, create its implicit closure and compile that.
|
|
// - If looking for a regular method and we only have the corresponding getter,
|
|
// compile the getter, create its method extractor and compile that.
|
|
// - If looking for a getter and we only have a const field, evaluate the const
|
|
// field.
|
|
ObjectPtr CompilationTraceLoader::CompileTriple(const char* uri_cstr,
|
|
const char* cls_cstr,
|
|
const char* func_cstr) {
|
|
uri_ = Symbols::New(thread_, uri_cstr);
|
|
class_name_ = Symbols::New(thread_, cls_cstr);
|
|
function_name_ = Symbols::New(thread_, func_cstr);
|
|
|
|
if (function_name_.Equals("_getMainClosure")) {
|
|
// The scheme for invoking main relies on compiling _getMainClosure after
|
|
// synthetically importing the root library.
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Compilation trace: skip %s,%s,%s\n", uri_.ToCString(),
|
|
class_name_.ToCString(), function_name_.ToCString());
|
|
}
|
|
return Object::null();
|
|
}
|
|
|
|
lib_ = Library::LookupLibrary(thread_, uri_);
|
|
if (lib_.IsNull()) {
|
|
// Missing library.
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Compilation trace: missing library %s,%s,%s\n",
|
|
uri_.ToCString(), class_name_.ToCString(),
|
|
function_name_.ToCString());
|
|
}
|
|
return Object::null();
|
|
}
|
|
|
|
bool is_dyn = Function::IsDynamicInvocationForwarderName(function_name_);
|
|
if (is_dyn) {
|
|
function_name_ =
|
|
Function::DemangleDynamicInvocationForwarderName(function_name_);
|
|
}
|
|
|
|
bool is_getter = Field::IsGetterName(function_name_);
|
|
bool is_init = Field::IsInitName(function_name_);
|
|
bool add_closure = false;
|
|
bool processed = false;
|
|
|
|
if (class_name_.Equals(Symbols::TopLevel())) {
|
|
function_ = lib_.LookupFunctionAllowPrivate(function_name_);
|
|
field_ = lib_.LookupFieldAllowPrivate(function_name_);
|
|
if (function_.IsNull() && is_getter) {
|
|
// Maybe this was a tear off.
|
|
add_closure = true;
|
|
function_name2_ = Field::NameFromGetter(function_name_);
|
|
function_ = lib_.LookupFunctionAllowPrivate(function_name2_);
|
|
field_ = lib_.LookupFieldAllowPrivate(function_name2_);
|
|
}
|
|
if (field_.IsNull() && is_getter) {
|
|
function_name2_ = Field::NameFromGetter(function_name_);
|
|
field_ = lib_.LookupFieldAllowPrivate(function_name2_);
|
|
}
|
|
if (field_.IsNull() && is_init) {
|
|
function_name2_ = Field::NameFromInit(function_name_);
|
|
field_ = lib_.LookupFieldAllowPrivate(function_name2_);
|
|
}
|
|
} else {
|
|
cls_ = lib_.SlowLookupClassAllowMultiPartPrivate(class_name_);
|
|
if (cls_.IsNull()) {
|
|
// Missing class.
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Compilation trace: missing class %s,%s,%s\n",
|
|
uri_.ToCString(), class_name_.ToCString(),
|
|
function_name_.ToCString());
|
|
}
|
|
return Object::null();
|
|
}
|
|
|
|
error_ = cls_.EnsureIsFinalized(thread_);
|
|
if (error_.IsError()) {
|
|
// Non-finalized class.
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Compilation trace: non-finalized class %s,%s,%s (%s)\n",
|
|
uri_.ToCString(), class_name_.ToCString(),
|
|
function_name_.ToCString(),
|
|
Error::Cast(error_).ToErrorCString());
|
|
}
|
|
return error_.ptr();
|
|
}
|
|
|
|
function_ = cls_.LookupFunctionAllowPrivate(function_name_);
|
|
field_ = cls_.LookupFieldAllowPrivate(function_name_);
|
|
if (function_.IsNull() && is_getter) {
|
|
// Maybe this was a tear off.
|
|
add_closure = true;
|
|
function_name2_ = Field::NameFromGetter(function_name_);
|
|
function_ = cls_.LookupFunctionAllowPrivate(function_name2_);
|
|
field_ = cls_.LookupFieldAllowPrivate(function_name2_);
|
|
if (!function_.IsNull() && !function_.is_static()) {
|
|
// Maybe this was a method extractor.
|
|
function2_ =
|
|
Resolver::ResolveDynamicAnyArgs(zone_, cls_, function_name_);
|
|
if (!function2_.IsNull()) {
|
|
error_ = CompileFunction(function2_);
|
|
if (error_.IsError()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print(
|
|
"Compilation trace: error compiling extractor %s for "
|
|
"%s,%s,%s (%s)\n",
|
|
function2_.ToCString(), uri_.ToCString(),
|
|
class_name_.ToCString(), function_name_.ToCString(),
|
|
Error::Cast(error_).ToErrorCString());
|
|
}
|
|
return error_.ptr();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (field_.IsNull() && is_getter) {
|
|
function_name2_ = Field::NameFromGetter(function_name_);
|
|
field_ = cls_.LookupFieldAllowPrivate(function_name2_);
|
|
}
|
|
if (field_.IsNull() && is_init) {
|
|
function_name2_ = Field::NameFromInit(function_name_);
|
|
field_ = cls_.LookupFieldAllowPrivate(function_name2_);
|
|
}
|
|
}
|
|
|
|
if (!field_.IsNull() && field_.is_const() && field_.is_static() &&
|
|
(field_.StaticValue() == Object::sentinel().ptr())) {
|
|
processed = true;
|
|
error_ = field_.InitializeStatic();
|
|
if (error_.IsError()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print(
|
|
"Compilation trace: error initializing field %s for %s,%s,%s "
|
|
"(%s)\n",
|
|
field_.ToCString(), uri_.ToCString(), class_name_.ToCString(),
|
|
function_name_.ToCString(), Error::Cast(error_).ToErrorCString());
|
|
}
|
|
return error_.ptr();
|
|
}
|
|
}
|
|
|
|
if (!function_.IsNull()) {
|
|
processed = true;
|
|
error_ = CompileFunction(function_);
|
|
if (error_.IsError()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Compilation trace: error compiling %s,%s,%s (%s)\n",
|
|
uri_.ToCString(), class_name_.ToCString(),
|
|
function_name_.ToCString(),
|
|
Error::Cast(error_).ToErrorCString());
|
|
}
|
|
return error_.ptr();
|
|
}
|
|
if (add_closure) {
|
|
function_ = function_.ImplicitClosureFunction();
|
|
error_ = CompileFunction(function_);
|
|
if (error_.IsError()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print(
|
|
"Compilation trace: error compiling closure %s,%s,%s (%s)\n",
|
|
uri_.ToCString(), class_name_.ToCString(),
|
|
function_name_.ToCString(), Error::Cast(error_).ToErrorCString());
|
|
}
|
|
return error_.ptr();
|
|
}
|
|
} else if (is_dyn) {
|
|
function_name_ = function_.name(); // With private mangling.
|
|
function_name_ =
|
|
Function::CreateDynamicInvocationForwarderName(function_name_);
|
|
function_ = function_.GetDynamicInvocationForwarder(function_name_);
|
|
error_ = CompileFunction(function_);
|
|
if (error_.IsError()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print(
|
|
"Compilation trace: error compiling dynamic forwarder %s,%s,%s "
|
|
"(%s)\n",
|
|
uri_.ToCString(), class_name_.ToCString(),
|
|
function_name_.ToCString(), Error::Cast(error_).ToErrorCString());
|
|
}
|
|
return error_.ptr();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!field_.IsNull() && field_.is_static() && !field_.is_const() &&
|
|
field_.has_nontrivial_initializer()) {
|
|
processed = true;
|
|
function_ = field_.EnsureInitializerFunction();
|
|
error_ = CompileFunction(function_);
|
|
if (error_.IsError()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print(
|
|
"Compilation trace: error compiling initializer %s,%s,%s (%s)\n",
|
|
uri_.ToCString(), class_name_.ToCString(),
|
|
function_name_.ToCString(), Error::Cast(error_).ToErrorCString());
|
|
}
|
|
return error_.ptr();
|
|
}
|
|
}
|
|
|
|
if (FLAG_trace_compilation_trace) {
|
|
if (!processed) {
|
|
THR_Print("Compilation trace: ignored %s,%s,%s\n", uri_.ToCString(),
|
|
class_name_.ToCString(), function_name_.ToCString());
|
|
}
|
|
}
|
|
return Object::null();
|
|
}
|
|
|
|
ObjectPtr CompilationTraceLoader::CompileFunction(const Function& function) {
|
|
if (function.is_abstract() || function.HasCode()) {
|
|
return Object::null();
|
|
}
|
|
|
|
error_ = Compiler::CompileFunction(thread_, function);
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
|
|
SpeculateInstanceCallTargets(function);
|
|
|
|
return error_.ptr();
|
|
}
|
|
|
|
// For instance calls, if the receiver's static type has one concrete
|
|
// implementation, lookup the target for that implementation and add it
|
|
// to the ICData's entries.
|
|
// For some well-known interfaces, do the same for the most common concrete
|
|
// implementation (e.g., int -> _Smi).
|
|
void CompilationTraceLoader::SpeculateInstanceCallTargets(
|
|
const Function& function) {
|
|
sites_ = function.ic_data_array();
|
|
if (sites_.IsNull()) {
|
|
return;
|
|
}
|
|
for (intptr_t i = 1; i < sites_.Length(); i++) {
|
|
site_ ^= sites_.At(i);
|
|
if (site_.rebind_rule() != ICData::kInstance) {
|
|
continue;
|
|
}
|
|
if (site_.NumArgsTested() != 1) {
|
|
continue;
|
|
}
|
|
|
|
static_type_ = site_.receivers_static_type();
|
|
if (static_type_.IsNull()) {
|
|
continue;
|
|
} else if (static_type_.IsDoubleType()) {
|
|
receiver_cls_ = IsolateGroup::Current()->class_table()->At(kDoubleCid);
|
|
} else if (static_type_.IsIntType()) {
|
|
receiver_cls_ = IsolateGroup::Current()->class_table()->At(kSmiCid);
|
|
} else if (static_type_.IsStringType()) {
|
|
receiver_cls_ =
|
|
IsolateGroup::Current()->class_table()->At(kOneByteStringCid);
|
|
} else if (static_type_.IsDartFunctionType() ||
|
|
static_type_.IsDartClosureType()) {
|
|
receiver_cls_ = IsolateGroup::Current()->class_table()->At(kClosureCid);
|
|
} else if (static_type_.HasTypeClass()) {
|
|
receiver_cls_ = static_type_.type_class();
|
|
if (receiver_cls_.is_implemented() || receiver_cls_.is_abstract()) {
|
|
continue;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
selector_ = site_.target_name();
|
|
args_desc_ = site_.arguments_descriptor();
|
|
target_ = Resolver::ResolveDynamicForReceiverClass(
|
|
receiver_cls_, selector_, ArgumentsDescriptor(args_desc_));
|
|
if (!target_.IsNull() && !site_.HasReceiverClassId(receiver_cls_.id())) {
|
|
intptr_t count = 0; // Don't pollute type feedback and coverage data.
|
|
site_.AddReceiverCheck(receiver_cls_.id(), target_, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
TypeFeedbackSaver::TypeFeedbackSaver(BaseWriteStream* stream)
|
|
: stream_(stream),
|
|
cls_(Class::Handle()),
|
|
lib_(Library::Handle()),
|
|
str_(String::Handle()),
|
|
fields_(Array::Handle()),
|
|
field_(Field::Handle()),
|
|
code_(Code::Handle()),
|
|
call_sites_(Array::Handle()),
|
|
call_site_(ICData::Handle()) {}
|
|
|
|
// These flags affect deopt ids.
|
|
static char* CompilerFlags() {
|
|
TextBuffer buffer(64);
|
|
|
|
#define ADD_FLAG(flag) buffer.AddString(FLAG_##flag ? " " #flag : " no-" #flag)
|
|
ADD_FLAG(enable_asserts);
|
|
ADD_FLAG(use_field_guards);
|
|
ADD_FLAG(use_osr);
|
|
ADD_FLAG(fields_may_be_reset);
|
|
#undef ADD_FLAG
|
|
|
|
return buffer.Steal();
|
|
}
|
|
|
|
void TypeFeedbackSaver::WriteHeader() {
|
|
const char* expected_version = Version::SnapshotString();
|
|
ASSERT(expected_version != NULL);
|
|
const intptr_t version_len = strlen(expected_version);
|
|
stream_->WriteBytes(reinterpret_cast<const uint8_t*>(expected_version),
|
|
version_len);
|
|
|
|
char* expected_features = CompilerFlags();
|
|
ASSERT(expected_features != NULL);
|
|
const intptr_t features_len = strlen(expected_features);
|
|
stream_->WriteBytes(reinterpret_cast<const uint8_t*>(expected_features),
|
|
features_len + 1);
|
|
free(expected_features);
|
|
}
|
|
|
|
void TypeFeedbackSaver::SaveClasses() {
|
|
ClassTable* table = IsolateGroup::Current()->class_table();
|
|
|
|
intptr_t num_cids = table->NumCids();
|
|
WriteInt(num_cids);
|
|
|
|
for (intptr_t cid = kNumPredefinedCids; cid < num_cids; cid++) {
|
|
cls_ = table->At(cid);
|
|
WriteClassByName(cls_);
|
|
}
|
|
}
|
|
|
|
void TypeFeedbackSaver::SaveFields() {
|
|
ClassTable* table = IsolateGroup::Current()->class_table();
|
|
intptr_t num_cids = table->NumCids();
|
|
for (intptr_t cid = kNumPredefinedCids; cid < num_cids; cid++) {
|
|
cls_ = table->At(cid);
|
|
WriteClassByName(cls_);
|
|
|
|
fields_ = cls_.fields();
|
|
WriteInt(fields_.Length());
|
|
for (intptr_t i = 0; i < fields_.Length(); i++) {
|
|
field_ ^= fields_.At(i);
|
|
|
|
str_ = field_.name();
|
|
str_ = String::RemovePrivateKey(str_);
|
|
WriteString(str_);
|
|
|
|
WriteInt(field_.guarded_cid());
|
|
WriteInt(static_cast<intptr_t>(field_.is_nullable()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TypeFeedbackSaver::VisitFunction(const Function& function) {
|
|
if (!function.HasCode()) {
|
|
return; // Not compiled.
|
|
}
|
|
|
|
cls_ = function.Owner();
|
|
WriteClassByName(cls_);
|
|
|
|
str_ = function.name();
|
|
str_ = String::RemovePrivateKey(str_);
|
|
WriteString(str_);
|
|
|
|
WriteInt(function.kind());
|
|
WriteInt(function.token_pos().Serialize());
|
|
|
|
code_ = function.CurrentCode();
|
|
intptr_t usage = function.usage_counter();
|
|
if (usage < 0) {
|
|
// Usage is set to INT32_MIN while in the background compilation queue ...
|
|
usage = (usage - INT32_MIN) + FLAG_optimization_counter_threshold;
|
|
} else if (code_.is_optimized()) {
|
|
// ... and set to 0 when an optimizing compile completes.
|
|
usage = usage + FLAG_optimization_counter_threshold;
|
|
}
|
|
WriteInt(usage);
|
|
|
|
WriteInt(function.inlining_depth());
|
|
|
|
call_sites_ = function.ic_data_array();
|
|
if (call_sites_.IsNull()) {
|
|
call_sites_ = Object::empty_array().ptr(); // Remove edge case.
|
|
}
|
|
|
|
// First element is edge counters.
|
|
WriteInt(call_sites_.Length() - 1);
|
|
for (intptr_t i = 1; i < call_sites_.Length(); i++) {
|
|
call_site_ ^= call_sites_.At(i);
|
|
|
|
WriteInt(call_site_.deopt_id());
|
|
WriteInt(call_site_.rebind_rule());
|
|
|
|
str_ = call_site_.target_name();
|
|
str_ = String::RemovePrivateKey(str_);
|
|
WriteString(str_);
|
|
|
|
intptr_t num_checked_arguments = call_site_.NumArgsTested();
|
|
WriteInt(num_checked_arguments);
|
|
|
|
intptr_t num_entries = call_site_.NumberOfChecks();
|
|
WriteInt(num_entries);
|
|
|
|
for (intptr_t entry_index = 0; entry_index < num_entries; entry_index++) {
|
|
WriteInt(call_site_.GetCountAt(entry_index));
|
|
|
|
for (intptr_t argument_index = 0; argument_index < num_checked_arguments;
|
|
argument_index++) {
|
|
WriteInt(call_site_.GetClassIdAt(entry_index, argument_index));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TypeFeedbackSaver::WriteClassByName(const Class& cls) {
|
|
lib_ = cls.library();
|
|
|
|
str_ = lib_.url();
|
|
WriteString(str_);
|
|
|
|
str_ = cls_.Name();
|
|
str_ = String::RemovePrivateKey(str_);
|
|
WriteString(str_);
|
|
}
|
|
|
|
void TypeFeedbackSaver::WriteString(const String& value) {
|
|
const char* cstr = value.ToCString();
|
|
intptr_t len = strlen(cstr);
|
|
stream_->WriteUnsigned(len);
|
|
stream_->WriteBytes(cstr, len);
|
|
}
|
|
|
|
TypeFeedbackLoader::TypeFeedbackLoader(Thread* thread)
|
|
: thread_(thread),
|
|
zone_(thread->zone()),
|
|
stream_(nullptr),
|
|
cid_map_(nullptr),
|
|
uri_(String::Handle(zone_)),
|
|
lib_(Library::Handle(zone_)),
|
|
cls_name_(String::Handle(zone_)),
|
|
cls_(Class::Handle(zone_)),
|
|
field_name_(String::Handle(zone_)),
|
|
fields_(Array::Handle(zone_)),
|
|
field_(Field::Handle(zone_)),
|
|
func_name_(String::Handle(zone_)),
|
|
func_(Function::Handle(zone_)),
|
|
call_sites_(Array::Handle(zone_)),
|
|
call_site_(ICData::Handle(zone_)),
|
|
target_name_(String::Handle(zone_)),
|
|
target_(Function::Handle(zone_)),
|
|
args_desc_(Array::Handle(zone_)),
|
|
functions_to_compile_(
|
|
GrowableObjectArray::Handle(zone_, GrowableObjectArray::New())),
|
|
error_(Error::Handle(zone_)) {}
|
|
|
|
TypeFeedbackLoader::~TypeFeedbackLoader() {
|
|
delete[] cid_map_;
|
|
}
|
|
|
|
ObjectPtr TypeFeedbackLoader::LoadFeedback(ReadStream* stream) {
|
|
stream_ = stream;
|
|
|
|
error_ = CheckHeader();
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
|
|
error_ = LoadClasses();
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
|
|
error_ = LoadFields();
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
|
|
while (stream_->PendingBytes() > 0) {
|
|
error_ = LoadFunction();
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
}
|
|
|
|
while (functions_to_compile_.Length() > 0) {
|
|
func_ ^= functions_to_compile_.RemoveLast();
|
|
|
|
if (Compiler::CanOptimizeFunction(thread_, func_) &&
|
|
(func_.usage_counter() >= FLAG_optimization_counter_threshold)) {
|
|
error_ = Compiler::CompileOptimizedFunction(thread_, func_);
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Done loading feedback\n");
|
|
}
|
|
|
|
return Error::null();
|
|
}
|
|
|
|
ObjectPtr TypeFeedbackLoader::CheckHeader() {
|
|
const char* expected_version = Version::SnapshotString();
|
|
ASSERT(expected_version != NULL);
|
|
const intptr_t version_len = strlen(expected_version);
|
|
if (stream_->PendingBytes() < version_len) {
|
|
const intptr_t kMessageBufferSize = 128;
|
|
char message_buffer[kMessageBufferSize];
|
|
Utils::SNPrint(message_buffer, kMessageBufferSize,
|
|
"No snapshot version found, expected '%s'",
|
|
expected_version);
|
|
const String& msg = String::Handle(String::New(message_buffer, Heap::kOld));
|
|
return ApiError::New(msg, Heap::kOld);
|
|
}
|
|
|
|
const char* version =
|
|
reinterpret_cast<const char*>(stream_->AddressOfCurrentPosition());
|
|
ASSERT(version != NULL);
|
|
if (strncmp(version, expected_version, version_len) != 0) {
|
|
const intptr_t kMessageBufferSize = 256;
|
|
char message_buffer[kMessageBufferSize];
|
|
char* actual_version = Utils::StrNDup(version, version_len);
|
|
Utils::SNPrint(message_buffer, kMessageBufferSize,
|
|
"Wrong snapshot version, expected '%s' found '%s'",
|
|
expected_version, actual_version);
|
|
free(actual_version);
|
|
const String& msg = String::Handle(String::New(message_buffer, Heap::kOld));
|
|
return ApiError::New(msg, Heap::kOld);
|
|
}
|
|
stream_->Advance(version_len);
|
|
|
|
char* expected_features = CompilerFlags();
|
|
ASSERT(expected_features != NULL);
|
|
const intptr_t expected_len = strlen(expected_features);
|
|
|
|
const char* features =
|
|
reinterpret_cast<const char*>(stream_->AddressOfCurrentPosition());
|
|
ASSERT(features != NULL);
|
|
intptr_t buffer_len = Utils::StrNLen(features, stream_->PendingBytes());
|
|
if ((buffer_len != expected_len) ||
|
|
(strncmp(features, expected_features, expected_len) != 0)) {
|
|
const String& msg = String::Handle(String::NewFormatted(
|
|
Heap::kOld,
|
|
"Feedback not compatible with the current VM configuration: "
|
|
"the feedback requires '%.*s' but the VM has '%s'",
|
|
static_cast<int>(buffer_len > 1024 ? 1024 : buffer_len), features,
|
|
expected_features));
|
|
free(expected_features);
|
|
return ApiError::New(msg, Heap::kOld);
|
|
}
|
|
free(expected_features);
|
|
stream_->Advance(expected_len + 1);
|
|
return Error::null();
|
|
}
|
|
|
|
ObjectPtr TypeFeedbackLoader::LoadClasses() {
|
|
num_cids_ = ReadInt();
|
|
|
|
cid_map_ = new intptr_t[num_cids_];
|
|
for (intptr_t cid = 0; cid < num_cids_; cid++) {
|
|
cid_map_[cid] = kIllegalCid;
|
|
}
|
|
for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) {
|
|
cid_map_[cid] = cid;
|
|
}
|
|
|
|
for (intptr_t cid = kNumPredefinedCids; cid < num_cids_; cid++) {
|
|
cls_ = ReadClassByName();
|
|
if (!cls_.IsNull()) {
|
|
cid_map_[cid] = cls_.id();
|
|
}
|
|
}
|
|
|
|
return Error::null();
|
|
}
|
|
|
|
ObjectPtr TypeFeedbackLoader::LoadFields() {
|
|
for (intptr_t cid = kNumPredefinedCids; cid < num_cids_; cid++) {
|
|
cls_ = ReadClassByName();
|
|
bool skip = cls_.IsNull();
|
|
|
|
intptr_t num_fields = ReadInt();
|
|
if (!skip && (num_fields > 0)) {
|
|
error_ = cls_.EnsureIsFinalized(thread_);
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
fields_ = cls_.fields();
|
|
}
|
|
|
|
SafepointWriteRwLocker ml(thread_,
|
|
thread_->isolate_group()->program_lock());
|
|
for (intptr_t i = 0; i < num_fields; i++) {
|
|
field_name_ = ReadString();
|
|
intptr_t guarded_cid = cid_map_[ReadInt()];
|
|
intptr_t is_nullable = ReadInt();
|
|
|
|
if (skip) {
|
|
continue;
|
|
}
|
|
|
|
if (i >= fields_.Length()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Missing field %s\n", field_name_.ToCString());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
field_ ^= fields_.At(i);
|
|
if (!String::EqualsIgnoringPrivateKey(String::Handle(field_.name()),
|
|
field_name_)) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Missing field %s\n", field_name_.ToCString());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (guarded_cid == kIllegalCid) {
|
|
// Guarded CID from feedback is not in current program: assume the field
|
|
// will become polymorphic.
|
|
field_.set_guarded_cid(kDynamicCid);
|
|
field_.set_is_nullable(true);
|
|
} else if ((field_.guarded_cid() != kIllegalCid) &&
|
|
(field_.guarded_cid() == guarded_cid)) {
|
|
// Guarded CID from feedback is different from initialized guarded CID
|
|
// in the current program: assume the field will become polymorphic.
|
|
field_.set_guarded_cid(kDynamicCid);
|
|
field_.set_is_nullable(true);
|
|
} else {
|
|
field_.set_guarded_cid(guarded_cid);
|
|
field_.set_is_nullable((is_nullable != 0) || field_.is_nullable());
|
|
}
|
|
|
|
// TODO(rmacnak): Merge other field type feedback.
|
|
field_.set_guarded_list_length(Field::kNoFixedLength);
|
|
field_.set_guarded_list_length_in_object_offset(
|
|
Field::kUnknownLengthOffset);
|
|
field_.set_static_type_exactness_state(
|
|
StaticTypeExactnessState::NotTracking());
|
|
field_.DeoptimizeDependentCode();
|
|
}
|
|
}
|
|
|
|
return Error::null();
|
|
}
|
|
|
|
ObjectPtr TypeFeedbackLoader::LoadFunction() {
|
|
bool skip = false;
|
|
|
|
cls_ = ReadClassByName();
|
|
if (!cls_.IsNull()) {
|
|
error_ = cls_.EnsureIsFinalized(thread_);
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
} else {
|
|
skip = true;
|
|
}
|
|
|
|
func_name_ = ReadString(); // Without private mangling.
|
|
UntaggedFunction::Kind kind = static_cast<UntaggedFunction::Kind>(ReadInt());
|
|
const TokenPosition& token_pos = TokenPosition::Deserialize(ReadInt());
|
|
intptr_t usage = ReadInt();
|
|
intptr_t inlining_depth = ReadInt();
|
|
intptr_t num_call_sites = ReadInt();
|
|
|
|
if (!skip) {
|
|
func_ = FindFunction(kind, token_pos);
|
|
if (func_.IsNull()) {
|
|
skip = true;
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Missing function %s %s\n", func_name_.ToCString(),
|
|
Function::KindToCString(kind));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!skip) {
|
|
error_ = Compiler::CompileFunction(thread_, func_);
|
|
if (error_.IsError()) {
|
|
return error_.ptr();
|
|
}
|
|
call_sites_ = func_.ic_data_array();
|
|
if (call_sites_.IsNull()) {
|
|
call_sites_ = Object::empty_array().ptr(); // Remove edge case.
|
|
}
|
|
if (call_sites_.Length() != num_call_sites + 1) {
|
|
skip = true;
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Mismatched call site count %s %" Pd " %" Pd "\n",
|
|
func_name_.ToCString(), call_sites_.Length(), num_call_sites);
|
|
}
|
|
}
|
|
}
|
|
|
|
// First element is edge counters.
|
|
for (intptr_t i = 1; i <= num_call_sites; i++) {
|
|
intptr_t deopt_id = ReadInt();
|
|
intptr_t rebind_rule = ReadInt();
|
|
target_name_ = ReadString();
|
|
intptr_t num_checked_arguments = ReadInt();
|
|
intptr_t num_entries = ReadInt();
|
|
|
|
if (!skip) {
|
|
call_site_ ^= call_sites_.At(i);
|
|
if ((call_site_.deopt_id() != deopt_id) ||
|
|
(call_site_.rebind_rule() != rebind_rule) ||
|
|
(call_site_.NumArgsTested() != num_checked_arguments)) {
|
|
skip = true;
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Mismatched call site %s\n", call_site_.ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (intptr_t entry_index = 0; entry_index < num_entries; entry_index++) {
|
|
intptr_t entry_usage = ReadInt();
|
|
bool skip_entry = skip;
|
|
GrowableArray<intptr_t> cids(num_checked_arguments);
|
|
|
|
for (intptr_t argument_index = 0; argument_index < num_checked_arguments;
|
|
argument_index++) {
|
|
intptr_t cid = cid_map_[ReadInt()];
|
|
cids.Add(cid);
|
|
if (cid == kIllegalCid) {
|
|
// Alternative: switch to a sentinel value such as kDynamicCid and
|
|
// have the optimizer generate a megamorphic call.
|
|
skip_entry = true;
|
|
}
|
|
}
|
|
|
|
if (skip_entry) {
|
|
continue;
|
|
}
|
|
|
|
intptr_t reuse_index = call_site_.FindCheck(cids);
|
|
if (reuse_index == -1) {
|
|
cls_ = thread_->isolate_group()->class_table()->At(cids[0]);
|
|
// Use target name and args descriptor from the current program
|
|
// instead of saved feedback to get the correct private mangling and
|
|
// ensure no arity mismatch crashes.
|
|
target_name_ = call_site_.target_name();
|
|
args_desc_ = call_site_.arguments_descriptor();
|
|
if (cls_.EnsureIsFinalized(thread_) == Error::null()) {
|
|
target_ = Resolver::ResolveDynamicForReceiverClass(
|
|
cls_, target_name_, ArgumentsDescriptor(args_desc_));
|
|
}
|
|
if (!target_.IsNull()) {
|
|
if (num_checked_arguments == 1) {
|
|
call_site_.AddReceiverCheck(cids[0], target_, entry_usage);
|
|
} else {
|
|
call_site_.AddCheck(cids, target_, entry_usage);
|
|
}
|
|
}
|
|
} else {
|
|
call_site_.IncrementCountAt(reuse_index, entry_usage);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!skip) {
|
|
func_.set_usage_counter(usage);
|
|
func_.set_inlining_depth(inlining_depth);
|
|
|
|
// Delay compilation until all feedback is loaded so feedback is available
|
|
// for inlined functions.
|
|
functions_to_compile_.Add(func_);
|
|
}
|
|
|
|
return Error::null();
|
|
}
|
|
|
|
FunctionPtr TypeFeedbackLoader::FindFunction(UntaggedFunction::Kind kind,
|
|
const TokenPosition& token_pos) {
|
|
if (cls_name_.Equals(Symbols::TopLevel())) {
|
|
func_ = lib_.LookupFunctionAllowPrivate(func_name_);
|
|
} else {
|
|
func_ = cls_.LookupFunctionAllowPrivate(func_name_);
|
|
}
|
|
|
|
if (!func_.IsNull()) {
|
|
// Found regular method.
|
|
} else if (kind == UntaggedFunction::kMethodExtractor) {
|
|
ASSERT(Field::IsGetterName(func_name_));
|
|
// Without private mangling:
|
|
String& name = String::Handle(zone_, Field::NameFromGetter(func_name_));
|
|
func_ = cls_.LookupFunctionAllowPrivate(name);
|
|
if (!func_.IsNull() && func_.IsDynamicFunction()) {
|
|
name = func_.name(); // With private mangling.
|
|
name = Field::GetterName(name);
|
|
func_ = func_.GetMethodExtractor(name);
|
|
} else {
|
|
func_ = Function::null();
|
|
}
|
|
} else if (kind == UntaggedFunction::kDynamicInvocationForwarder) {
|
|
// Without private mangling:
|
|
String& name = String::Handle(
|
|
zone_, Function::DemangleDynamicInvocationForwarderName(func_name_));
|
|
func_ = cls_.LookupFunctionAllowPrivate(name);
|
|
if (!func_.IsNull() && func_.IsDynamicFunction()) {
|
|
name = func_.name(); // With private mangling.
|
|
name = Function::CreateDynamicInvocationForwarderName(name);
|
|
SafepointWriteRwLocker ml(thread_,
|
|
thread_->isolate_group()->program_lock());
|
|
func_ = func_.CreateDynamicInvocationForwarder(name);
|
|
} else {
|
|
func_ = Function::null();
|
|
}
|
|
} else if (kind == UntaggedFunction::kClosureFunction) {
|
|
// Note this lookup relies on parent functions appearing before child
|
|
// functions in the serialized feedback, so the parent will have already
|
|
// been unoptimized compilated and the child function created and added to
|
|
// ObjectStore::closure_functions_.
|
|
func_ = ClosureFunctionsCache::LookupClosureFunction(cls_, token_pos);
|
|
} else {
|
|
// This leaves unhandled:
|
|
// - kInvokeFieldDispatcher (how to get a valid args descriptor?)
|
|
// - static field getters
|
|
// - static field initializers (not retained by the field object)
|
|
}
|
|
|
|
if (!func_.IsNull()) {
|
|
if (kind == UntaggedFunction::kImplicitClosureFunction) {
|
|
func_ = func_.ImplicitClosureFunction();
|
|
}
|
|
if (func_.is_abstract() || (func_.kind() != kind)) {
|
|
func_ = Function::null();
|
|
}
|
|
}
|
|
|
|
return func_.ptr();
|
|
}
|
|
|
|
ClassPtr TypeFeedbackLoader::ReadClassByName() {
|
|
uri_ = ReadString();
|
|
cls_name_ = ReadString();
|
|
|
|
lib_ = Library::LookupLibrary(thread_, uri_);
|
|
if (lib_.IsNull()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Missing library %s\n", uri_.ToCString());
|
|
}
|
|
return Class::null();
|
|
}
|
|
|
|
if (cls_name_.Equals(Symbols::TopLevel())) {
|
|
cls_ = lib_.toplevel_class();
|
|
} else {
|
|
cls_ = lib_.SlowLookupClassAllowMultiPartPrivate(cls_name_);
|
|
if (cls_.IsNull()) {
|
|
if (FLAG_trace_compilation_trace) {
|
|
THR_Print("Missing class %s %s\n", uri_.ToCString(),
|
|
cls_name_.ToCString());
|
|
}
|
|
}
|
|
}
|
|
return cls_.ptr();
|
|
}
|
|
|
|
StringPtr TypeFeedbackLoader::ReadString() {
|
|
intptr_t len = stream_->ReadUnsigned();
|
|
const char* cstr =
|
|
reinterpret_cast<const char*>(stream_->AddressOfCurrentPosition());
|
|
stream_->Advance(len);
|
|
return Symbols::New(thread_, cstr, len);
|
|
}
|
|
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
} // namespace dart
|