blob: 57239ece7363020cf573d4e60043423fc2a078a1 [file] [log] [blame]
// Copyright (c) 2015, 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/compiler/aot/precompiler.h"
#include "platform/unicode.h"
#include "platform/utils.h"
#include "vm/canonical_tables.h"
#include "vm/class_finalizer.h"
#include "vm/closure_functions_cache.h"
#include "vm/code_patcher.h"
#include "vm/compiler/aot/aot_call_specializer.h"
#include "vm/compiler/aot/precompiler_tracer.h"
#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/compiler/backend/branch_optimizer.h"
#include "vm/compiler/backend/constant_propagator.h"
#include "vm/compiler/backend/flow_graph.h"
#include "vm/compiler/backend/flow_graph_compiler.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/inliner.h"
#include "vm/compiler/backend/linearscan.h"
#include "vm/compiler/backend/range_analysis.h"
#include "vm/compiler/backend/redundancy_elimination.h"
#include "vm/compiler/backend/type_propagator.h"
#include "vm/compiler/cha.h"
#include "vm/compiler/compiler_pass.h"
#include "vm/compiler/compiler_state.h"
#include "vm/compiler/compiler_timings.h"
#include "vm/compiler/frontend/flow_graph_builder.h"
#include "vm/compiler/frontend/kernel_to_il.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/dart_entry.h"
#include "vm/exceptions.h"
#include "vm/flags.h"
#include "vm/hash_table.h"
#include "vm/isolate.h"
#include "vm/log.h"
#include "vm/longjump.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/os.h"
#include "vm/parser.h"
#include "vm/program_visitor.h"
#include "vm/regexp_assembler.h"
#include "vm/regexp_parser.h"
#include "vm/resolver.h"
#include "vm/runtime_entry.h"
#include "vm/symbols.h"
#include "vm/tags.h"
#include "vm/timeline.h"
#include "vm/timer.h"
#include "vm/type_testing_stubs.h"
#include "vm/version.h"
#include "vm/zone_text_buffer.h"
namespace dart {
#define T (thread())
#define IG (isolate_group())
#define Z (zone())
DEFINE_FLAG(bool,
print_precompiler_timings,
false,
"Print per-phase breakdown of time spent precompiling");
DEFINE_FLAG(bool, print_unique_targets, false, "Print unique dynamic targets");
DEFINE_FLAG(bool, print_gop, false, "Print global object pool");
DEFINE_FLAG(charp,
print_object_layout_to,
nullptr,
"Print layout of Dart objects to the given file");
DEFINE_FLAG(bool, trace_precompiler, false, "Trace precompiler.");
DEFINE_FLAG(
int,
max_speculative_inlining_attempts,
1,
"Max number of attempts with speculative inlining (precompilation only)");
DEFINE_FLAG(charp,
write_retained_reasons_to,
nullptr,
"Print reasons for retaining objects to the given file");
DECLARE_FLAG(bool, print_flow_graph);
DECLARE_FLAG(bool, print_flow_graph_optimized);
DECLARE_FLAG(bool, trace_compiler);
DECLARE_FLAG(bool, trace_optimizing_compiler);
DECLARE_FLAG(bool, trace_bailout);
DECLARE_FLAG(bool, trace_failed_optimization_attempts);
DECLARE_FLAG(bool, trace_inlining_intervals);
DECLARE_FLAG(int, inlining_hotness);
DECLARE_FLAG(int, inlining_size_threshold);
DECLARE_FLAG(int, inlining_callee_size_threshold);
DECLARE_FLAG(int, inline_getters_setters_smaller_than);
DECLARE_FLAG(int, inlining_depth_threshold);
DECLARE_FLAG(int, inlining_caller_size_threshold);
DECLARE_FLAG(int, inlining_constant_arguments_max_size_threshold);
DECLARE_FLAG(int, inlining_constant_arguments_min_size_threshold);
DECLARE_FLAG(bool, print_instruction_stats);
Precompiler* Precompiler::singleton_ = nullptr;
#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
// Reasons for retaining a given object.
struct RetainReasons : public AllStatic {
// The LLVM pools are active and the object appears in one of them.
static constexpr const char* kLLVMPool = "llvm pool";
// The object is an invoke field dispatcher.
static constexpr const char* kInvokeFieldDispatcher =
"invoke field dispatcher";
// The object is a dynamic invocation forwarder.
static constexpr const char* kDynamicInvocationForwarder =
"dynamic invocation forwarder";
// The object is a method extractor.
static constexpr const char* kMethodExtractor = "method extractor";
// The object is for a compiled implicit closure.
static constexpr const char* kImplicitClosure = "implicit closure";
// The object is a local closure.
static constexpr const char* kLocalClosure = "local closure";
// The object is a sync or async function or in the parent chain of one.
static constexpr const char* kIsSyncAsyncFunction = "sync or async function";
// The object is the initializer for a static field.
static constexpr const char* kStaticFieldInitializer =
"static field initializer";
// The object is the initializer for a instance field.
static constexpr const char* kInstanceFieldInitializer =
"instance field initializer";
// The object is the initializer for a late field.
static constexpr const char* kLateFieldInitializer = "late field initializer";
// The object is an implicit getter.
static constexpr const char* kImplicitGetter = "implicit getter";
// The object is an implicit setter.
static constexpr const char* kImplicitSetter = "implicit setter";
// The object is an implicit static getter.
static constexpr const char* kImplicitStaticGetter = "implicit static getter";
// The object is a function that is called through a getter method.
static constexpr const char* kCalledThroughGetter = "called through getter";
// The object is a function that is called via selector.
static constexpr const char* kCalledViaSelector = "called via selector";
// The object is a function and the flag --retain-function-objects is enabled.
static constexpr const char* kForcedRetain = "forced via flag";
// The object is a function and symbolic stack traces are enabled.
static constexpr const char* kSymbolicStackTraces =
"needed for symbolic stack traces";
// The object is a parent function function of a non-inlined local function.
static constexpr const char* kLocalParent = "parent of a local function";
// The object is a main function of the root library.
static constexpr const char* kMainFunction =
"this is main function of the root library";
// The object has an entry point pragma that requires it be retained.
static constexpr const char* kEntryPointPragma = "entry point pragma";
// The function is a target of FFI callback.
static constexpr const char* kFfiCallbackTarget = "ffi callback target";
// The signature is used in a closure function.
static constexpr const char* kClosureSignature = "closure signature";
// The signature is used in an FFI trampoline.
static constexpr const char* kFfiTrampolineSignature =
"FFI trampoline signature";
// The signature is used in a native function.
static constexpr const char* kNativeSignature = "native function signature";
// The signature has required named parameters.
static constexpr const char* kRequiredNamedParameters =
"signature has required named parameters";
// The signature is used in a function that has dynamic calls.
static constexpr const char* kDynamicallyCalledSignature =
"signature of dynamically called function";
// The signature is used in a function with an entry point pragma.
static constexpr const char* kEntryPointPragmaSignature =
"signature of entry point function";
};
class RetainedReasonsWriter : public ValueObject {
public:
explicit RetainedReasonsWriter(Zone* zone)
: zone_(zone), retained_reasons_map_(zone) {}
bool Init(const char* filename) {
if (filename == nullptr) return false;
if ((Dart::file_write_callback() == nullptr) ||
(Dart::file_open_callback() == nullptr) ||
(Dart::file_close_callback() == nullptr)) {
OS::PrintErr("warning: Could not access file callbacks.");
return false;
}
void* file = Dart::file_open_callback()(filename, /*write=*/true);
if (file == nullptr) {
OS::PrintErr("warning: Failed to write retained reasons: %s\n", filename);
return false;
}
file_ = file;
// We open the array here so that we can also print some objects to the
// JSON as we go, instead of requiring all information be collected
// and printed at one point. This avoids having to keep otherwise
// unneeded information around.
writer_.OpenArray();
return true;
}
void AddDropped(const Object& obj) {
if (HasReason(obj)) {
FATAL("dropped object has reasons to retain");
}
writer_.OpenObject();
WriteRetainedObjectSpecificFields(obj);
writer_.PrintPropertyBool("retained", false);
writer_.CloseObject();
}
bool HasReason(const Object& obj) const {
return retained_reasons_map_.HasKey(&obj);
}
void AddReason(const Object& obj, const char* reason) {
if (auto const kv = retained_reasons_map_.Lookup(&obj)) {
if (kv->value->Lookup(reason) == nullptr) {
kv->value->Insert(reason);
}
return;
}
auto const key = &Object::ZoneHandle(zone_, obj.ptr());
auto const value = new (zone_) ZoneCStringSet(zone_);
value->Insert(reason);
retained_reasons_map_.Insert(RetainedReasonsTrait::Pair(key, value));
}
// Finalizes the JSON output and writes it.
void Write() {
if (file_ == nullptr) return;
// Add all the objects for which we have reasons to retain.
auto it = retained_reasons_map_.GetIterator();
for (auto kv = it.Next(); kv != nullptr; kv = it.Next()) {
writer_.OpenObject();
WriteRetainedObjectSpecificFields(*kv->key);
writer_.PrintPropertyBool("retained", true);
writer_.OpenArray("reasons");
auto it = kv->value->GetIterator();
for (auto cstrp = it.Next(); cstrp != nullptr; cstrp = it.Next()) {
ASSERT(*cstrp != nullptr);
writer_.PrintValue(*cstrp);
}
writer_.CloseArray();
writer_.CloseObject();
}
writer_.CloseArray();
char* output = nullptr;
intptr_t length = -1;
writer_.Steal(&output, &length);
if (const auto file_write = Dart::file_write_callback()) {
file_write(output, length, file_);
}
if (const auto file_close = Dart::file_close_callback()) {
file_close(file_);
}
free(output);
}
private:
struct RetainedReasonsTrait {
using Key = const Object*;
using Value = ZoneCStringSet*;
struct Pair {
Key key;
Value value;
Pair() : key(nullptr), value(nullptr) {}
Pair(Key key, Value value) : key(key), value(value) {}
};
static Key KeyOf(Pair kv) { return kv.key; }
static Value ValueOf(Pair kv) { return kv.value; }
static inline uword Hash(Key key) {
if (key->IsFunction()) {
return Function::Cast(*key).Hash();
}
if (key->IsClass()) {
return Utils::WordHash(Class::Cast(*key).id());
}
if (key->IsAbstractType()) {
return AbstractType::Cast(*key).Hash();
}
return Utils::WordHash(key->GetClassId());
}
static inline bool IsKeyEqual(Pair pair, Key key) {
return pair.key->ptr() == key->ptr();
}
};
using RetainedReasonsMap = DirectChainedHashMap<RetainedReasonsTrait>;
void WriteRetainedObjectSpecificFields(const Object& obj) {
if (obj.IsFunction()) {
writer_.PrintProperty("type", "Function");
const auto& function = Function::Cast(obj);
writer_.PrintProperty("name",
function.ToLibNamePrefixedQualifiedCString());
writer_.PrintProperty("kind",
UntaggedFunction::KindToCString(function.kind()));
return;
} else if (obj.IsFunctionType()) {
writer_.PrintProperty("type", "FunctionType");
const auto& sig = FunctionType::Cast(obj);
writer_.PrintProperty("name", sig.ToCString());
return;
}
FATAL("Unexpected object %s", obj.ToCString());
}
Zone* const zone_;
RetainedReasonsMap retained_reasons_map_;
JSONWriter writer_;
void* file_;
};
class PrecompileParsedFunctionHelper : public ValueObject {
public:
PrecompileParsedFunctionHelper(Precompiler* precompiler,
ParsedFunction* parsed_function,
bool optimized)
: precompiler_(precompiler),
parsed_function_(parsed_function),
optimized_(optimized),
thread_(Thread::Current()) {}
bool Compile(CompilationPipeline* pipeline);
private:
ParsedFunction* parsed_function() const { return parsed_function_; }
bool optimized() const { return optimized_; }
Thread* thread() const { return thread_; }
Isolate* isolate() const { return thread_->isolate(); }
void FinalizeCompilation(compiler::Assembler* assembler,
FlowGraphCompiler* graph_compiler,
FlowGraph* flow_graph,
CodeStatistics* stats);
Precompiler* precompiler_;
ParsedFunction* parsed_function_;
const bool optimized_;
Thread* const thread_;
DISALLOW_COPY_AND_ASSIGN(PrecompileParsedFunctionHelper);
};
static void Jump(const Error& error) {
Thread::Current()->long_jump_base()->Jump(1, error);
}
ErrorPtr Precompiler::CompileAll() {
LongJumpScope jump;
if (setjmp(*jump.Set()) == 0) {
Precompiler precompiler(Thread::Current());
precompiler.DoCompileAll();
precompiler.ReportStats();
return Error::null();
} else {
return Thread::Current()->StealStickyError();
}
}
void Precompiler::ReportStats() {
if (!FLAG_print_precompiler_timings) {
return;
}
thread()->compiler_timings()->Print();
}
Precompiler::Precompiler(Thread* thread)
: thread_(thread),
zone_(NULL),
isolate_(thread->isolate()),
changed_(false),
retain_root_library_caches_(false),
function_count_(0),
class_count_(0),
selector_count_(0),
dropped_function_count_(0),
dropped_field_count_(0),
dropped_class_count_(0),
dropped_typearg_count_(0),
dropped_type_count_(0),
dropped_functiontype_count_(0),
dropped_typeparam_count_(0),
dropped_library_count_(0),
libraries_(GrowableObjectArray::Handle(
isolate_->group()->object_store()->libraries())),
pending_functions_(
GrowableObjectArray::Handle(GrowableObjectArray::New())),
sent_selectors_(),
functions_called_dynamically_(
HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
functions_with_entry_point_pragmas_(
HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
seen_functions_(HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
possibly_retained_functions_(
HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
fields_to_retain_(),
functions_to_retain_(
HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
classes_to_retain_(),
typeargs_to_retain_(),
types_to_retain_(),
functiontypes_to_retain_(),
typeparams_to_retain_(),
consts_to_retain_(),
seen_table_selectors_(),
error_(Error::Handle()),
get_runtime_type_is_unique_(false) {
ASSERT(Precompiler::singleton_ == NULL);
Precompiler::singleton_ = this;
if (FLAG_print_precompiler_timings) {
thread->set_compiler_timings(new CompilerTimings());
}
}
Precompiler::~Precompiler() {
// We have to call Release() in DEBUG mode.
functions_called_dynamically_.Release();
functions_with_entry_point_pragmas_.Release();
seen_functions_.Release();
possibly_retained_functions_.Release();
functions_to_retain_.Release();
ASSERT(Precompiler::singleton_ == this);
Precompiler::singleton_ = NULL;
delete thread()->compiler_timings();
thread()->set_compiler_timings(nullptr);
}
void Precompiler::DoCompileAll() {
PRECOMPILER_TIMER_SCOPE(this, CompileAll);
{
StackZone stack_zone(T);
zone_ = stack_zone.GetZone();
RetainedReasonsWriter reasons_writer(zone_);
if (reasons_writer.Init(FLAG_write_retained_reasons_to)) {
retained_reasons_writer_ = &reasons_writer;
}
// Since we keep the object pool until the end of AOT compilation, it
// will hang on to its entries until the very end. Therefore we have
// to use handles which survive that long, so we use [zone_] here.
global_object_pool_builder_.InitializeWithZone(zone_);
{
HANDLESCOPE(T);
// Make sure class hierarchy is stable before compilation so that CHA
// can be used. Also ensures lookup of entry points won't miss functions
// because their class hasn't been finalized yet.
FinalizeAllClasses();
ASSERT(Error::Handle(Z, T->sticky_error()).IsNull());
if (FLAG_print_object_layout_to != nullptr) {
IG->class_table()->PrintObjectLayout(FLAG_print_object_layout_to);
}
ClassFinalizer::SortClasses();
// Collects type usage information which allows us to decide when/how to
// optimize runtime type tests.
TypeUsageInfo type_usage_info(T);
// The cid-ranges of subclasses of a class are e.g. used for is/as checks
// as well as other type checks.
HierarchyInfo hierarchy_info(T);
dispatch_table_generator_ = new compiler::DispatchTableGenerator(Z);
dispatch_table_generator_->Initialize(IG->class_table());
// Precompile constructors to compute information such as
// optimized instruction count (used in inlining heuristics).
ClassFinalizer::ClearAllCode(
/*including_nonchanging_cids=*/true);
{
CompilerState state(thread_, /*is_aot=*/true, /*is_optimizing=*/true);
PrecompileConstructors();
}
ClassFinalizer::ClearAllCode(
/*including_nonchanging_cids=*/true);
tracer_ = PrecompilerTracer::StartTracingIfRequested(this);
// All stubs have already been generated, all of them share the same pool.
// We use that pool to initialize our global object pool, to guarantee
// stubs as well as code compiled from here on will have the same pool.
{
// We use any stub here to get it's object pool (all stubs share the
// same object pool in bare instructions mode).
const Code& code = StubCode::LazyCompile();
const ObjectPool& stub_pool = ObjectPool::Handle(code.object_pool());
global_object_pool_builder()->Reset();
stub_pool.CopyInto(global_object_pool_builder());
// We have various stubs we would like to generate inside the isolate,
// to ensure the rest of the AOT compilation will use the
// isolate-specific stubs (callable via pc-relative calls).
auto& stub_code = Code::Handle();
#define DO(member, name) \
stub_code = StubCode::BuildIsolateSpecific##name##Stub( \
global_object_pool_builder()); \
IG->object_store()->set_##member(stub_code);
OBJECT_STORE_STUB_CODE_LIST(DO)
#undef DO
{
SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
stub_code = StubCode::GetBuildGenericMethodExtractorStub(
global_object_pool_builder());
}
IG->object_store()->set_build_generic_method_extractor_code(stub_code);
{
SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
stub_code = StubCode::GetBuildNonGenericMethodExtractorStub(
global_object_pool_builder());
}
IG->object_store()->set_build_nongeneric_method_extractor_code(
stub_code);
}
CollectDynamicFunctionNames();
// Start with the allocations and invocations that happen from C++.
{
TracingScope scope(this);
AddRoots();
AddAnnotatedRoots();
}
// With the nnbd experiment enabled, these non-nullable type arguments may
// not be retained, although they will be used and expected to be
// canonical by Dart_NewListOfType.
AddTypeArguments(
TypeArguments::Handle(Z, IG->object_store()->type_argument_int()));
AddTypeArguments(
TypeArguments::Handle(Z, IG->object_store()->type_argument_double()));
AddTypeArguments(
TypeArguments::Handle(Z, IG->object_store()->type_argument_string()));
AddTypeArguments(TypeArguments::Handle(
Z, IG->object_store()->type_argument_string_dynamic()));
AddTypeArguments(TypeArguments::Handle(
Z, IG->object_store()->type_argument_string_string()));
// Compile newly found targets and add their callees until we reach a
// fixed point.
Iterate();
// Replace the default type testing stubs installed on [Type]s with new
// [Type]-specialized stubs.
AttachOptimizedTypeTestingStub();
{
// Now we generate the actual object pool instance and attach it to the
// object store. The AOT runtime will use it from there in the enter
// dart code stub.
const auto& pool = ObjectPool::Handle(
ObjectPool::NewFromBuilder(*global_object_pool_builder()));
IG->object_store()->set_global_object_pool(pool);
global_object_pool_builder()->Reset();
if (FLAG_print_gop) {
THR_Print("Global object pool:\n");
pool.DebugPrint();
}
}
if (tracer_ != nullptr) {
tracer_->Finalize();
tracer_ = nullptr;
}
{
PRECOMPILER_TIMER_SCOPE(this, TraceForRetainedFunctions);
TraceForRetainedFunctions();
}
FinalizeDispatchTable();
ReplaceFunctionStaticCallEntries();
{
PRECOMPILER_TIMER_SCOPE(this, Drop);
DropFunctions();
DropFields();
TraceTypesFromRetainedClasses();
// Clear these before dropping classes as they may hold onto otherwise
// dead instances of classes we will remove or otherwise unused symbols.
IG->object_store()->set_unique_dynamic_targets(Array::null_array());
Class& null_class = Class::Handle(Z);
Function& null_function = Function::Handle(Z);
Field& null_field = Field::Handle(Z);
IG->object_store()->set_pragma_class(null_class);
IG->object_store()->set_pragma_name(null_field);
IG->object_store()->set_pragma_options(null_field);
IG->object_store()->set_completer_class(null_class);
IG->object_store()->set_compiletime_error_class(null_class);
IG->object_store()->set_growable_list_factory(null_function);
IG->object_store()->set_simple_instance_of_function(null_function);
IG->object_store()->set_simple_instance_of_true_function(null_function);
IG->object_store()->set_simple_instance_of_false_function(
null_function);
IG->object_store()->set_async_star_move_next_helper(null_function);
IG->object_store()->set_complete_on_async_return(null_function);
IG->object_store()->set_async_star_stream_controller(null_class);
DropMetadata();
DropLibraryEntries();
}
}
{
PRECOMPILER_TIMER_SCOPE(this, Drop);
DropClasses();
DropLibraries();
}
{
PRECOMPILER_TIMER_SCOPE(this, Obfuscate);
Obfuscate();
}
#if defined(DEBUG)
const auto& non_visited =
Function::Handle(Z, FindUnvisitedRetainedFunction());
if (!non_visited.IsNull()) {
FATAL1("Code visitor would miss the code for function \"%s\"\n",
non_visited.ToFullyQualifiedCString());
}
#endif
DiscardCodeObjects();
{
PRECOMPILER_TIMER_SCOPE(this, Dedup);
ProgramVisitor::Dedup(T);
}
if (retained_reasons_writer_ != nullptr) {
reasons_writer.Write();
retained_reasons_writer_ = nullptr;
}
zone_ = NULL;
}
intptr_t symbols_before = -1;
intptr_t symbols_after = -1;
intptr_t capacity = -1;
if (FLAG_trace_precompiler) {
Symbols::GetStats(IG, &symbols_before, &capacity);
}
if (FLAG_trace_precompiler) {
Symbols::GetStats(IG, &symbols_after, &capacity);
THR_Print("Precompiled %" Pd " functions,", function_count_);
THR_Print(" %" Pd " dynamic types,", class_count_);
THR_Print(" %" Pd " dynamic selectors.\n", selector_count_);
THR_Print("Dropped %" Pd " functions,", dropped_function_count_);
THR_Print(" %" Pd " fields,", dropped_field_count_);
THR_Print(" %" Pd " symbols,", symbols_before - symbols_after);
THR_Print(" %" Pd " types,", dropped_type_count_);
THR_Print(" %" Pd " function types,", dropped_functiontype_count_);
THR_Print(" %" Pd " type parameters,", dropped_typeparam_count_);
THR_Print(" %" Pd " type arguments,", dropped_typearg_count_);
THR_Print(" %" Pd " classes,", dropped_class_count_);
THR_Print(" %" Pd " libraries.\n", dropped_library_count_);
}
}
void Precompiler::PrecompileConstructors() {
PRECOMPILER_TIMER_SCOPE(this, PrecompileConstructors);
class ConstructorVisitor : public FunctionVisitor {
public:
explicit ConstructorVisitor(Precompiler* precompiler, Zone* zone)
: precompiler_(precompiler), zone_(zone) {}
void VisitFunction(const Function& function) {
if (!function.IsGenerativeConstructor()) return;
if (function.HasCode()) {
// Const constructors may have been visited before. Recompile them here
// to collect type information for final fields for them as well.
function.ClearCode();
}
if (FLAG_trace_precompiler) {
THR_Print("Precompiling constructor %s\n", function.ToCString());
}
ASSERT(Class::Handle(zone_, function.Owner()).is_finalized());
CompileFunction(precompiler_, Thread::Current(), zone_, function);
}
private:
Precompiler* precompiler_;
Zone* zone_;
};
phase_ = Phase::kCompilingConstructorsForInstructionCounts;
HANDLESCOPE(T);
ConstructorVisitor visitor(this, Z);
ProgramVisitor::WalkProgram(Z, IG, &visitor);
phase_ = Phase::kPreparation;
}
void Precompiler::AddRoots() {
HANDLESCOPE(T);
AddSelector(Symbols::NoSuchMethod());
AddSelector(Symbols::Call()); // For speed, not correctness.
// Add main as an entry point.
const Library& lib = Library::Handle(IG->object_store()->root_library());
if (lib.IsNull()) {
const String& msg = String::Handle(
Z, String::New("Cannot find root library in isolate.\n"));
Jump(Error::Handle(Z, ApiError::New(msg)));
UNREACHABLE();
}
const String& name = String::Handle(String::New("main"));
Function& main = Function::Handle(lib.LookupFunctionAllowPrivate(name));
if (main.IsNull()) {
const Object& obj = Object::Handle(lib.LookupReExport(name));
if (obj.IsFunction()) {
main ^= obj.ptr();
}
}
if (!main.IsNull()) {
if (lib.LookupLocalFunction(name) == Function::null()) {
retain_root_library_caches_ = true;
}
AddRetainReason(main, RetainReasons::kMainFunction);
AddTypesOf(main);
// Create closure object from main.
main = main.ImplicitClosureFunction();
AddConstObject(Closure::Handle(main.ImplicitStaticClosure()));
} else {
String& msg = String::Handle(
Z, String::NewFormatted("Cannot find main in library %s\n",
lib.ToCString()));
Jump(Error::Handle(Z, ApiError::New(msg)));
UNREACHABLE();
}
}
void Precompiler::Iterate() {
PRECOMPILER_TIMER_SCOPE(this, Iterate);
Function& function = Function::Handle(Z);
phase_ = Phase::kFixpointCodeGeneration;
while (changed_) {
changed_ = false;
while (pending_functions_.Length() > 0) {
function ^= pending_functions_.RemoveLast();
ProcessFunction(function);
}
CheckForNewDynamicFunctions();
CollectCallbackFields();
}
phase_ = Phase::kDone;
}
void Precompiler::CollectCallbackFields() {
PRECOMPILER_TIMER_SCOPE(this, CollectCallbackFields);
HANDLESCOPE(T);
Library& lib = Library::Handle(Z);
Class& cls = Class::Handle(Z);
Class& subcls = Class::Handle(Z);
Array& fields = Array::Handle(Z);
Field& field = Field::Handle(Z);
FunctionType& signature = FunctionType::Handle(Z);
Function& dispatcher = Function::Handle(Z);
Array& args_desc = Array::Handle(Z);
AbstractType& field_type = AbstractType::Handle(Z);
String& field_name = String::Handle(Z);
GrowableArray<intptr_t> cids;
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
if (!cls.is_allocated()) continue;
fields = cls.fields();
for (intptr_t k = 0; k < fields.Length(); k++) {
field ^= fields.At(k);
if (field.is_static()) continue;
field_type = field.type();
if (!field_type.IsFunctionType()) continue;
field_name = field.name();
if (!IsSent(field_name)) continue;
// Create arguments descriptor with fixed parameters from
// signature of field_type.
signature ^= field_type.ptr();
if (signature.IsGeneric()) continue;
if (signature.HasOptionalParameters()) continue;
if (FLAG_trace_precompiler) {
THR_Print("Found callback field %s\n", field_name.ToCString());
}
// TODO(dartbug.com/33549): Update this code to use the size of the
// parameters when supporting calls to non-static methods with
// unboxed parameters.
args_desc =
ArgumentsDescriptor::NewBoxed(0, // No type argument vector.
signature.num_fixed_parameters());
cids.Clear();
if (CHA::ConcreteSubclasses(cls, &cids)) {
for (intptr_t j = 0; j < cids.length(); ++j) {
subcls = IG->class_table()->At(cids[j]);
if (subcls.is_allocated()) {
// Add dispatcher to cls.
dispatcher = subcls.GetInvocationDispatcher(
field_name, args_desc,
UntaggedFunction::kInvokeFieldDispatcher,
/* create_if_absent = */ true);
if (FLAG_trace_precompiler) {
THR_Print("Added invoke-field-dispatcher for %s to %s\n",
field_name.ToCString(), subcls.ToCString());
}
AddFunction(dispatcher, RetainReasons::kInvokeFieldDispatcher);
}
}
}
}
}
}
}
void Precompiler::ProcessFunction(const Function& function) {
HANDLESCOPE(T);
const intptr_t gop_offset = global_object_pool_builder()->CurrentLength();
RELEASE_ASSERT(!function.HasCode());
// Ffi trampoline functions have no signature.
ASSERT(function.kind() == UntaggedFunction::kFfiTrampoline ||
FunctionType::Handle(Z, function.signature()).IsFinalized());
TracingScope tracing_scope(this);
function_count_++;
if (FLAG_trace_precompiler) {
THR_Print("Precompiling %" Pd " %s (%s, %s)\n", function_count_,
function.ToLibNamePrefixedQualifiedCString(),
function.token_pos().ToCString(),
Function::KindToCString(function.kind()));
}
if (function.SourceSize() >= FLAG_huge_method_cutoff_in_tokens) {
THR_Print(
"Warning: %s from %s is too large. Some optimizations have been "
"disabled, and the compiler might run out of memory. "
"Consider refactoring this code into smaller components.\n",
function.QualifiedUserVisibleNameCString(),
String::Handle(
Z, Library::Handle(Z, Class::Handle(Z, function.Owner()).library())
.url())
.ToCString());
}
ASSERT(!function.is_abstract());
error_ = CompileFunction(this, thread_, zone_, function);
if (!error_.IsNull()) {
Jump(error_);
}
// Used in the JIT to save type-feedback across compilations.
function.ClearICDataArray();
AddCalleesOf(function, gop_offset);
}
void Precompiler::AddCalleesOf(const Function& function, intptr_t gop_offset) {
PRECOMPILER_TIMER_SCOPE(this, AddCalleesOf);
ASSERT(function.HasCode());
const Code& code = Code::Handle(Z, function.CurrentCode());
Object& entry = Object::Handle(Z);
Class& cls = Class::Handle(Z);
Function& target = Function::Handle(Z);
const Array& table = Array::Handle(Z, code.static_calls_target_table());
StaticCallsTable static_calls(table);
for (auto& view : static_calls) {
entry = view.Get<Code::kSCallTableFunctionTarget>();
if (entry.IsFunction()) {
// Since generally function objects are retained when symbolic stack
// traces are enabled, only return kForcedRetain to mark that retention
// was otherwise forced.
const char* const reason =
FLAG_retain_function_objects
? (!FLAG_dwarf_stack_traces_mode
? RetainReasons::kSymbolicStackTraces
: RetainReasons::kForcedRetain)
: nullptr;
AddFunction(Function::Cast(entry), reason);
ASSERT(view.Get<Code::kSCallTableCodeOrTypeTarget>() == Code::null());
continue;
}
entry = view.Get<Code::kSCallTableCodeOrTypeTarget>();
if (entry.IsCode() && Code::Cast(entry).IsAllocationStubCode()) {
cls ^= Code::Cast(entry).owner();
AddInstantiatedClass(cls);
}
}
const ExceptionHandlers& handlers =
ExceptionHandlers::Handle(Z, code.exception_handlers());
if (!handlers.IsNull()) {
#if defined(PRODUCT)
// List of handled types is only used by debugger and
// can be removed in PRODUCT mode.
for (intptr_t i = 0; i < handlers.num_entries(); i++) {
handlers.SetHandledTypes(i, Array::empty_array());
}
#else
Array& types = Array::Handle(Z);
AbstractType& type = AbstractType::Handle(Z);
for (intptr_t i = 0; i < handlers.num_entries(); i++) {
types = handlers.GetHandledTypes(i);
for (intptr_t j = 0; j < types.Length(); j++) {
type ^= types.At(j);
AddType(type);
}
}
#endif // defined(PRODUCT)
}
#if defined(TARGET_ARCH_IA32)
FATAL("Callee scanning unimplemented for IA32");
#endif
String& selector = String::Handle(Z);
// When tracing we want to scan the object pool attached to the code object
// rather than scanning global object pool - because we want to include
// *all* outgoing references into the trace. Scanning GOP would exclude
// references that have been deduplicated.
if (!is_tracing()) {
for (intptr_t i = gop_offset;
i < global_object_pool_builder()->CurrentLength(); i++) {
const auto& wrapper_entry = global_object_pool_builder()->EntryAt(i);
if (wrapper_entry.type() ==
compiler::ObjectPoolBuilderEntry::kTaggedObject) {
const auto& entry = *wrapper_entry.obj_;
AddCalleesOfHelper(entry, &selector, &cls);
}
}
} else {
const auto& pool = ObjectPool::Handle(Z, code.object_pool());
auto& entry = Object::Handle(Z);
for (intptr_t i = 0; i < pool.Length(); i++) {
if (pool.TypeAt(i) == ObjectPool::EntryType::kTaggedObject) {
entry = pool.ObjectAt(i);
AddCalleesOfHelper(entry, &selector, &cls);
}
}
}
if (!FLAG_dwarf_stack_traces_mode) {
const Array& inlined_functions =
Array::Handle(Z, code.inlined_id_to_function());
for (intptr_t i = 0; i < inlined_functions.Length(); i++) {
target ^= inlined_functions.At(i);
AddRetainReason(target, RetainReasons::kSymbolicStackTraces);
AddTypesOf(target);
}
}
}
static bool IsPotentialClosureCall(const String& selector) {
return selector.ptr() == Symbols::Call().ptr() ||
selector.ptr() == Symbols::DynamicCall().ptr();
}
void Precompiler::AddCalleesOfHelper(const Object& entry,
String* temp_selector,
Class* temp_cls) {
switch (entry.GetClassId()) {
case kOneByteStringCid:
case kNullCid:
// Skip common leaf constants early in order to
// process object pools faster.
return;
case kUnlinkedCallCid: {
const auto& call_site = UnlinkedCall::Cast(entry);
// A dynamic call.
*temp_selector = call_site.target_name();
AddSelector(*temp_selector);
if (IsPotentialClosureCall(*temp_selector)) {
const Array& arguments_descriptor =
Array::Handle(Z, call_site.arguments_descriptor());
AddClosureCall(*temp_selector, arguments_descriptor);
}
break;
}
case kMegamorphicCacheCid: {
// A dynamic call.
const auto& cache = MegamorphicCache::Cast(entry);
*temp_selector = cache.target_name();
AddSelector(*temp_selector);
if (IsPotentialClosureCall(*temp_selector)) {
const Array& arguments_descriptor =
Array::Handle(Z, cache.arguments_descriptor());
AddClosureCall(*temp_selector, arguments_descriptor);
}
break;
}
case kFieldCid: {
// Potential need for field initializer.
const auto& field = Field::Cast(entry);
AddField(field);
break;
}
case kFunctionCid: {
// Local closure function.
const auto& target = Function::Cast(entry);
AddFunction(target, RetainReasons::kLocalClosure);
if (target.IsFfiTrampoline()) {
const auto& callback_target =
Function::Handle(Z, target.FfiCallbackTarget());
if (!callback_target.IsNull()) {
AddFunction(callback_target, RetainReasons::kFfiCallbackTarget);
}
}
break;
}
case kCodeCid: {
const auto& target_code = Code::Cast(entry);
if (target_code.IsAllocationStubCode()) {
*temp_cls ^= target_code.owner();
AddInstantiatedClass(*temp_cls);
}
break;
}
default:
if (entry.IsInstance()) {
// Const object, literal or args descriptor.
const auto& instance = Instance::Cast(entry);
AddConstObject(instance);
}
break;
}
}
void Precompiler::AddTypesOf(const Class& cls) {
if (cls.IsNull()) return;
if (classes_to_retain_.HasKey(&cls)) return;
classes_to_retain_.Insert(&Class::ZoneHandle(Z, cls.ptr()));
Array& interfaces = Array::Handle(Z, cls.interfaces());
AbstractType& type = AbstractType::Handle(Z);
for (intptr_t i = 0; i < interfaces.Length(); i++) {
type ^= interfaces.At(i);
AddType(type);
}
AddTypeParameters(TypeParameters::Handle(Z, cls.type_parameters()));
type = cls.super_type();
AddType(type);
}
void Precompiler::AddRetainReason(const Object& obj, const char* reason) {
if (retained_reasons_writer_ == nullptr || reason == nullptr) return;
retained_reasons_writer_->AddReason(obj, reason);
}
void Precompiler::AddTypesOf(const Function& function) {
if (function.IsNull()) return;
if (functions_to_retain_.ContainsKey(function)) return;
functions_to_retain_.Insert(function);
if (retained_reasons_writer_ != nullptr &&
!retained_reasons_writer_->HasReason(function)) {
FATAL("no retaining reasons given");
}
if (function.NeedsMonomorphicCheckedEntry(Z) ||
Function::IsDynamicInvocationForwarderName(function.name())) {
functions_called_dynamically_.Insert(function);
}
const FunctionType& signature = FunctionType::Handle(Z, function.signature());
AddType(signature);
// A class may have all functions inlined except a local function.
const Class& owner = Class::Handle(Z, function.Owner());
AddTypesOf(owner);
const auto& parent_function = Function::Handle(Z, function.parent_function());
if (parent_function.IsNull()) {
return;
}
// It can happen that all uses of a function are inlined, leaving
// a compiled local function with an uncompiled parent. Retain such
// parents and their enclosing classes and libraries when needed.
// We always retain parents if symbolic stack traces are enabled.
if (!FLAG_dwarf_stack_traces_mode) {
AddRetainReason(parent_function, RetainReasons::kSymbolicStackTraces);
AddTypesOf(parent_function);
return;
}
// Special case to allow walking of lazy async stacks to work.
// Should match parent checks in CallerClosureFinder::FindCaller.
if (parent_function.recognized_kind() == MethodRecognizer::kFutureTimeout ||
parent_function.recognized_kind() == MethodRecognizer::kFutureWait) {
AddRetainReason(parent_function, RetainReasons::kIsSyncAsyncFunction);
AddTypesOf(parent_function);
return;
}
// Preserve parents for generated bodies in async/async*/sync* functions,
// since predicates like Function::IsAsyncClosure(), etc. need that info.
if (function.is_generated_body()) {
AddRetainReason(parent_function, RetainReasons::kIsSyncAsyncFunction);
AddTypesOf(parent_function);
return;
}
// We're not retaining the parent due to this function, so wrap it with
// a weak serialization reference.
const auto& data = ClosureData::CheckedHandle(Z, function.data());
const auto& wsr =
Object::Handle(Z, WeakSerializationReference::New(
parent_function, Object::null_function()));
data.set_parent_function(wsr);
}
void Precompiler::AddType(const AbstractType& abstype) {
if (abstype.IsNull()) return;
if (abstype.IsTypeParameter()) {
const auto& param = TypeParameter::Cast(abstype);
if (typeparams_to_retain_.HasKey(&param)) return;
typeparams_to_retain_.Insert(&TypeParameter::ZoneHandle(Z, param.ptr()));
auto& bound = AbstractType::Handle(Z, param.bound());
AddType(bound);
return;
}
if (abstype.IsFunctionType()) {
if (functiontypes_to_retain_.HasKey(&FunctionType::Cast(abstype))) return;
const FunctionType& signature =
FunctionType::ZoneHandle(Z, FunctionType::Cast(abstype).ptr());
functiontypes_to_retain_.Insert(&signature);
AddTypeParameters(TypeParameters::Handle(Z, signature.type_parameters()));
AbstractType& type = AbstractType::Handle(Z);
type = signature.result_type();
AddType(type);
for (intptr_t i = 0; i < signature.NumParameters(); i++) {
type = signature.ParameterTypeAt(i);
AddType(type);
}
return;
}
if (types_to_retain_.HasKey(&abstype)) return;
types_to_retain_.Insert(&AbstractType::ZoneHandle(Z, abstype.ptr()));
if (abstype.IsType()) {
const Type& type = Type::Cast(abstype);
const Class& cls = Class::Handle(Z, type.type_class());
AddTypesOf(cls);
const TypeArguments& vector = TypeArguments::Handle(Z, abstype.arguments());
AddTypeArguments(vector);
} else if (abstype.IsTypeRef()) {
AbstractType& type = AbstractType::Handle(Z);
type = TypeRef::Cast(abstype).type();
AddType(type);
}
}
void Precompiler::AddTypeParameters(const TypeParameters& params) {
if (params.IsNull()) return;
TypeArguments& args = TypeArguments::Handle();
args = params.bounds();
AddTypeArguments(args);
args = params.defaults();
AddTypeArguments(args);
}
void Precompiler::AddTypeArguments(const TypeArguments& args) {
if (args.IsNull()) return;
if (typeargs_to_retain_.HasKey(&args)) return;
typeargs_to_retain_.Insert(&TypeArguments::ZoneHandle(Z, args.ptr()));
AbstractType& arg = AbstractType::Handle(Z);
for (intptr_t i = 0; i < args.Length(); i++) {
arg = args.TypeAt(i);
AddType(arg);
}
}
void Precompiler::AddConstObject(const class Instance& instance) {
// Types, type parameters, and type arguments require special handling.
if (instance.IsAbstractType()) { // Includes type parameter.
AddType(AbstractType::Cast(instance));
return;
} else if (instance.IsTypeArguments()) {
AddTypeArguments(TypeArguments::Cast(instance));
return;
}
if (instance.ptr() == Object::sentinel().ptr() ||
instance.ptr() == Object::transition_sentinel().ptr()) {
return;
}
Class& cls = Class::Handle(Z, instance.clazz());
AddInstantiatedClass(cls);
if (instance.IsClosure()) {
// An implicit static closure.
const Function& func =
Function::Handle(Z, Closure::Cast(instance).function());
ASSERT(func.is_static());
AddFunction(func, RetainReasons::kImplicitClosure);
AddTypeArguments(TypeArguments::Handle(
Z, Closure::Cast(instance).instantiator_type_arguments()));
AddTypeArguments(TypeArguments::Handle(
Z, Closure::Cast(instance).function_type_arguments()));
AddTypeArguments(TypeArguments::Handle(
Z, Closure::Cast(instance).delayed_type_arguments()));
return;
}
if (instance.IsLibraryPrefix()) {
const LibraryPrefix& prefix = LibraryPrefix::Cast(instance);
ASSERT(prefix.is_deferred_load());
const Library& target = Library::Handle(Z, prefix.GetLibrary(0));
cls = target.toplevel_class();
if (!classes_to_retain_.HasKey(&cls)) {
classes_to_retain_.Insert(&Class::ZoneHandle(Z, cls.ptr()));
}
return;
}
// Can't ask immediate objects if they're canonical.
if (instance.IsSmi()) return;
// Some Instances in the ObjectPool aren't const objects, such as
// argument descriptors.
if (!instance.IsCanonical()) return;
// Constants are canonicalized and we avoid repeated processing of them.
if (consts_to_retain_.HasKey(&instance)) return;
consts_to_retain_.Insert(&Instance::ZoneHandle(Z, instance.ptr()));
if (cls.NumTypeArguments() > 0) {
AddTypeArguments(TypeArguments::Handle(Z, instance.GetTypeArguments()));
}
class ConstObjectVisitor : public ObjectPointerVisitor {
public:
ConstObjectVisitor(Precompiler* precompiler, IsolateGroup* isolate_group)
: ObjectPointerVisitor(isolate_group),
precompiler_(precompiler),
subinstance_(Object::Handle()) {}
void VisitPointers(ObjectPtr* first, ObjectPtr* last) {
for (ObjectPtr* current = first; current <= last; current++) {
subinstance_ = *current;
if (subinstance_.IsInstance()) {
precompiler_->AddConstObject(Instance::Cast(subinstance_));
}
}
subinstance_ = Object::null();
}
void VisitCompressedPointers(uword heap_base,
CompressedObjectPtr* first,
CompressedObjectPtr* last) {
for (CompressedObjectPtr* current = first; current <= last; current++) {
subinstance_ = current->Decompress(heap_base);
if (subinstance_.IsInstance()) {
precompiler_->AddConstObject(Instance::Cast(subinstance_));
}
}
subinstance_ = Object::null();
}
private:
Precompiler* precompiler_;
Object& subinstance_;
};
ConstObjectVisitor visitor(this, IG);
instance.ptr()->untag()->VisitPointers(&visitor);
}
void Precompiler::AddClosureCall(const String& call_selector,
const Array& arguments_descriptor) {
const Class& cache_class =
Class::Handle(Z, IG->object_store()->closure_class());
const Function& dispatcher =
Function::Handle(Z, cache_class.GetInvocationDispatcher(
call_selector, arguments_descriptor,
UntaggedFunction::kInvokeFieldDispatcher,
true /* create_if_absent */));
AddFunction(dispatcher, RetainReasons::kInvokeFieldDispatcher);
}
void Precompiler::AddField(const Field& field) {
if (is_tracing()) {
tracer_->WriteFieldRef(field);
}
if (fields_to_retain_.HasKey(&field)) return;
fields_to_retain_.Insert(&Field::ZoneHandle(Z, field.ptr()));
if (field.is_static()) {
const Object& value =
Object::Handle(Z, IG->initial_field_table()->At(field.field_id()));
// Should not be in the middle of initialization while precompiling.
ASSERT(value.ptr() != Object::transition_sentinel().ptr());
if (value.ptr() != Object::sentinel().ptr() &&
value.ptr() != Object::null()) {
ASSERT(value.IsInstance());
AddConstObject(Instance::Cast(value));
}
}
if (field.has_nontrivial_initializer() &&
(field.is_static() || field.is_late())) {
const Function& initializer =
Function::ZoneHandle(Z, field.EnsureInitializerFunction());
const char* const reason = field.is_static()
? RetainReasons::kStaticFieldInitializer
: RetainReasons::kLateFieldInitializer;
AddFunction(initializer, reason);
}
}
const char* Precompiler::MustRetainFunction(const Function& function) {
// There are some cases where we must retain, even if there are no directly
// observable need for function objects at runtime. Here, we check for cases
// where the function is not marked with the vm:entry-point pragma, which also
// forces retention:
//
// * Native functions (for LinkNativeCall)
// * Selector matches a symbol used in Resolver::ResolveDynamic calls
// in dart_entry.cc or dart_api_impl.cc.
// * _Closure.call (used in async stack handling)
if (function.is_native()) {
return "native function";
}
// Use the same check for _Closure.call as in stack_trace.{h|cc}.
const auto& selector = String::Handle(Z, function.name());
if (selector.ptr() == Symbols::Call().ptr()) {
const auto& name = String::Handle(Z, function.QualifiedScrubbedName());
if (name.Equals(Symbols::_ClosureCall())) {
return "_Closure.call";
}
}
// We have to retain functions which can be a target of a SwitchableCall
// at AOT runtime, since the AOT runtime needs to be able to find the
// function object in the class.
if (function.NeedsMonomorphicCheckedEntry(Z)) {
return "needs monomorphic checked entry";
}
if (Function::IsDynamicInvocationForwarderName(function.name())) {
return "dynamic invocation forwarder";
}
return nullptr;
}
void Precompiler::AddFunction(const Function& function,
const char* retain_reason) {
if (is_tracing()) {
tracer_->WriteFunctionRef(function);
}
if (retain_reason == nullptr) {
retain_reason = MustRetainFunction(function);
}
// Add even if we've already marked this function as possibly retained
// because this could be an additional reason for doing so.
AddRetainReason(function, retain_reason);
if (possibly_retained_functions_.ContainsKey(function)) return;
if (retain_reason != nullptr) {
possibly_retained_functions_.Insert(function);
}
if (seen_functions_.ContainsKey(function)) return;
seen_functions_.Insert(function);
pending_functions_.Add(function);
changed_ = true;
}
bool Precompiler::IsSent(const String& selector) {
if (selector.IsNull()) {
return false;
}
return sent_selectors_.HasKey(&selector);
}
void Precompiler::AddSelector(const String& selector) {
if (is_tracing()) {
tracer_->WriteSelectorRef(selector);
}
ASSERT(!selector.IsNull());
if (!IsSent(selector)) {
sent_selectors_.Insert(&String::ZoneHandle(Z, selector.ptr()));
selector_count_++;
changed_ = true;
if (FLAG_trace_precompiler) {
THR_Print("Enqueueing selector %" Pd " %s\n", selector_count_,
selector.ToCString());
}
}
}
void Precompiler::AddTableSelector(const compiler::TableSelector* selector) {
if (is_tracing()) {
tracer_->WriteTableSelectorRef(selector->id);
}
if (seen_table_selectors_.HasKey(selector->id)) return;
seen_table_selectors_.Insert(selector->id);
changed_ = true;
}
bool Precompiler::IsHitByTableSelector(const Function& function) {
const int32_t selector_id = selector_map()->SelectorId(function);
if (selector_id == compiler::SelectorMap::kInvalidSelectorId) return false;
return seen_table_selectors_.HasKey(selector_id);
}
void Precompiler::AddInstantiatedClass(const Class& cls) {
if (is_tracing()) {
tracer_->WriteClassInstantiationRef(cls);
}
if (cls.is_allocated()) return;
class_count_++;
cls.set_is_allocated_unsafe(true);
error_ = cls.EnsureIsAllocateFinalized(T);
if (!error_.IsNull()) {
Jump(error_);
}
changed_ = true;
if (FLAG_trace_precompiler) {
THR_Print("Allocation %" Pd " %s\n", class_count_, cls.ToCString());
}
const Class& superclass = Class::Handle(cls.SuperClass());
if (!superclass.IsNull()) {
AddInstantiatedClass(superclass);
}
}
// Adds all values annotated with @pragma('vm:entry-point') as roots.
void Precompiler::AddAnnotatedRoots() {
HANDLESCOPE(T);
auto& lib = Library::Handle(Z);
auto& cls = Class::Handle(Z);
auto& members = Array::Handle(Z);
auto& function = Function::Handle(Z);
auto& function2 = Function::Handle(Z);
auto& field = Field::Handle(Z);
auto& metadata = Array::Handle(Z);
auto& reusable_object_handle = Object::Handle(Z);
auto& reusable_field_handle = Field::Handle(Z);
// Lists of fields which need implicit getter/setter/static final getter
// added.
auto& implicit_getters = GrowableObjectArray::Handle(Z);
auto& implicit_setters = GrowableObjectArray::Handle(Z);
auto& implicit_static_getters = GrowableObjectArray::Handle(Z);
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
// Check for @pragma on the class itself.
if (cls.has_pragma()) {
metadata ^= lib.GetMetadata(cls);
if (FindEntryPointPragma(IG, metadata, &reusable_field_handle,
&reusable_object_handle) ==
EntryPointPragma::kAlways) {
AddInstantiatedClass(cls);
}
}
// Check for @pragma on any fields in the class.
members = cls.fields();
implicit_getters = GrowableObjectArray::New(members.Length());
implicit_setters = GrowableObjectArray::New(members.Length());
implicit_static_getters = GrowableObjectArray::New(members.Length());
for (intptr_t k = 0; k < members.Length(); ++k) {
field ^= members.At(k);
if (field.has_pragma()) {
metadata ^= lib.GetMetadata(field);
if (metadata.IsNull()) continue;
EntryPointPragma pragma = FindEntryPointPragma(
IG, metadata, &reusable_field_handle, &reusable_object_handle);
if (pragma == EntryPointPragma::kNever) continue;
AddField(field);
if (!field.is_static()) {
if (pragma != EntryPointPragma::kSetterOnly) {
implicit_getters.Add(field);
}
if (pragma != EntryPointPragma::kGetterOnly) {
implicit_setters.Add(field);
}
} else {
implicit_static_getters.Add(field);
}
}
}
// Check for @pragma on any functions in the class.
members = cls.current_functions();
for (intptr_t k = 0; k < members.Length(); k++) {
function ^= members.At(k);
if (function.has_pragma()) {
metadata ^= lib.GetMetadata(function);
if (metadata.IsNull()) continue;
auto type = FindEntryPointPragma(IG, metadata, &reusable_field_handle,
&reusable_object_handle);
if (type == EntryPointPragma::kAlways ||
type == EntryPointPragma::kCallOnly) {
functions_with_entry_point_pragmas_.Insert(function);
AddFunction(function, RetainReasons::kEntryPointPragma);
}
if ((type == EntryPointPragma::kAlways ||
type == EntryPointPragma::kGetterOnly) &&
function.kind() != UntaggedFunction::kConstructor &&
!function.IsSetterFunction()) {
function2 = function.ImplicitClosureFunction();
functions_with_entry_point_pragmas_.Insert(function2);
AddFunction(function2, RetainReasons::kEntryPointPragma);
}
if (function.IsGenerativeConstructor()) {
AddInstantiatedClass(cls);
}
}
if (function.kind() == UntaggedFunction::kImplicitGetter &&
!implicit_getters.IsNull()) {
for (intptr_t i = 0; i < implicit_getters.Length(); ++i) {
field ^= implicit_getters.At(i);
if (function.accessor_field() == field.ptr()) {
functions_with_entry_point_pragmas_.Insert(function);
AddFunction(function, RetainReasons::kImplicitGetter);
}
}
}
if (function.kind() == UntaggedFunction::kImplicitSetter &&
!implicit_setters.IsNull()) {
for (intptr_t i = 0; i < implicit_setters.Length(); ++i) {
field ^= implicit_setters.At(i);
if (function.accessor_field() == field.ptr()) {
functions_with_entry_point_pragmas_.Insert(function);
AddFunction(function, RetainReasons::kImplicitSetter);
}
}
}
if (function.kind() == UntaggedFunction::kImplicitStaticGetter &&
!implicit_static_getters.IsNull()) {
for (intptr_t i = 0; i < implicit_static_getters.Length(); ++i) {
field ^= implicit_static_getters.At(i);
if (function.accessor_field() == field.ptr()) {
functions_with_entry_point_pragmas_.Insert(function);
AddFunction(function, RetainReasons::kImplicitStaticGetter);
}
}
}
}
implicit_getters = GrowableObjectArray::null();
implicit_setters = GrowableObjectArray::null();
implicit_static_getters = GrowableObjectArray::null();
}
}
}
void Precompiler::CheckForNewDynamicFunctions() {
PRECOMPILER_TIMER_SCOPE(this, CheckForNewDynamicFunctions);
HANDLESCOPE(T);
Library& lib = Library::Handle(Z);
Class& cls = Class::Handle(Z);
Array& functions = Array::Handle(Z);
Function& function = Function::Handle(Z);
Function& function2 = Function::Handle(Z);
String& selector = String::Handle(Z);
String& selector2 = String::Handle(Z);
String& selector3 = String::Handle(Z);
Field& field = Field::Handle(Z);
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
if (!cls.is_allocated()) continue;
functions = cls.current_functions();
for (intptr_t k = 0; k < functions.Length(); k++) {
function ^= functions.At(k);
if (function.is_static() || function.is_abstract()) continue;
// Don't bail out early if there is already code because we may discover
// the corresponding getter selector is sent in some later iteration.
// if (function.HasCode()) continue;
selector = function.name();
if (IsSent(selector)) {
AddFunction(function, RetainReasons::kCalledViaSelector);
}
if (IsHitByTableSelector(function)) {
AddFunction(function, FLAG_retain_function_objects
? RetainReasons::kForcedRetain
: nullptr);
}
bool found_metadata = false;
kernel::ProcedureAttributesMetadata metadata;
// Handle the implicit call type conversions.
if (Field::IsGetterName(selector) &&
(function.kind() != UntaggedFunction::kMethodExtractor)) {
// Call-through-getter.
// Function is get:foo and somewhere foo (or dyn:foo) is called.
// Note that we need to skip method extractors (which were potentially
// created by DispatchTableGenerator): call of foo will never
// hit method extractor get:foo, because it will hit an existing
// method foo first.
selector2 = Field::NameFromGetter(selector);
if (IsSent(selector2)) {
AddFunction(function, RetainReasons::kCalledThroughGetter);
}
selector2 = Function::CreateDynamicInvocationForwarderName(selector2);
if (IsSent(selector2)) {
selector2 =
Function::CreateDynamicInvocationForwarderName(selector);
function2 = function.GetDynamicInvocationForwarder(selector2);
AddFunction(function2, RetainReasons::kDynamicInvocationForwarder);
functions_called_dynamically_.Insert(function2);
}
} else if (function.kind() == UntaggedFunction::kRegularFunction) {
selector2 = Field::LookupGetterSymbol(selector);
selector3 = String::null();
if (!selector2.IsNull()) {
selector3 =
Function::CreateDynamicInvocationForwarderName(selector2);
}
if (IsSent(selector2) || IsSent(selector3)) {
metadata = kernel::ProcedureAttributesOf(function, Z);
found_metadata = true;
if (metadata.has_tearoff_uses) {
// Closurization.
// Function is foo and somewhere get:foo is called.
function2 = function.ImplicitClosureFunction();
AddFunction(function2, RetainReasons::kImplicitClosure);
// Add corresponding method extractor.
function2 = function.GetMethodExtractor(selector2);
AddFunction(function2, RetainReasons::kMethodExtractor);
}
}
}
const bool is_getter =
function.kind() == UntaggedFunction::kImplicitGetter ||
function.kind() == UntaggedFunction::kGetterFunction;
const bool is_setter =
function.kind() == UntaggedFunction::kImplicitSetter ||
function.kind() == UntaggedFunction::kSetterFunction;
const bool is_regular =
function.kind() == UntaggedFunction::kRegularFunction;
if (is_getter || is_setter || is_regular) {
selector2 = Function::CreateDynamicInvocationForwarderName(selector);
if (IsSent(selector2)) {
if (function.kind() == UntaggedFunction::kImplicitGetter ||
function.kind() == UntaggedFunction::kImplicitSetter) {
field = function.accessor_field();
metadata = kernel::ProcedureAttributesOf(field, Z);
} else if (!found_metadata) {
metadata = kernel::ProcedureAttributesOf(function, Z);
}
if (is_getter) {
if (metadata.getter_called_dynamically) {
function2 = function.GetDynamicInvocationForwarder(selector2);
AddFunction(function2,
RetainReasons::kDynamicInvocationForwarder);
functions_called_dynamically_.Insert(function2);
}
} else {
if (metadata.method_or_setter_called_dynamically) {
function2 = function.GetDynamicInvocationForwarder(selector2);
AddFunction(function2,
RetainReasons::kDynamicInvocationForwarder);
functions_called_dynamically_.Insert(function2);
}
}
}
}
}
}
}
}
class NameFunctionsTraits {
public:
static const char* Name() { return "NameFunctionsTraits"; }
static bool ReportStats() { return false; }
static bool IsMatch(const Object& a, const Object& b) {
return a.IsString() && b.IsString() &&
String::Cast(a).Equals(String::Cast(b));
}
static uword Hash(const Object& obj) { return String::Cast(obj).Hash(); }
static ObjectPtr NewKey(const String& str) { return str.ptr(); }
};
typedef UnorderedHashMap<NameFunctionsTraits> Table;
static void AddNameToFunctionsTable(Zone* zone,
Table* table,
const String& fname,
const Function& function) {
Array& farray = Array::Handle(zone);
farray ^= table->InsertNewOrGetValue(fname, Array::empty_array());
farray = Array::Grow(farray, farray.Length() + 1);
farray.SetAt(farray.Length() - 1, function);
table->UpdateValue(fname, farray);
}
static void AddNamesToFunctionsTable(Zone* zone,
Table* table,
const String& fname,
const Function& function,
String* mangled_name,
Function* dyn_function) {
AddNameToFunctionsTable(zone, table, fname, function);
*dyn_function = function.ptr();
if (kernel::NeedsDynamicInvocationForwarder(function)) {
*mangled_name = function.name();
*mangled_name =
Function::CreateDynamicInvocationForwarderName(*mangled_name);
*dyn_function = function.GetDynamicInvocationForwarder(*mangled_name,
/*allow_add=*/true);
}
*mangled_name = Function::CreateDynamicInvocationForwarderName(fname);
AddNameToFunctionsTable(zone, table, *mangled_name, *dyn_function);
}
void Precompiler::CollectDynamicFunctionNames() {
if (!FLAG_collect_dynamic_function_names) {
return;
}
HANDLESCOPE(T);
auto& lib = Library::Handle(Z);
auto& cls = Class::Handle(Z);
auto& functions = Array::Handle(Z);
auto& function = Function::Handle(Z);
auto& fname = String::Handle(Z);
auto& farray = Array::Handle(Z);
auto& mangled_name = String::Handle(Z);
auto& dyn_function = Function::Handle(Z);
Table table(HashTables::New<Table>(100));
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
functions = cls.current_functions();
const intptr_t length = functions.Length();
for (intptr_t j = 0; j < length; j++) {
function ^= functions.At(j);
if (function.IsDynamicFunction()) {
fname = function.name();
if (function.IsSetterFunction() ||
function.IsImplicitSetterFunction()) {
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
} else if (function.IsGetterFunction() ||
function.IsImplicitGetterFunction()) {
// Enter both getter and non getter name.
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
fname = Field::NameFromGetter(fname);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
} else if (function.IsMethodExtractor()) {
// Skip. We already add getter names for regular methods below.
continue;
} else {
// Regular function. Enter both getter and non getter name.
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
fname = Field::GetterName(fname);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
}
}
}
}
}
// Locate all entries with one function only
Table::Iterator iter(&table);
String& key = String::Handle(Z);
String& key_demangled = String::Handle(Z);
UniqueFunctionsMap functions_map(HashTables::New<UniqueFunctionsMap>(20));
while (iter.MoveNext()) {
intptr_t curr_key = iter.Current();
key ^= table.GetKey(curr_key);
farray ^= table.GetOrNull(key);
ASSERT(!farray.IsNull());
if (farray.Length() == 1) {
function ^= farray.At(0);
// It looks like there is exactly one target for the given name. Though we
// have to be careful: e.g. A name like `dyn:get:foo` might have a target
// `foo()`. Though the actual target would be a lazily created method
// extractor `get:foo` for the `foo` function.
//
// We'd like to prevent eager creation of functions which we normally
// create lazily.
// => We disable unique target optimization if the target belongs to the
// lazily created functions.
key_demangled = key.ptr();
if (Function::IsDynamicInvocationForwarderName(key)) {
key_demangled = Function::DemangleDynamicInvocationForwarderName(key);
}
if (function.name() != key.ptr() &&
function.name() != key_demangled.ptr()) {
continue;
}
functions_map.UpdateOrInsert(key, function);
}
}
farray ^= table.GetOrNull(Symbols::GetRuntimeType());
get_runtime_type_is_unique_ = !farray.IsNull() && (farray.Length() == 1);
if (FLAG_print_unique_targets) {
UniqueFunctionsMap::Iterator unique_iter(&functions_map);
while (unique_iter.MoveNext()) {
intptr_t curr_key = unique_iter.Current();
function ^= functions_map.GetPayload(curr_key, 0);
THR_Print("* %s\n", function.ToQualifiedCString());
}
THR_Print("%" Pd " of %" Pd " dynamic selectors are unique\n",
functions_map.NumOccupied(), table.NumOccupied());
}
IG->object_store()->set_unique_dynamic_targets(functions_map.Release());
table.Release();
}
void Precompiler::TraceForRetainedFunctions() {
HANDLESCOPE(T);
Library& lib = Library::Handle(Z);
Class& cls = Class::Handle(Z);
Array& functions = Array::Handle(Z);
String& name = String::Handle(Z);
Function& function = Function::Handle(Z);
Function& function2 = Function::Handle(Z);
Array& fields = Array::Handle(Z);
Field& field = Field::Handle(Z);
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
functions = cls.current_functions();
for (intptr_t j = 0; j < functions.Length(); j++) {
SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
function ^= functions.At(j);
function.DropUncompiledImplicitClosureFunction();
const bool retained =
possibly_retained_functions_.ContainsKey(function);
if (retained) {
AddTypesOf(function);
}
if (function.HasImplicitClosureFunction()) {
function2 = function.ImplicitClosureFunction();
if (possibly_retained_functions_.ContainsKey(function2)) {
AddTypesOf(function2);
// If function has @pragma('vm:entry-point', 'get') we need to keep
// the function itself around so that runtime could find it and
// get to the implicit closure through it.
if (!retained &&
functions_with_entry_point_pragmas_.ContainsKey(function2)) {
AddRetainReason(function, RetainReasons::kEntryPointPragma);
AddTypesOf(function);
}
}
}
}
fields = cls.fields();
for (intptr_t j = 0; j < fields.Length(); j++) {
field ^= fields.At(j);
if (fields_to_retain_.HasKey(&field) &&
field.HasInitializerFunction()) {
function = field.InitializerFunction();
if (possibly_retained_functions_.ContainsKey(function)) {
AddTypesOf(function);
}
}
}
{
functions = cls.invocation_dispatcher_cache();
InvocationDispatcherTable dispatchers(functions);
for (auto dispatcher : dispatchers) {
name = dispatcher.Get<Class::kInvocationDispatcherName>();
if (name.IsNull()) break; // Reached last entry.
function = dispatcher.Get<Class::kInvocationDispatcherFunction>();
if (possibly_retained_functions_.ContainsKey(function)) {
AddTypesOf(function);
}
}
}
}
}
ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& function) {
if (possibly_retained_functions_.ContainsKey(function)) {
AddTypesOf(function);
}
return true; // Continue iteration.
});
#ifdef DEBUG
// Make sure functions_to_retain_ is a super-set of
// possibly_retained_functions_.
FunctionSet::Iterator it(&possibly_retained_functions_);
while (it.MoveNext()) {
function ^= possibly_retained_functions_.GetKey(it.Current());
// Ffi trampoline functions are not reachable from program structure,
// they are referenced only from code (object pool).
if (!functions_to_retain_.ContainsKey(function) &&
!function.IsFfiTrampoline()) {
FATAL1("Function %s was not traced in TraceForRetainedFunctions\n",
function.ToFullyQualifiedCString());
}
}
#endif // DEBUG
}
void Precompiler::FinalizeDispatchTable() {
PRECOMPILER_TIMER_SCOPE(this, FinalizeDispatchTable);
HANDLESCOPE(T);
// Build the entries used to serialize the dispatch table before
// dropping functions, as we may clear references to Code objects.
const auto& entries =
Array::Handle(Z, dispatch_table_generator_->BuildCodeArray());
IG->object_store()->set_dispatch_table_code_entries(entries);
// Delete the dispatch table generator to ensure there's no attempt
// to add new entries after this point.
delete dispatch_table_generator_;
dispatch_table_generator_ = nullptr;
if (FLAG_retain_function_objects || !FLAG_trace_precompiler) return;
FunctionSet printed(HashTables::New<FunctionSet>(/*initial_capacity=*/1024));
auto& code = Code::Handle(Z);
auto& function = Function::Handle(Z);
for (intptr_t i = 0; i < entries.Length(); i++) {
code = Code::RawCast(entries.At(i));
if (code.IsNull()) continue;
if (!code.IsFunctionCode()) continue;
function = code.function();
ASSERT(!function.IsNull());
if (printed.ContainsKey(function)) continue;
if (functions_to_retain_.ContainsKey(function)) continue;
THR_Print("Dispatch table references code for function to drop: %s\n",
function.ToLibNamePrefixedQualifiedCString());
printed.Insert(function);
}
printed.Release();
}
void Precompiler::ReplaceFunctionStaticCallEntries() {
PRECOMPILER_TIMER_SCOPE(this, ReplaceFunctionStaticCallEntries);
class StaticCallTableEntryFixer : public CodeVisitor {
public:
explicit StaticCallTableEntryFixer(Zone* zone)
: table_(Array::Handle(zone)),
kind_and_offset_(Smi::Handle(zone)),
target_function_(Function::Handle(zone)),
target_code_(Code::Handle(zone)),
pool_(ObjectPool::Handle(zone)) {}
void VisitCode(const Code& code) {
if (!code.IsFunctionCode()) return;
table_ = code.static_calls_target_table();
StaticCallsTable static_calls(table_);
// With bare instructions, there is a global pool and per-Code local
// pools. Instructions are generated to use offsets into the global pool,
// but we still use the local pool to track which Code are using which
// pool values for the purposes of analyzing snapshot size
// (--write_v8_snapshot_profile_to and --print_instructions_sizes_to) and
// deferred loading deciding which snapshots to place pool values in.
// We don't keep track of which offsets in the local pools correspond to
// which entries in the static call table, so we don't properly replace
// the old references to the CallStaticFunction stub, but it is sufficient
// for the local pool to include the actual call target.
compiler::ObjectPoolBuilder builder;
pool_ = code.object_pool();
pool_.CopyInto(&builder);
for (auto& view : static_calls) {
kind_and_offset_ = view.Get<Code::kSCallTableKindAndOffset>();
auto const kind = Code::KindField::decode(kind_and_offset_.Value());
if ((kind != Code::kCallViaCode) && (kind != Code::kPcRelativeCall))
continue;
target_function_ = view.Get<Code::kSCallTableFunctionTarget>();
if (target_function_.IsNull()) continue;
ASSERT(view.Get<Code::kSCallTableCodeOrTypeTarget>() == Code::null());
ASSERT(target_function_.HasCode());
target_code_ = target_function_.CurrentCode();
ASSERT(!target_code_.IsStubCode());
view.Set<Code::kSCallTableCodeOrTypeTarget>(target_code_);
view.Set<Code::kSCallTableFunctionTarget>(Object::null_function());
if (kind == Code::kCallViaCode) {
auto const pc_offset =
Code::OffsetField::decode(kind_and_offset_.Value());
const uword pc = pc_offset + code.PayloadStart();
CodePatcher::PatchStaticCallAt(pc, code, target_code_);
builder.AddObject(Object::ZoneHandle(target_code_.ptr()));
}
if (FLAG_trace_precompiler) {
THR_Print("Updated static call entry to %s in \"%s\"\n",
target_function_.ToFullyQualifiedCString(),
code.ToCString());
}
}
code.set_object_pool(ObjectPool::NewFromBuilder(builder));
}
private:
Array& table_;
Smi& kind_and_offset_;
Function& target_function_;
Code& target_code_;
ObjectPool& pool_;
};
HANDLESCOPE(T);
StaticCallTableEntryFixer visitor(Z);
ProgramVisitor::WalkProgram(Z, IG, &visitor);
}
void Precompiler::DropFunctions() {
HANDLESCOPE(T);
Library& lib = Library::Handle(Z);
Class& cls = Class::Handle(Z);
Array& functions = Array::Handle(Z);
Function& function = Function::Handle(Z);
Function& target = Function::Handle(Z);
Function& implicit_closure = Function::Handle(Z);
Code& code = Code::Handle(Z);
Object& owner = Object::Handle(Z);
GrowableObjectArray& retained_functions = GrowableObjectArray::Handle(Z);
auto& sig = FunctionType::Handle(Z);
auto& ref = Object::Handle(Z);
auto trim_function = [&](const Function& function) {
if (function.IsDynamicInvocationForwarder()) {
// For dynamic invocation forwarders sever strong connection between the
// forwarder and the target function if we are not going to retain
// target function anyway. The only use of the forwarding target outside
// of compilation pipeline is in Function::script() and that should not
// be used when we are dropping functions (cause we are not going to
// emit symbolic stack traces anyway).
// Note that we still need Function::script() to work during snapshot
// generation to generate DWARF, that's why we are using WSR and not
// simply setting forwarding target to null.
target = function.ForwardingTarget();
if (!functions_to_retain_.ContainsKey(target)) {
ref =
WeakSerializationReference::New(target, Function::null_function());
function.set_data(ref);
}
}
sig = function.signature();
// In the AOT runtime, most calls are direct or through the dispatch table,
// not resolved via dynamic lookup. Thus, we only need to retain the
// function signature in the following cases:
if (function.IsClosureFunction()) {
// Dynamic calls to closures go through dynamic closure call dispatchers,
// which need the signature.
return AddRetainReason(sig, RetainReasons::kClosureSignature);
}
if (function.IsFfiTrampoline()) {
// FFI trampolines may be dynamically called.
return AddRetainReason(sig, RetainReasons::kFfiTrampolineSignature);
}
if (function.is_native()) {
return AddRetainReason(sig, RetainReasons::kNativeSignature);
}
if (function.HasRequiredNamedParameters()) {
// Required named parameters must be checked, so a NoSuchMethod exception
// can be thrown if they are not provided.
return AddRetainReason(sig, RetainReasons::kRequiredNamedParameters);
}
if (functions_called_dynamically_.ContainsKey(function)) {
// Dynamic resolution of these functions checks for valid arguments.
return AddRetainReason(sig, RetainReasons::kDynamicallyCalledSignature);
}
if (functions_with_entry_point_pragmas_.ContainsKey(function)) {
// Dynamic resolution of entry points also checks for valid arguments.
return AddRetainReason(sig, RetainReasons::kEntryPointPragmaSignature);
}
if (FLAG_trace_precompiler) {
THR_Print("Clearing signature for function %s\n",
function.ToLibNamePrefixedQualifiedCString());
}
// Other functions not listed here may end up in dynamic resolution via
// UnlinkedCalls. However, since it is not a dynamic invocation and has
// been type checked at compile time, we already know the arguments are
// valid. Thus, we can skip checking arguments for functions with dropped
// signatures in ResolveDynamicForReceiverClassWithCustomLookup.
ref = WeakSerializationReference::New(sig, Object::null_function_type());
function.set_signature(ref);
};
auto drop_function = [&](const Function& function) {
if (function.HasCode()) {
code = function.CurrentCode();
function.ClearCode();
// Wrap the owner of the code object in case the code object will be
// serialized but the function object will not.
owner = code.owner();
owner = WeakSerializationReference::New(
owner, Smi::Handle(Smi::New(owner.GetClassId())));
code.set_owner(owner);
}
if (function.HasImplicitClosureFunction()) {
// If we are going to drop the function which has a compiled
// implicit closure move the closure itself to the list of closures
// attached to the object store so that ProgramVisitor could find it.
// The list of closures is going to be dropped during PRODUCT snapshotting
// so there is no overhead in doing so.
implicit_closure = function.ImplicitClosureFunction();
RELEASE_ASSERT(functions_to_retain_.ContainsKey(implicit_closure));
ClosureFunctionsCache::AddClosureFunctionLocked(
implicit_closure, /*allow_implicit_closure_functions=*/true);
}
dropped_function_count_++;
if (FLAG_trace_precompiler) {
THR_Print("Dropping function %s\n",
function.ToLibNamePrefixedQualifiedCString());
}
if (retained_reasons_writer_ != nullptr) {
retained_reasons_writer_->AddDropped(function);
}
};
SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
auto& dispatchers_array = Array::Handle(Z);
auto& name = String::Handle(Z);
auto& desc = Array::Handle(Z);
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
functions = cls.functions();
retained_functions = GrowableObjectArray::New();
for (intptr_t j = 0; j < functions.Length(); j++) {
function ^= functions.At(j);
function.DropUncompiledImplicitClosureFunction();
if (functions_to_retain_.ContainsKey(function)) {
trim_function(function);
retained_functions.Add(function);
} else {
drop_function(function);
}
}
if (retained_functions.Length() > 0) {
functions = Array::MakeFixedLength(retained_functions);
cls.SetFunctions(functions);
} else {
cls.SetFunctions(Object::empty_array());
}
retained_functions = GrowableObjectArray::New();
{
dispatchers_array = cls.invocation_dispatcher_cache();
InvocationDispatcherTable dispatchers(dispatchers_array);
for (auto dispatcher : dispatchers) {
name = dispatcher.Get<Class::kInvocationDispatcherName>();
if (name.IsNull()) break; // Reached last entry.
desc = dispatcher.Get<Class::kInvocationDispatcherArgsDesc>();
function = dispatcher.Get<Class::kInvocationDispatcherFunction>();
if (functions_to_retain_.ContainsKey(function)) {
retained_functions.Add(name);
retained_functions.Add(desc);
trim_function(function);
retained_functions.Add(function);
} else {
drop_function(function);
}
}
}
if (retained_functions.Length() > 0) {
// Last entry must be null.
retained_functions.Add(Object::null_object());
retained_functions.Add(Object::null_object());
retained_functions.Add(Object::null_object());
functions = Array::MakeFixedLength(retained_functions);
} else {
functions = Object::empty_array().ptr();
}
cls.set_invocation_dispatcher_cache(functions);
}
}
retained_functions = GrowableObjectArray::New();
ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& function) {
if (functions_to_retain_.ContainsKey(function)) {
trim_function(function);
retained_functions.Add(function);
} else {
drop_function(function);
}
return true; // Continue iteration.
});
// Note: in PRODUCT mode snapshotter will drop this field when serializing.
// This is done in ProgramSerializationRoots.
IG->object_store()->set_closure_functions(retained_functions);
}
void Precompiler::DropFields() {
HANDLESCOPE(T);
Library& lib = Library::Handle(Z);
Class& cls = Class::Handle(Z);
Array& fields = Array::Handle(Z);
Field& field = Field::Handle(Z);
GrowableObjectArray& retained_fields = GrowableObjectArray::Handle(Z);
AbstractType& type = AbstractType::Handle(Z);
SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
fields = cls.fields();
retained_fields = GrowableObjectArray::New();
for (intptr_t j = 0; j < fields.Length(); j++) {
field ^= fields.At(j);
bool retain = fields_to_retain_.HasKey(&field);
#if !defined(PRODUCT)
if (field.is_instance() && cls.is_allocated()) {
// Keep instance fields so their names are available to graph tools.
retain = true;
}
#endif
if (retain) {
if (FLAG_trace_precompiler) {
THR_Print("Retaining %s field %s\n",
field.is_static() ? "static" : "instance",
field.ToCString());
}
retained_fields.Add(field);
type = field.type();
AddType(type);
} else {
dropped_field_count_++;
if (FLAG_trace_precompiler) {
THR_Print("Dropping %s field %s\n",
field.is_static() ? "static" : "instance",
field.ToCString());
}
// This cleans up references to field current and initial values.
if (field.is_static()) {
field.SetStaticValue(Object::null_instance());
field.SetStaticConstFieldValue(Object::null_instance(),
/*assert_initializing_store=*/false);
}
}
}
if (retained_fields.Length() > 0) {
fields = Array::MakeFixedLength(retained_fields);
cls.SetFields(fields);
} else {
cls.SetFields(Object::empty_array());
}
}
}
}
void Precompiler::AttachOptimizedTypeTestingStub() {
PRECOMPILER_TIMER_SCOPE(this, AttachOptimizedTypeTestingStub);
HANDLESCOPE(T);
IsolateGroup::Current()->heap()->CollectAllGarbage();
GrowableHandlePtrArray<const AbstractType> types(Z, 200);
{
class TypesCollector : public ObjectVisitor {
public:
explicit TypesCollector(Zone* zone,
GrowableHandlePtrArray<const AbstractType>* types)
: type_(AbstractType::Handle(zone)), types_(types) {}
void VisitObject(ObjectPtr obj) {
if (obj->GetClassId() == kTypeCid ||
obj->GetClassId() == kFunctionTypeCid ||
obj->GetClassId() == kTypeRefCid) {
type_ ^= obj;
types_->Add(type_);
}
}
private:
AbstractType& type_;
GrowableHandlePtrArray<const AbstractType>* types_;
};
HeapIterationScope his(T);
TypesCollector visitor(Z, &types);
// Find all type objects in this isolate.
IG->heap()->VisitObjects(&visitor);
// Find all type objects in the vm-isolate.
Dart::vm_isolate_group()->heap()->VisitObjects(&visitor);
}
TypeUsageInfo* type_usage_info = Thread::Current()->type_usage_info();
// At this point we're not generating any new code, so we build a picture of
// which types we might type-test against.
type_usage_info->BuildTypeUsageInformation();
TypeTestingStubGenerator type_testing_stubs;
Code& code = Code::Handle();
for (intptr_t i = 0; i < types.length(); i++) {
const AbstractType& type = types.At(i);
if (type.InVMIsolateHeap()) {
// The only important types in the vm isolate are
// "dynamic"/"void"/"Never", which will get their optimized
// testing stub installed at creation.
continue;
}
if (type_usage_info->IsUsedInTypeTest(type)) {
code = type_testing_stubs.OptimizedCodeForType(type);
type.SetTypeTestingStub(code);
// Ensure we retain the type.
AddType(type);
}
}
ASSERT(Object::dynamic_type().type_test_stub_entry_point() ==
StubCode::TopTypeTypeTest().EntryPoint());
}
void Precompiler::TraceTypesFromRetainedClasses() {
HANDLESCOPE(T);
auto& lib = Library::Handle(Z);
auto& cls = Class::Handle(Z);
auto& members = Array::Handle(Z);
auto& constants = Array::Handle(Z);
auto& retained_constants = GrowableObjectArray::Handle(Z);
auto& obj = Object::Handle(Z);
auto& constant = Instance::Handle(Z);
SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
bool retain = false;
members = cls.fields();
if (members.Length() > 0) {
retain = true;
}
members = cls.current_functions();
if (members.Length() > 0) {
retain = true;
}
if (cls.is_allocated()) {
retain = true;
}
constants = cls.constants();
retained_constants = GrowableObjectArray::New();
if (!constants.IsNull()) {
for (intptr_t j = 0; j < constants.Length(); j++) {
obj = constants.At(j);
if ((obj.ptr() == HashTableBase::UnusedMarker().ptr()) ||
(obj.ptr() == HashTableBase::DeletedMarker().ptr())) {
continue;
}
constant ^= obj.ptr();
bool retain = consts_to_retain_.HasKey(&constant);
if (retain) {
retained_constants.Add(constant);
}
}
}
// Rehash.
cls.set_constants(Object::null_array());
for (intptr_t j = 0; j < retained_constants.Length(); j++) {
constant ^= retained_constants.At(j);
cls.InsertCanonicalConstant(Z, constant);
}
if (retained_constants.Length() > 0) {
ASSERT(retain); // This shouldn't be the reason we keep a class.
retain = true;
}
if (retain) {
AddTypesOf(cls);
}
}
}
}
void Precompiler::DropMetadata() {
HANDLESCOPE(T);
SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
Library& lib = Library::Handle(Z);
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
lib.set_metadata(Array::null_array());
}
}
void Precompiler::DropLibraryEntries() {
HANDLESCOPE(T);
Library& lib = Library::Handle(Z);
Array& dict = Array::Handle(Z);
Object& entry = Object::Handle(Z);
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
dict = lib.dictionary();
intptr_t dict_size = dict.Length() - 1;
intptr_t used = 0;
for (intptr_t j = 0; j < dict_size; j++) {
entry = dict.At(j);
if (entry.IsNull()) continue;
if (entry.IsClass()) {
if (classes_to_retain_.HasKey(&Class::Cast(entry))) {
used++;
continue;
}
} else if (entry.IsFunction()) {
if (functions_to_retain_.ContainsKey(Function::Cast(entry))) {
used++;
continue;
}
} else if (entry.IsField()) {
if (fields_to_retain_.HasKey(&Field::Cast(entry))) {
used++;
continue;
}
} else if (entry.IsLibraryPrefix()) {
// Always drop.
} else {
FATAL1("Unexpected library entry: %s", entry.ToCString());
}
dict.SetAt(j, Object::null_object());
}
lib.RehashDictionary(dict, used * 4 / 3 + 1);
if (!(retain_root_library_caches_ &&
(lib.ptr() == IG->object_store()->root_library()))) {
lib.DropDependenciesAndCaches();
}
}
}
void Precompiler::DropClasses() {
HANDLESCOPE(T);
Class& cls = Class::Handle(Z);
Array& constants = Array::Handle(Z);
GrowableObjectArray& implementors = GrowableObjectArray::Handle(Z);
GrowableObjectArray& retained_implementors = GrowableObjectArray::Handle(Z);
Class& implementor = Class::Handle(Z);
GrowableObjectArray& subclasses = GrowableObjectArray::Handle(Z);
GrowableObjectArray& retained_subclasses = GrowableObjectArray::Handle(Z);
Class& subclass = Class::Handle(Z);
// We are about to remove classes from the class table. For this to be safe,
// there must be no instances of these classes on the heap, not even
// corpses because the class table entry may be used to find the size of
// corpses. Request a full GC and wait for the sweeper tasks to finish before
// we continue.
IG->heap()->CollectAllGarbage();
IG->heap()->WaitForSweeperTasks(T);
SafepointWriteRwLocker ml(T, IG->program_lock());
ClassTable* class_table = IG->class_table();
intptr_t num_cids = class_table->NumCids();
for (intptr_t cid = 0; cid < num_cids; cid++) {
if (!class_table->IsValidIndex(cid)) continue;
if (!class_table->HasValidClassAt(cid)) continue;
cls = class_table->At(cid);
constants = cls.constants();
HashTables::Weaken(constants);
}
for (intptr_t cid = kNumPredefinedCids; cid < num_cids; cid++) {
if (!class_table->IsValidIndex(cid)) continue;
if (!class_table->HasValidClassAt(cid)) continue;
cls = class_table->At(cid);
ASSERT(!cls.IsNull());
implementors = cls.direct_implementors();
if (!implementors.IsNull()) {
retained_implementors = GrowableObjectArray::New();
for (intptr_t i = 0; i < implementors.Length(); i++) {
implementor ^= implementors.At(i);
if (classes_to_retain_.HasKey(&implementor)) {
retained_implementors.Add(implementor);
}
}
cls.set_direct_implementors(retained_implementors);
}
subclasses = cls.direct_subclasses();
if (!subclasses.IsNull()) {
retained_subclasses = GrowableObjectArray::New();
for (intptr_t i = 0; i < subclasses.Length(); i++) {
subclass ^= subclasses.At(i);
if (classes_to_retain_.HasKey(&subclass)) {
retained_subclasses.Add(subclass);
}
}
cls.set_direct_subclasses(retained_subclasses);
}
if (cls.IsTopLevel()) {
// Top-level classes are referenced directly from their library. They
// will only be removed as a consequence of an entire library being
// removed.
continue;
}
bool retain = classes_to_retain_.HasKey(&cls);
if (retain) {
continue;
}
ASSERT(!cls.is_allocated());
constants = cls.constants();
ASSERT(constants.IsNull() || (constants.Length() == 0));
dropped_class_count_++;
if (FLAG_trace_precompiler) {
THR_Print("Dropping class %" Pd " %s\n", cid, cls.ToCString());
}
cls.set_id(kIllegalCid); // We check this when serializing.
}
}
void Precompiler::DropLibraries() {
HANDLESCOPE(T);
const GrowableObjectArray& retained_libraries =
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
const Library& root_lib =
Library::Handle(Z, IG->object_store()->root_library());
Library& lib = Library::Handle(Z);
Class& toplevel_class = Class::Handle(Z);
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
intptr_t entries = 0;
DictionaryIterator it(lib);
while (it.HasNext()) {
entries++;
it.GetNext();
}
bool retain = false;
if (entries > 0) {
retain = true;
} else if (lib.is_dart_scheme()) {
// The core libraries are referenced from the object store.
retain = true;
} else if (lib.ptr() == root_lib.ptr()) {
// The root library might have no surviving members if it only exports
// main from another library. It will still be referenced from the object
// store, so retain it.
retain = true;
} else {
// A type for a top-level class may be referenced from an object pool as
// part of an error message.
toplevel_class = lib.toplevel_class();
if (classes_to_retain_.HasKey(&toplevel_class)) {
retain = true;
}
}
if (retain) {
lib.set_index(retained_libraries.Length());
retained_libraries.Add(lib);
} else {
toplevel_class = lib.toplevel_class();
IG->class_table()->UnregisterTopLevel(toplevel_class.id());
toplevel_class.set_id(kIllegalCid); // We check this when serializing.
dropped_library_count_++;
lib.set_index(-1);
if (FLAG_trace_precompiler) {
THR_Print("Dropping library %s\n", lib.ToCString());
}
}
}
Library::RegisterLibraries(T, retained_libraries);
libraries_ = retained_libraries.ptr();
}
// Traverse program structure and mark Code objects
// which do not have useful information as discarded.
// Should be called after Precompiler::ReplaceFunctionStaticCallEntries().
// Should be called before ProgramVisitor::Dedup() as Dedup may clear
// static calls target table.
void Precompiler::DiscardCodeObjects() {
class DiscardCodeVisitor : public CodeVisitor {
public:
DiscardCodeVisitor(Zone* zone,
const FunctionSet& functions_to_retain,
const FunctionSet& functions_called_dynamically)
: zone_(zone),
function_(Function::Handle(zone)),
class_(Class::Handle(zone)),
library_(Library::Handle(zone)),
loading_unit_(LoadingUnit::Handle(zone)),
static_calls_target_table_(Array::Handle(zone)),
kind_and_offset_(Smi::Handle(zone)),
call_target_(Code::Handle(zone)),
targets_of_calls_via_code_(
GrowableObjectArray::Handle(zone, GrowableObjectArray::New())),
functions_to_retain_(functions_to_retain),
functions_called_dynamically_(functions_called_dynamically) {}
// Certain static calls (e.g. between different loading units) are
// performed through Code objects indirectly. Such Code objects
// cannot be fully discarded.
void RecordCodeObjectsUsedForCalls(const Code& code) {
static_calls_target_table_ = code.static_calls_target_table();
if (static_calls_target_table_.IsNull()) return;
StaticCallsTable static_calls(static_calls_target_table_);
for (const auto& view : static_calls) {
kind_and_offset_ = view.Get<Code::kSCallTableKindAndOffset>();
auto const kind = Code::KindField::decode(kind_and_offset_.Value());
if (kind == Code::kCallViaCode) {
call_target_ =
Code::RawCast(view.Get<Code::kSCallTableCodeOrTypeTarget>());
ASSERT(!call_target_.IsNull());
targets_of_calls_via_code_.Add(call_target_);
}
}
}
void VisitCode(const Code& code) override {
++total_code_objects_;
RecordCodeObjectsUsedForCalls(code);