// 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 #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/ffi/callback.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/ffi/native_assets.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/resolver.h" #include "vm/runtime_entry.h" #include "vm/stack_trace.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(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(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 needed for async stack unwinding. static constexpr const char* kAsyncStackUnwinding = "needed for async stack unwinding"; // 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 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 StackResource { public: explicit RetainedReasonsWriter(Zone* zone) : StackResource(Thread::Current()), 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; 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) : precompiler_(precompiler), parsed_function_(parsed_function), thread_(Thread::Current()) {} bool Compile(); private: ParsedFunction* parsed_function() const { return parsed_function_; } Thread* thread() const { return thread_; } bool GenerateCode(FlowGraph* flow_graph); void FinalizeCompilation(compiler::Assembler* assembler, FlowGraphCompiler* graph_compiler, FlowGraph* flow_graph, CodeStatistics* stats); Precompiler* precompiler_; ParsedFunction* parsed_function_; 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() { Thread* thread = Thread::Current(); LongJumpScope jump(thread); if (DART_SETJMP(*jump.Set()) == 0) { Precompiler precompiler(thread); precompiler.DoCompileAll(); precompiler.ReportStats(); return Error::null(); } else { return thread->StealStickyError(); } } void Precompiler::ReportStats() { if (!FLAG_print_precompiler_timings) { return; } thread()->compiler_timings()->Print(); } Precompiler::Precompiler(Thread* thread) : thread_(thread), zone_(nullptr), 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), dropped_constants_arrays_entries_count_(0), libraries_(GrowableObjectArray::Handle( thread->isolate_group()->object_store()->libraries())), pending_functions_( GrowableObjectArray::Handle(GrowableObjectArray::New())), sent_selectors_(), functions_called_dynamically_( HashTables::New(/*initial_capacity=*/1024)), functions_with_entry_point_pragmas_( HashTables::New(/*initial_capacity=*/1024)), seen_functions_(HashTables::New(/*initial_capacity=*/1024)), possibly_retained_functions_( HashTables::New(/*initial_capacity=*/1024)), fields_to_retain_(), functions_to_retain_( HashTables::New(/*initial_capacity=*/1024)), classes_to_retain_(), typeargs_to_retain_(), types_to_retain_(), functiontypes_to_retain_(), typeparams_to_retain_(), consts_to_retain_(), seen_table_selectors_(), api_uses_(), error_(Error::Handle()), get_runtime_type_is_unique_(false) { ASSERT(Precompiler::singleton_ == nullptr); 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_ = nullptr; 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()); // After finding all code, and before starting to trace, populate the // assets map. GetNativeAssetsMap(T); // 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()); } 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_disassemble) { 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(); DropTransitiveUserDefinedConstants(); 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()); Library& null_library = Library::Handle(Z); 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_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); #if !defined(DART_DYNAMIC_MODULES) IG->object_store()->set_async_star_stream_controller(null_class); #endif IG->object_store()->set_native_assets_library(null_library); 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()) { FATAL("Code visitor would miss the code for function \"%s\"\n", non_visited.ToFullyQualifiedCString()); } #endif DiscardCodeObjects(); { PRECOMPILER_TIMER_SCOPE(this, Dedup); ProgramVisitor::Dedup(T); } PruneDictionaries(); if (retained_reasons_writer_ != nullptr) { reasons_writer.Write(); retained_reasons_writer_ = nullptr; } zone_ = nullptr; } 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,", dropped_library_count_); THR_Print(" %" Pd " constants arrays entries.\n", dropped_constants_arrays_entries_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(), 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 = Symbols::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()) { AddApiUse(main); if (lib.LookupFunctionAllowPrivate(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 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, field.is_dynamically_callable()); 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())); } ASSERT(!function.is_abstract()); CompileFunction(this, thread_, function); // 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(); 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::null()); continue; } entry = view.Get(); 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(); *temp_selector = Function::DropImplicitCallPrefix(*temp_selector).ptr(); 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(); *temp_selector = Function::DropImplicitCallPrefix(*temp_selector).ptr(); 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.IsFfiCallbackTrampoline()) { const auto& callback_target = Function::Handle(Z, target.FfiCallbackTarget()); if (!callback_target.IsNull()) { AddFunction(callback_target, RetainReasons::kFfiCallbackTarget); } AddTypesOf(target); } 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); if (function.IsFfiCallbackTrampoline()) { AddType(FunctionType::Handle(Z, function.FfiCSignature())); } 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; } // 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(¶m)) return; typeparams_to_retain_.Insert(&TypeParameter::ZoneHandle(Z, param.ptr())); if (param.IsClassTypeParameter()) { AddTypesOf(Class::Handle(Z, param.parameterized_class())); } else { AddType(FunctionType::Handle(Z, param.parameterized_function_type())); } 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, type.arguments()); AddTypeArguments(vector); } else if (abstype.IsRecordType()) { const auto& rec = RecordType::Cast(abstype); AbstractType& type = AbstractType::Handle(Z); for (intptr_t i = 0, n = rec.NumFields(); i < n; ++i) { type = rec.FieldTypeAt(i); 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()) { 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())); } // Collects instances directly referenced from an object. class SubInstanceCollector : public ObjectPointerVisitor { public: explicit SubInstanceCollector(IsolateGroup* isolate_group) : ObjectPointerVisitor(isolate_group), subinstance_(Object::Handle()) {} void HandlePointer(ObjectPtr ptr) { // Note: do not invoke AddConstObject recursively here because it can // cause a GC which would invalidate raw pointers we are working with. // Instead collect all instance pointers in an array of handles they // will be processed later. subinstance_ = ptr; if (subinstance_.IsInstance()) { subinstances_.Add( &Instance::Handle(Instance::Cast(subinstance_).ptr())); } } void VisitPointers(ObjectPtr* first, ObjectPtr* last) override { for (ObjectPtr* current = first; current <= last; current++) { HandlePointer(*current); } subinstance_ = Object::null(); } #if defined(DART_COMPRESSED_POINTERS) void VisitCompressedPointers(uword heap_base, CompressedObjectPtr* first, CompressedObjectPtr* last) override { for (CompressedObjectPtr* current = first; current <= last; current++) { HandlePointer(current->Decompress(heap_base)); } subinstance_ = Object::null(); } #endif const GrowableArray& subinstances() const { return subinstances_; } private: Object& subinstance_; GrowableArray subinstances_; }; SubInstanceCollector visitor(IG); { NoSafepointScope no_safepoint_scope; // Working with raw pointers here. instance.ptr()->untag()->VisitPointers(&visitor); } // Recursively call AddConstObject on all subinstances. // // Note: AddConstObject can trigger GC. for (auto instance : visitor.subinstances()) { AddConstObject(*instance); *instance = Instance::null(); } } 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, /* create_if_absent = */ true, /* is_dynamically_callable = */ true)); 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()) { auto field_table = field.is_shared() ? IG->shared_initial_field_table() : IG->initial_field_table(); const Object& value = Object::Handle(Z, field_table->At(field.field_id())); 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. if (function.is_old_native()) { return "native function"; } // 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"; } if (StackTraceUtils::IsNeededForAsyncAwareUnwinding(function)) { return RetainReasons::kAsyncStackUnwinding; } return nullptr; } void Precompiler::AddFunction(const Function& function, const char* retain_reason) { ASSERT(!function.is_abstract()); 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::AddApiUse(const Object& obj) { api_uses_.Insert(&Object::ZoneHandle(Z, obj.ptr())); } bool Precompiler::HasApiUse(const Object& obj) { return api_uses_.HasKey(&obj); } 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); EntryPointPragma pragma = FindEntryPointPragma( IG, metadata, &reusable_field_handle, &reusable_object_handle); if (EntryPointPragmaUtils::AllowsAccess(pragma)) { AddInstantiatedClass(cls); AddApiUse(cls); } else if (EntryPointPragmaUtils::AllowsTypeAccess(pragma)) { AddApiUse(cls); AddTypesOf(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); EntryPointPragma pragma = FindEntryPointPragma( IG, metadata, &reusable_field_handle, &reusable_object_handle); if (!EntryPointPragmaUtils::AllowsAccess(pragma)) continue; AddField(field); AddApiUse(field); if (!field.is_static()) { if (EntryPointPragmaUtils::AllowsGet(pragma)) { implicit_getters.Add(field); } if (EntryPointPragmaUtils::AllowsSet(pragma)) { implicit_setters.Add(field); } } else { implicit_static_getters.Add(field); } } } auto mark_entry_point_and_api_methods = [&](const Function& function, const Function& api_function, const char* reason = RetainReasons::kEntryPointPragma) { functions_with_entry_point_pragmas_.Insert(function); // Check the api_function for being abstract, since function is // derived from api_function when they differ. if (!api_function.is_abstract()) { AddFunction(function, reason); } else { AddRetainReason(function, reason); } AddApiUse(api_function); }; auto mark_entry_point_method = [&](const Function& function, const char* const reason = RetainReasons::kEntryPointPragma) { mark_entry_point_and_api_methods(function, function, reason); }; auto mark_implicit_method_if_field_saved = [&](const Function& function, const GrowableObjectArray& field_array, const char* reason) { for (intptr_t i = 0; i < field_array.Length(); ++i) { field ^= field_array.At(i); if (function.accessor_field() == field.ptr()) { mark_entry_point_method(function, reason); } } }; // 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); // Do anything that doesn't depend on having a pragma first, so that // the pragma check can continue if one isn't found. if (function.is_old_native()) { // The embedder will need to lookup this library to provide the native // resolver, even if there are no embedder calls into the library. AddApiUse(lib); } // Implicit getters and setters are marked only if the corresponding // field was recorded as having the appropriate entry point annotation. if (function.IsImplicitGetterFunction()) { mark_implicit_method_if_field_saved(function, implicit_getters, RetainReasons::kImplicitGetter); } else if (function.IsImplicitSetterFunction()) { mark_implicit_method_if_field_saved(function, implicit_setters, RetainReasons::kImplicitSetter); } else if (function.IsImplicitStaticGetterFunction()) { mark_implicit_method_if_field_saved( function, implicit_static_getters, RetainReasons::kImplicitStaticGetter); } else if (function.has_pragma()) { metadata ^= lib.GetMetadata(function); auto type = FindEntryPointPragma(IG, metadata, &reusable_field_handle, &reusable_object_handle); if (!EntryPointPragmaUtils::AllowsAccess(type)) continue; if (function.IsGetterFunction()) { ASSERT(EntryPointPragmaUtils::AllowsGet(type)); mark_entry_point_method(function); } else if (function.IsSetterFunction()) { ASSERT(EntryPointPragmaUtils::AllowsSet(type)); mark_entry_point_method(function); } else if (function.IsConstructor()) { ASSERT(EntryPointPragmaUtils::AllowsCall(type)); mark_entry_point_method(function); if (function.IsGenerativeConstructor()) { // For generative constructors, the class must be accessible. AddInstantiatedClass(cls); AddApiUse(cls); } } else { ASSERT(EntryPointPragmaUtils::AllowsCall(type) || EntryPointPragmaUtils::AllowsGet(type)); if (EntryPointPragmaUtils::AllowsCall(type)) { mark_entry_point_method(function); } if (EntryPointPragmaUtils::AllowsGet(type)) { // Non-getter procedures use "get" to allow closurization. function2 = function.ImplicitClosureFunction(); // Mark `function` as having an api use and not `function2`, // because `Dart_GetField` retrieves the implicit closure // function via the original function. mark_entry_point_and_api_methods(function2, function); } } } } 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::GetterSymbol(selector); 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); bool generate_dynamic_forwarder = false; if (function.is_dynamically_callable()) { generate_dynamic_forwarder = true; } else 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); } generate_dynamic_forwarder = is_getter ? metadata.getter_called_dynamically : metadata.method_or_setter_called_dynamically; } if (generate_dynamic_forwarder) { function2 = function.GetDynamicInvocationForwarder(selector2); ASSERT(function.is_dynamically_callable() == function2.is_dynamically_callable()); 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 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 (function.NeedsDynamicInvocationForwarder()) { *mangled_name = function.name(); *mangled_name = Function::CreateDynamicInvocationForwarderName(*mangled_name); *dyn_function = function.GetDynamicInvocationForwarder(*mangled_name); } *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(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(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); if (function.IsDynamicallyOverridden()) continue; // 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); } } function ^= functions_map.GetOrNull(Symbols::GetRuntimeType()); get_runtime_type_is_unique_ = !function.IsNull(); 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); 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(); bool retained = possibly_retained_functions_.ContainsKey(function); #if defined(DART_DYNAMIC_MODULES) // Retain abstract functions annotated with entry point // pragmas as they can be used as targets of interface calls. if (function.is_abstract() && functions_with_entry_point_pragmas_.ContainsKey(function)) { retained = true; } #endif // defined(DART_DYNAMIC_MODULES) 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); } } } if (cls.invocation_dispatcher_cache() != Array::empty_array().ptr()) { DispatcherSet dispatchers(cls.invocation_dispatcher_cache()); DispatcherSet::Iterator it(&dispatchers); while (it.MoveNext()) { function ^= dispatchers.GetKey(it.Current()); if (possibly_retained_functions_.ContainsKey(function)) { AddTypesOf(function); } } dispatchers.Release(); } } } 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.IsFfiCallbackTrampoline()) { FATAL("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(/*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(); auto const kind = Code::KindField::decode(kind_and_offset_.Value()); if ((kind != Code::kCallViaCode) && (kind != Code::kPcRelativeCall)) continue; target_function_ = view.Get(); if (target_function_.IsNull()) continue; ASSERT(view.Get() == Code::null()); ASSERT(target_function_.HasCode()); target_code_ = target_function_.CurrentCode(); ASSERT(!target_code_.IsStubCode()); view.Set(target_code_); view.Set(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.IsFfiCallbackTrampoline()) { // FFI trampolines may be dynamically called. return AddRetainReason(sig, RetainReasons::kFfiTrampolineSignature); } if (function.is_old_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 (StackTraceUtils::IsNeededForAsyncAwareUnwinding(function)) { return AddRetainReason(sig, RetainReasons::kAsyncStackUnwinding); } 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. // If you are changing this code you might want to adjust // SnapshotAnalyzer::DumpCode which looks at owners. 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, ClosureFunctionsCache::kInvalidLocalFunctionId, /*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()); 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(); if (cls.invocation_dispatcher_cache() != Array::empty_array().ptr()) { DispatcherSet dispatchers(Z, cls.invocation_dispatcher_cache()); DispatcherSet::Iterator it(&dispatchers); while (it.MoveNext()) { function ^= dispatchers.GetKey(it.Current()); if (functions_to_retain_.ContainsKey(function)) { trim_function(function); retained_functions.Add(function); } else { drop_function(function); } } dispatchers.Release(); } if (retained_functions.Length() == 0) { cls.set_invocation_dispatcher_cache(Array::empty_array()); } else { DispatcherSet retained_dispatchers( Z, HashTables::New(retained_functions.Length(), Heap::kOld)); for (intptr_t j = 0; j < retained_functions.Length(); j++) { function ^= retained_functions.At(j); retained_dispatchers.Insert(function); } cls.set_invocation_dispatcher_cache(retained_dispatchers.Release()); } } } 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); // Only needed during compilation. IG->object_store()->set_closure_functions_table(Object::null_array()); } 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 types(Z, 200); { class TypesCollector : public ObjectVisitor { public: explicit TypesCollector(Zone* zone, GrowableHandlePtrArray* types) : type_(AbstractType::Handle(zone)), types_(types) {} void VisitObject(ObjectPtr obj) override { const auto cid = obj->GetClassIdOfHeapObject(); if (cid == kTypeCid || cid == kFunctionTypeCid || cid == kRecordTypeCid) { type_ ^= obj; types_->Add(type_); } } private: AbstractType& type_; GrowableHandlePtrArray* types_; }; HeapIterationScope his(T); TypesCollector visitor(Z, &types); // Find all type objects in this isolate. IG->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.type_class_id() == kDynamicCid) continue; if (type.type_class_id() == kVoidCid) continue; if (type.type_class_id() == kNeverCid) 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()); } enum ConstantVisitedValue { kNotVisited = 0, kRetain, kDrop }; static bool IsUserDefinedClass(Zone* zone, ClassPtr cls, ObjectStore* object_store) { intptr_t cid = cls.untag()->id(); if (cid < kNumPredefinedCids) { return false; } return true; } /// Updates |visited| weak table with information about whether object /// (transitively) references constants of user-defined classes: |kDrop| /// indicates it does, |kRetain| - does not. class ConstantInstanceVisitor { public: ConstantInstanceVisitor(Zone* zone, WeakTable* visited, ObjectStore* object_store) : zone_(zone), visited_(visited), object_store_(object_store), object_(Object::Handle(zone)), array_(Array::Handle(zone)) {} void Visit(ObjectPtr object_ptr) { if (!object_ptr->IsHeapObject()) { return; } ConstantVisitedValue value = static_cast( visited_->GetValueExclusive(object_ptr)); if (value != kNotVisited) { return; } object_ = object_ptr; if (IsUserDefinedClass(zone_, object_.clazz(), object_store_)) { visited_->SetValueExclusive(object_ptr, kDrop); return; } // Conservatively assume an object will be retained. visited_->SetValueExclusive(object_ptr, kRetain); switch (object_ptr.untag()->GetClassId()) { case kImmutableArrayCid: { array_ ^= object_ptr; for (intptr_t i = 0; i < array_.Length(); i++) { ObjectPtr element = array_.At(i); Visit(element); if (static_cast( visited_->GetValueExclusive(element)) == kDrop) { visited_->SetValueExclusive(object_ptr, kDrop); break; } } break; } case kConstMapCid: { const Map& map = Map::Handle(Map::RawCast(object_ptr)); Map::Iterator iterator(map); while (iterator.MoveNext()) { ObjectPtr element = iterator.CurrentKey(); Visit(element); if (static_cast( visited_->GetValueExclusive(element)) == kDrop) { visited_->SetValueExclusive(object_ptr, kDrop); break; } element = iterator.CurrentValue(); Visit(element); if (static_cast( visited_->GetValueExclusive(element)) == kDrop) { visited_->SetValueExclusive(object_ptr, kDrop); break; } } break; } case kConstSetCid: { const Set& set = Set::Handle(Set::RawCast(object_ptr)); Set::Iterator iterator(set); while (iterator.MoveNext()) { ObjectPtr element = iterator.CurrentKey(); Visit(element); if (static_cast( visited_->GetValueExclusive(element)) == kDrop) { visited_->SetValueExclusive(object_ptr, kDrop); break; } } break; } } } private: Zone* zone_; WeakTable* visited_; ObjectStore* object_store_; Object& object_; Array& array_; }; // To reduce snapshot size, we remove from constant tables all constants that // cannot be sent in messages between isolate groups. Such constants will not // be canonicalized at runtime. void Precompiler::DropTransitiveUserDefinedConstants() { HANDLESCOPE(T); auto& constants = Array::Handle(Z); auto& obj = Object::Handle(Z); auto& lib = Library::Handle(Z); auto& cls = Class::Handle(Z); auto& instance = Instance::Handle(Z); { NoSafepointScope no_safepoint(T); std::unique_ptr visited(new WeakTable()); ObjectStore* object_store = IG->object_store(); ConstantInstanceVisitor visitor(Z, visited.get(), object_store); 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.constants() == Array::null()) { continue; } #if defined(DART_DYNAMIC_MODULES) // Retain constant tables of exported classes to allow constant // canonicalization at runtime. if (HasApiUse(cls)) { continue; } #endif // defined(DART_DYNAMIC_MODULES) typedef UnorderedHashSet CanonicalInstancesSet; CanonicalInstancesSet constants_set(cls.constants()); CanonicalInstancesSet::Iterator iterator(&constants_set); if (IsUserDefinedClass(Z, cls.ptr(), object_store)) { // All constants for user-defined classes can be dropped. constants = cls.constants(); dropped_constants_arrays_entries_count_ += constants.Length(); if (FLAG_trace_precompiler) { THR_Print("Dropping %" Pd " entries from constants for class %s\n", constants.Length(), cls.ToCString()); } while (iterator.MoveNext()) { obj = constants_set.GetKey(iterator.Current()); instance = Instance::RawCast(obj.ptr()); consts_to_retain_.Remove(&instance); visited->SetValueExclusive(obj.ptr(), kDrop); } } else { // Core classes might have constants that refer to user-defined // classes. Those should be dropped too. while (iterator.MoveNext()) { obj = constants_set.GetKey(iterator.Current()); ConstantVisitedValue value = static_cast( visited->GetValueExclusive(obj.ptr())); if (value == kNotVisited) { visitor.Visit(obj.ptr()); value = static_cast( visited->GetValueExclusive(obj.ptr())); } ASSERT(value == kDrop || value == kRetain); if (value == kDrop) { dropped_constants_arrays_entries_count_++; if (FLAG_trace_precompiler) { THR_Print("Dropping constant entry for class %s instance:%s\n", cls.ToCString(), obj.ToCString()); } instance = Instance::RawCast(obj.ptr()); consts_to_retain_.Remove(&instance); } } } constants_set.Release(); } } } } 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 { FATAL("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)), parent_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(); auto const kind = Code::KindField::decode(kind_and_offset_.Value()); if (kind == Code::kCallViaCode) { call_target_ = Code::RawCast(view.Get()); ASSERT(!call_target_.IsNull()); targets_of_calls_via_code_.Add(call_target_); } } } void VisitCode(const Code& code) override { ++total_code_objects_; RecordCodeObjectsUsedForCalls(code); // Only discard Code objects corresponding to Dart functions. if (!code.IsFunctionCode() || code.IsUnknownDartCode()) { ++non_function_codes_; return; } // Retain Code object if it has exception handlers or PC descriptors. if (code.exception_handlers() != Object::empty_exception_handlers().ptr()) { ++codes_with_exception_handlers_; return; } if (code.pc_descriptors() != Object::empty_descriptors().ptr()) { ++codes_with_pc_descriptors_; return; } function_ = code.function(); if (functions_to_retain_.ContainsKey(function_)) { // Retain Code objects corresponding to native functions // (to find native implementation). if (function_.is_old_native()) { ++codes_with_native_function_; return; } // Retain Code objects corresponding to dynamically // called functions. if (functions_called_dynamically_.ContainsKey(function_)) { ++codes_with_dynamically_called_function_; return; } if (StackTraceUtils::IsNeededForAsyncAwareUnwinding(function_)) { ++codes_with_function_needed_for_async_unwinding_; return; } } else { ASSERT(!functions_called_dynamically_.ContainsKey(function_)); } // Retain Code objects in the non-root loading unit as // they are allocated while loading root unit but filled // while loading another unit. class_ = function_.Owner(); library_ = class_.library(); loading_unit_ = library_.loading_unit(); if (loading_unit_.id() != LoadingUnit::kRootId) { ++codes_with_deferred_function_; return; } // Retain Code objects corresponding to FFI trampolines. if (function_.IsFfiCallbackTrampoline()) { ++codes_with_ffi_trampoline_function_; return; } code.set_is_discarded(true); if (FLAG_trace_precompiler) { THR_Print("Discarding code object corresponding to %s\n", function_.ToFullyQualifiedCString()); } ++discarded_codes_; } void RetainCodeObjectsUsedAsCallTargets() { for (intptr_t i = 0, n = targets_of_calls_via_code_.Length(); i < n; ++i) { call_target_ = Code::RawCast(targets_of_calls_via_code_.At(i)); if (call_target_.is_discarded()) { call_target_.set_is_discarded(false); ++codes_used_as_call_targets_; --discarded_codes_; } } } void PrintStatistics() const { THR_Print("Discarding Code objects:\n"); THR_Print(" %8" Pd " non-function Codes\n", non_function_codes_); THR_Print(" %8" Pd " Codes with exception handlers\n", codes_with_exception_handlers_); THR_Print(" %8" Pd " Codes with pc descriptors\n", codes_with_pc_descriptors_); THR_Print(" %8" Pd " Codes with native functions\n", codes_with_native_function_); THR_Print(" %8" Pd " Codes with dynamically called functions\n", codes_with_dynamically_called_function_); THR_Print(" %8" Pd " Codes with async unwinding related functions\n", codes_with_function_needed_for_async_unwinding_); THR_Print(" %8" Pd " Codes with deferred functions\n", codes_with_deferred_function_); THR_Print(" %8" Pd " Codes with ffi trampoline functions\n", codes_with_ffi_trampoline_function_); THR_Print(" %8" Pd " Codes used as call targets\n", codes_used_as_call_targets_); THR_Print(" %8" Pd " Codes discarded\n", discarded_codes_); THR_Print(" %8" Pd " Codes total\n", total_code_objects_); } private: Zone* zone_; Function& function_; Function& parent_function_; Class& class_; Library& library_; LoadingUnit& loading_unit_; Array& static_calls_target_table_; Smi& kind_and_offset_; Code& call_target_; GrowableObjectArray& targets_of_calls_via_code_; const FunctionSet& functions_to_retain_; const FunctionSet& functions_called_dynamically_; // Statistics intptr_t total_code_objects_ = 0; intptr_t non_function_codes_ = 0; intptr_t codes_with_exception_handlers_ = 0; intptr_t codes_with_pc_descriptors_ = 0; intptr_t codes_with_native_function_ = 0; intptr_t codes_with_dynamically_called_function_ = 0; intptr_t codes_with_function_needed_for_async_unwinding_ = 0; intptr_t codes_with_deferred_function_ = 0; intptr_t codes_with_ffi_trampoline_function_ = 0; intptr_t codes_used_as_call_targets_ = 0; intptr_t discarded_codes_ = 0; }; // Code objects are used by stack traces if not dwarf_stack_traces. // Code objects are used by profiler in non-PRODUCT mode. if (!FLAG_dwarf_stack_traces_mode || FLAG_retain_code_objects) { return; } HANDLESCOPE(T); DiscardCodeVisitor visitor(Z, functions_to_retain_, functions_called_dynamically_); ProgramVisitor::WalkProgram(Z, IG, &visitor); visitor.RetainCodeObjectsUsedAsCallTargets(); if (FLAG_trace_precompiler) { visitor.PrintStatistics(); } } void Precompiler::PruneDictionaries() { #if defined(DEBUG) // Verify that api_uses_ is stable: any entry in it can be found. This // check serves to catch bugs when ProgramElementSet::Hash is accidentally // defined using unstable values. ProgramElementSet::Iterator it = api_uses_.GetIterator(); while (auto entry = it.Next()) { ASSERT(api_uses_.HasKey(*entry)); } #endif // PRODUCT-only: pruning interferes with various uses of the service protocol, // including heap analysis tools. #if defined(PRODUCT) class PruneDictionariesVisitor { public: GrowableObjectArrayPtr PruneLibraries( const GrowableObjectArray& libraries) { for (intptr_t i = 0; i < libraries.Length(); i++) { lib_ ^= libraries.At(i); bool retain = PruneLibrary(lib_); if (retain) { lib_.set_index(retained_libraries_.Length()); retained_libraries_.Add(lib_); } else { lib_.set_index(-1); lib_.set_private_key(null_string_); } } Library::RegisterLibraries(Thread::Current(), retained_libraries_); return retained_libraries_.ptr(); } bool PruneLibrary(const Library& lib) { dict_ = lib.dictionary(); intptr_t dict_size = dict_.Length() - 1; intptr_t used = 0; for (intptr_t i = 0; i < dict_size; i++) { entry_ = dict_.At(i); if (entry_.IsNull()) continue; bool retain = false; if (entry_.IsClass()) { // dart:async: Fix async stack trace lookups in dart:async to annotate // entry points or fail gracefully. // dart:core, dart:collection, dart:typed_data: Isolate messaging // between groups allows any class in these libraries. retain = PruneClass(Class::Cast(entry_)) || (lib.url() == Symbols::DartAsync().ptr()) || (lib.url() == Symbols::DartCore().ptr()) || (lib.url() == Symbols::DartCollection().ptr()) || (lib.url() == Symbols::DartTypedData().ptr()); } else if (entry_.IsFunction() || entry_.IsField()) { retain = precompiler_->HasApiUse(entry_); } else { FATAL("Unexpected library entry: %s", entry_.ToCString()); } if (retain) { used++; } else { dict_.SetAt(i, Object::null_object()); } } lib.RehashDictionary(dict_, used * 4 / 3 + 1); bool retain = used > 0; cls_ = lib.toplevel_class(); if (PruneClass(cls_)) { retain = true; } if (lib.is_dart_scheme()) { retain = true; } if (lib.ptr() == root_lib_.ptr()) { retain = true; } if (precompiler_->HasApiUse(lib)) { retain = true; } return retain; } bool PruneClass(const Class& cls) { bool retain = precompiler_->HasApiUse(cls); functions_ = cls.functions(); retained_functions_ = GrowableObjectArray::New(); for (intptr_t i = 0; i < functions_.Length(); i++) { function_ ^= functions_.At(i); if (precompiler_->HasApiUse(function_)) { retained_functions_.Add(function_); retain = true; } else if (precompiler_->functions_called_dynamically_.ContainsKey( function_)) { retained_functions_.Add(function_); // No `retain = true`: the function must appear in the method // dictionary for lookup, but the class may still be removed from the // library. } } if (retained_functions_.Length() > 0) { functions_ = Array::MakeFixedLength(retained_functions_); cls.SetFunctions(functions_); } else { cls.SetFunctions(Object::empty_array()); } fields_ = cls.fields(); retained_fields_ = GrowableObjectArray::New(); for (intptr_t i = 0; i < fields_.Length(); i++) { field_ ^= fields_.At(i); if (precompiler_->HasApiUse(field_)) { retained_fields_.Add(field_); retain = true; } } if (retained_fields_.Length() > 0) { fields_ = Array::MakeFixedLength(retained_fields_); cls.SetFields(fields_); } else { cls.SetFields(Object::empty_array()); } return retain; } explicit PruneDictionariesVisitor(Precompiler* precompiler, Zone* zone) : precompiler_(precompiler), lib_(Library::Handle(zone)), dict_(Array::Handle(zone)), entry_(Object::Handle(zone)), cls_(Class::Handle(zone)), functions_(Array::Handle(zone)), fields_(Array::Handle(zone)), function_(Function::Handle(zone)), field_(Field::Handle(zone)), retained_functions_(GrowableObjectArray::Handle(zone)), retained_fields_(GrowableObjectArray::Handle(zone)), retained_libraries_( GrowableObjectArray::Handle(zone, GrowableObjectArray::New())), root_lib_(Library::Handle( zone, precompiler->isolate_group()->object_store()->root_library())), null_string_(String::Handle(zone)) {} private: Precompiler* const precompiler_; Library& lib_; Array& dict_; Object& entry_; Class& cls_; Array& functions_; Array& fields_; Function& function_; Field& field_; GrowableObjectArray& retained_functions_; GrowableObjectArray& retained_fields_; const GrowableObjectArray& retained_libraries_; const Library& root_lib_; const String& null_string_; }; HANDLESCOPE(T); SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock()); PruneDictionariesVisitor visitor(this, Z); libraries_ = visitor.PruneLibraries(libraries_); #endif // defined(PRODUCT) } // Traits for the HashTable template. struct CodeKeyTraits { static uint32_t Hash(const Object& key) { return Code::Cast(key).Size(); } static const char* Name() { return "CodeKeyTraits"; } static bool IsMatch(const Object& x, const Object& y) { return x.ptr() == y.ptr(); } static bool ReportStats() { return false; } }; typedef UnorderedHashSet CodeSet; #if defined(DEBUG) FunctionPtr Precompiler::FindUnvisitedRetainedFunction() { class CodeChecker : public CodeVisitor { public: CodeChecker() : visited_code_(HashTables::New(/*initial_capacity=*/1024)) {} ~CodeChecker() { visited_code_.Release(); } const CodeSet& visited() const { return visited_code_; } void VisitCode(const Code& code) { visited_code_.Insert(code); } private: CodeSet visited_code_; }; CodeChecker visitor; ProgramVisitor::WalkProgram(Z, IG, &visitor); const CodeSet& visited = visitor.visited(); FunctionSet::Iterator it(&functions_to_retain_); Function& function = Function::Handle(Z); Code& code = Code::Handle(Z); while (it.MoveNext()) { function ^= functions_to_retain_.GetKey(it.Current()); if (!function.HasCode()) continue; code = function.CurrentCode(); if (!visited.ContainsKey(code)) return function.ptr(); } return Function::null(); } #endif void Precompiler::Obfuscate() { if (!IG->obfuscate()) { return; } class ScriptsCollector : public ObjectVisitor { public: explicit ScriptsCollector(Zone* zone, GrowableHandlePtrArray* scripts) : script_(Script::Handle(zone)), scripts_(scripts) {} void VisitObject(ObjectPtr obj) override { if (obj->GetClassIdOfHeapObject() == kScriptCid) { script_ ^= obj; scripts_->Add(Script::Cast(script_)); } } private: Script& script_; GrowableHandlePtrArray* scripts_; }; GrowableHandlePtrArray scripts(Z, 100); IsolateGroup::Current()->heap()->CollectAllGarbage(); { HeapIterationScope his(T); ScriptsCollector visitor(Z, &scripts); IG->heap()->VisitObjects(&visitor); } { // Note: when this object is destroyed it will commit obfuscation // mappings into the ObjectStore. Hence the block around it - to // ensure that destructor is called before we save obfuscation // mappings and clear the ObjectStore. Obfuscator obfuscator(T, /*private_key=*/String::Handle(Z)); String& str = String::Handle(Z); for (intptr_t i = 0; i < scripts.length(); i++) { const Script& script = scripts.At(i); str = script.url(); str = Symbols::New(T, str); str = obfuscator.Rename(str, /*atomic=*/true); script.set_url(str); } Library& lib = Library::Handle(); for (intptr_t i = 0; i < libraries_.Length(); i++) { lib ^= libraries_.At(i); if (!lib.is_dart_scheme()) { str = lib.name(); str = obfuscator.Rename(str, /*atomic=*/true); lib.set_name(str); str = lib.url(); str = Symbols::New(T, str); str = obfuscator.Rename(str, /*atomic=*/true); lib.set_url(str); } } Library::RegisterLibraries(T, libraries_); } // Obfuscation is done. Move obfuscation map into mallocated memory. IG->set_obfuscation_map(Obfuscator::SerializeMap(T)); // Discard obfuscation mappings to avoid including them into snapshot. IG->object_store()->set_obfuscation_map(Array::Handle(Z)); } void Precompiler::FinalizeAllClasses() { // Create a fresh Zone because kernel reading during class finalization // may create zone handles. Those handles may prevent garbage collection of // otherwise unreachable constants of dropped classes, which would // cause assertion failures during GC after classes are dropped. StackZone stack_zone(thread()); error_ = Library::FinalizeAllClasses(); if (!error_.IsNull()) { Jump(error_); } IG->set_all_classes_finalized(true); } void PrecompileParsedFunctionHelper::FinalizeCompilation( compiler::Assembler* assembler, FlowGraphCompiler* graph_compiler, FlowGraph* flow_graph, CodeStatistics* stats) { const Function& function = parsed_function()->function(); Zone* const zone = thread()->zone(); ASSERT(function.IsOptimizable()); // CreateDeoptInfo uses the object pool and needs to be done before // FinalizeCode. const Array& deopt_info_array = Array::Handle(zone, graph_compiler->CreateDeoptInfo(assembler)); // Allocates instruction object. Since this occurs only at safepoint, // there can be no concurrent access to the instruction page. const auto pool_attachment = Code::PoolAttachment::kNotAttachPool; SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock()); const Code& code = Code::Handle( Code::FinalizeCodeAndNotify(function, graph_compiler, assembler, pool_attachment, /*optimized=*/true, stats)); code.set_is_optimized(true); code.set_owner(function); graph_compiler->FinalizePcDescriptors(code); code.set_deopt_info_array(deopt_info_array); graph_compiler->FinalizeStackMaps(code); graph_compiler->FinalizeVarDescriptors(code); graph_compiler->FinalizeExceptionHandlers(code); graph_compiler->FinalizeCatchEntryMovesMap(code); graph_compiler->FinalizeStaticCallTargetsTable(code); graph_compiler->FinalizeCodeSourceMap(code); // Installs code while at safepoint. ASSERT(thread()->IsDartMutatorThread()); function.InstallOptimizedCode(code); if (function.IsFfiCallbackTrampoline()) { compiler::ffi::SetFfiCallbackCode(thread(), function, code); } } // Generate allocation stubs referenced by AllocateObject instructions. static void GenerateNecessaryAllocationStubs(FlowGraph* flow_graph) { for (auto block : flow_graph->reverse_postorder()) { for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) { if (auto allocation = it.Current()->AsAllocateObject()) { StubCode::GetAllocationStubForClass(allocation->cls()); } } } } bool PrecompileParsedFunctionHelper::GenerateCode(FlowGraph* flow_graph) { // We may reattempt compilation if the function needs to be assembled using // far branches on ARM. In the else branch of the setjmp call, done is set to // false, and use_far_branches is set to true if there is a longjmp from the // ARM assembler. In all other paths through this while loop, done is set to // true. use_far_branches is always false on ia32 and x64. bool done = false; volatile bool is_compiled = false; // volatile because the variable may be clobbered by a longjmp. volatile intptr_t far_branch_level = 0; while (!done) { LongJumpScope jump(thread()); const intptr_t val = DART_SETJMP(*jump.Set()); if (val == 0) { // Even in bare instructions mode we don't directly add objects into // the global object pool because code generation can bail out // (e.g. due to speculative optimization or branch offsets being // too big). If we were adding objects into the global pool directly // these recompilations would leave dead entries behind. // Instead we add objects into an intermediary pool which gets // committed into the global object pool at the end of the compilation. // This makes an assumption that global object pool itself does not // grow during code generation - unfortunately this is not the case // because we might have nested code generation (i.e. we might generate // some stubs). If this indeed happens we retry the compilation. // (See TryCommitToParent invocation below). compiler::ObjectPoolBuilder object_pool_builder( precompiler_->global_object_pool_builder()); compiler::Assembler assembler(&object_pool_builder, far_branch_level); CodeStatistics* function_stats = nullptr; if (FLAG_print_instruction_stats) { // At the moment we are leaking CodeStatistics objects for // simplicity because this is just a development mode flag. function_stats = new CodeStatistics(&assembler); } FlowGraphCompiler graph_compiler( &assembler, flow_graph, *parsed_function(), /*is_optimizing=*/true, /*deopt_id_to_ic_data=*/nullptr, function_stats); CompilerPassState pass_state(thread(), flow_graph, precompiler_); pass_state.graph_compiler = &graph_compiler; CompilerPass::GenerateCode(&pass_state); { COMPILER_TIMINGS_TIMER_SCOPE(thread(), FinalizeCode); TIMELINE_DURATION(thread(), CompilerVerbose, "FinalizeCompilation"); ASSERT(thread()->IsDartMutatorThread()); FinalizeCompilation(&assembler, &graph_compiler, flow_graph, function_stats); } if (precompiler_->phase() == Precompiler::Phase::kFixpointCodeGeneration) { for (intptr_t i = 0; i < graph_compiler.used_static_fields().length(); i++) { precompiler_->AddField(*graph_compiler.used_static_fields().At(i)); } const GrowableArray& call_selectors = graph_compiler.dispatch_table_call_targets(); for (intptr_t i = 0; i < call_selectors.length(); i++) { precompiler_->AddTableSelector(call_selectors[i]); } } else { // We should not be generating code outside of these two specific // precompilation phases. RELEASE_ASSERT( precompiler_->phase() == Precompiler::Phase::kCompilingConstructorsForInstructionCounts); } // In bare instructions mode try adding all entries from the object // pool into the global object pool. This might fail if we have // nested code generation (i.e. we generated some stubs) which means // that some of the object indices we used are already occupied in the // global object pool. // // In this case we simply retry compilation assuming that we are not // going to hit this problem on the second attempt. // // Note: currently we can't assume that two compilations of the same // method will lead to the same IR due to instability of inlining // heuristics (under some conditions we might end up inlining // more aggressively on the second attempt). if (!object_pool_builder.TryCommitToParent()) { done = false; continue; } // Exit the loop and the function with the correct result value. is_compiled = true; done = true; } else { // We bailed out or we encountered an error. const Error& error = Error::Handle(thread()->StealStickyError()); if (error.ptr() == Object::branch_offset_error().ptr()) { // Compilation failed due to an out of range branch offset in the // assembler. We try again (done = false) with far branches enabled. done = false; RELEASE_ASSERT(far_branch_level < 2); far_branch_level = far_branch_level + 1; } else { // If the error isn't due to an out of range branch offset, we don't // try again (done = true), and indicate that we did not finish // compiling (is_compiled = false). if (FLAG_trace_bailout) { THR_Print("%s\n", error.ToErrorCString()); } done = true; // Continue propagating. thread()->set_sticky_error(error); } is_compiled = false; } } return is_compiled; } // Return false if bailed out. bool PrecompileParsedFunctionHelper::Compile() { ASSERT(CompilerState::Current().is_aot()); Zone* const zone = thread()->zone(); HANDLESCOPE(thread()); FlowGraph* flow_graph = nullptr; const Function& function = parsed_function()->function(); ASSERT(!function.IsIrregexpFunction()); ASSERT(function.IsOptimizable()); CompilerState compiler_state(thread(), /*is_aot=*/true, /*is_optimizing=*/true, CompilerState::ShouldTrace(function)); compiler_state.set_function(function); { ZoneGrowableArray* ic_data_array = new (zone) ZoneGrowableArray(); TIMELINE_DURATION(thread(), CompilerVerbose, "BuildFlowGraph"); COMPILER_TIMINGS_TIMER_SCOPE(thread(), BuildGraph); kernel::FlowGraphBuilder builder(parsed_function(), ic_data_array, /* not building var desc */ nullptr, /* not inlining */ nullptr, /*optimizing=*/true, Compiler::kNoOSRDeoptId); flow_graph = builder.BuildGraph(); ASSERT(flow_graph != nullptr); } flow_graph->PopulateWithICData(function); { TIMELINE_DURATION(thread(), CompilerVerbose, "OptimizationPasses"); AotCallSpecializer call_specializer(precompiler_, flow_graph); CompilerPassState pass_state(thread(), flow_graph, precompiler_); pass_state.call_specializer = &call_specializer; flow_graph = CompilerPass::RunPipeline(CompilerPass::kAOT, &pass_state); } ASSERT(precompiler_ != nullptr); // When generating code in bare instruction mode all code objects // share the same global object pool. To reduce interleaving of // unrelated object pool entries from different code objects // we attempt to pregenerate stubs referenced by the code // we are going to generate. // // Reducing interleaving means reducing recompilations triggered by // failure to commit object pool into the global object pool. GenerateNecessaryAllocationStubs(flow_graph); return GenerateCode(flow_graph); } void Precompiler::CompileFunction(Precompiler* precompiler, Thread* thread, const Function& function) { PRECOMPILER_TIMER_SCOPE(precompiler, CompileFunction); NoActiveIsolateScope no_isolate_scope; VMTagScope tagScope(thread, VMTag::kCompileUnoptimizedTagId); TIMELINE_FUNCTION_COMPILATION_DURATION(thread, "CompileFunction", function); ASSERT(CompilerState::Current().is_aot()); ASSERT(function.IsOptimizable()); ASSERT(!function.HasCode()); if (precompiler->is_tracing()) { precompiler->tracer_->WriteCompileFunctionEvent(function); } StackZone stack_zone(thread); Zone* const zone = stack_zone.GetZone(); const bool trace_compiler = FLAG_trace_compiler || FLAG_trace_optimizing_compiler; Timer per_compile_timer; per_compile_timer.Start(); ParsedFunction* parsed_function = new (zone) ParsedFunction(thread, Function::ZoneHandle(zone, function.ptr())); if (trace_compiler) { THR_Print("Precompiling optimized function: '%s' @ token %" Pd ", size %" Pd "\n", function.ToFullyQualifiedCString(), function.token_pos().Pos(), (function.end_token_pos().Pos() - function.token_pos().Pos())); } PrecompileParsedFunctionHelper helper(precompiler, parsed_function); const bool success = helper.Compile(); if (!success) { // We got an error during compilation. const Error& error = Error::Handle(thread->StealStickyError()); ASSERT(error.IsLanguageError() && LanguageError::Cast(error).kind() != Report::kBailout); Jump(error); } per_compile_timer.Stop(); if (trace_compiler) { THR_Print("--> '%s' entry: %#" Px " size: %" Pd " time: %" Pd64 " us\n", function.ToFullyQualifiedCString(), Code::Handle(function.CurrentCode()).PayloadStart(), Code::Handle(function.CurrentCode()).Size(), per_compile_timer.TotalElapsedTime()); } if ((FLAG_disassemble || FLAG_disassemble_optimized) && FlowGraphPrinter::ShouldPrint(function)) { Code& code = Code::Handle(function.CurrentCode()); Disassembler::DisassembleCode(function, code, /*optimized=*/true); } } Obfuscator::Obfuscator(Thread* thread, const String& private_key) : state_(nullptr) { auto isolate_group = thread->isolate_group(); if (!isolate_group->obfuscate()) { // Nothing to do. return; } auto zone = thread->zone(); // Create ObfuscationState from ObjectStore::obfuscation_map(). ObjectStore* store = isolate_group->object_store(); Array& obfuscation_state = Array::Handle(zone, store->obfuscation_map()); if (store->obfuscation_map() == Array::null()) { // We are just starting the obfuscation. Create initial state. const int kInitialPrivateCapacity = 256; obfuscation_state = Array::New(kSavedStateSize); obfuscation_state.SetAt( 1, Array::Handle(zone, HashTables::New( kInitialPrivateCapacity, Heap::kOld))); } state_ = new (zone) ObfuscationState(thread, obfuscation_state, private_key); if (store->obfuscation_map() == Array::null()) { // We are just starting the obfuscation. Initialize the renaming map. // Note: InitializeRenamingMap uses state_. InitializeRenamingMap(); } } Obfuscator::~Obfuscator() { if (state_ != nullptr) { state_->SaveState(); } } void Obfuscator::InitializeRenamingMap() { // Prevent renaming of all pseudo-keywords and operators. // Note: not all pseudo-keywords are mentioned in DART_KEYWORD_LIST // (for example 'hide', 'show' and async related keywords are omitted). // Those are protected from renaming as part of all symbols. #define PREVENT_RENAMING(name, value, priority, attr) \ do { \ if (Token::CanBeOverloaded(Token::name) || \ ((Token::attr & Token::kPseudoKeyword) != 0)) { \ PreventRenaming(value); \ } \ } while (0); DART_TOKEN_LIST(PREVENT_RENAMING) DART_KEYWORD_LIST(PREVENT_RENAMING) #undef PREVENT_RENAMING // this is a keyword token unless it occurs in the string interpolation // which causes it to be obfuscated. PreventRenaming("this"); // Protect all symbols from renaming. #define PREVENT_RENAMING(name, value) PreventRenaming(value); PREDEFINED_SYMBOLS_LIST(PREVENT_RENAMING) #undef PREVENT_RENAMING // Protect NativeFieldWrapperClassX names from being obfuscated. Those // classes are created manually by the runtime system. // TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place // where these are created. PreventRenaming("NativeFieldWrapperClass1"); PreventRenaming("NativeFieldWrapperClass2"); PreventRenaming("NativeFieldWrapperClass3"); PreventRenaming("NativeFieldWrapperClass4"); // Prevent renaming of ClassID.cid* fields. These fields are injected by // runtime. // TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place // where these are created. #define CLASS_LIST_WITH_NULL(V) \ V(Null) \ CLASS_LIST_NO_OBJECT(V) #define PREVENT_RENAMING(clazz) PreventRenaming("cid" #clazz); CLASS_LIST_WITH_NULL(PREVENT_RENAMING) #undef PREVENT_RENAMING #undef CLASS_LIST_WITH_NULL // Prevent renaming of methods that are looked up by method recognizer. // TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place // where these are looked up. #define PREVENT_RENAMING(library, class_name, function_name, recognized_enum, \ fingerprint) \ do { \ PreventRenaming(#class_name); \ PreventRenaming(#function_name); \ } while (0); RECOGNIZED_LIST(PREVENT_RENAMING) #undef PREVENT_RENAMING // Prevent renaming of methods that are looked up by method recognizer. // TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place // where these are looked up. #define PREVENT_RENAMING(libary, class_name, function_name, recognized_enum, \ fingerprint) \ do { \ PreventRenaming(#class_name); \ PreventRenaming(#function_name); \ } while (0); POLYMORPHIC_TARGET_LIST(PREVENT_RENAMING) #undef PREVENT_RENAMING // These are not mentioned by entry points but are still looked up by name. // (They are not mentioned in the entry points because we don't need them // after the compilation) PreventRenaming("_resolveScriptUri"); // Precompiler is looking up "main". // TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place // where these are created. PreventRenaming("main"); // Fast path for common conditional import. See Deobfuscate method. PreventRenaming("dart"); PreventRenaming("library"); PreventRenaming("io"); PreventRenaming("html"); // Looked up by name via "DartUtils::GetDartType". PreventRenaming("_RandomAccessFile"); PreventRenaming("_RandomAccessFileOpsImpl"); PreventRenaming("ResourceHandle"); PreventRenaming("_ResourceHandleImpl"); PreventRenaming("_SocketControlMessageImpl"); PreventRenaming("_NamespaceImpl"); } StringPtr Obfuscator::ObfuscationState::RenameImpl(const String& name, bool atomic) { ASSERT(name.IsSymbol()); renamed_ ^= renames_.GetOrNull(name); if (renamed_.IsNull()) { renamed_ = BuildRename(name, atomic); renames_.UpdateOrInsert(name, renamed_); } return renamed_.ptr(); } static const char* const kGetterPrefix = "get:"; static const intptr_t kGetterPrefixLength = strlen(kGetterPrefix); static const char* const kSetterPrefix = "set:"; static const intptr_t kSetterPrefixLength = strlen(kSetterPrefix); void Obfuscator::PreventRenaming(const char* name) { // For constructor names Class.name skip class name (if any) and a dot. const char* dot = strchr(name, '.'); if (dot != nullptr) { name = dot + 1; } // Empty name: do nothing. if (name[0] == '\0') { return; } // Skip get: and set: prefixes. if (strncmp(name, kGetterPrefix, kGetterPrefixLength) == 0) { name = name + kGetterPrefixLength; } else if (strncmp(name, kSetterPrefix, kSetterPrefixLength) == 0) { name = name + kSetterPrefixLength; } state_->PreventRenaming(name); } void Obfuscator::ObfuscationState::SaveState() { saved_state_.SetAt(kSavedStateNameIndex, String::Handle(String::New(name_))); saved_state_.SetAt(kSavedStateRenamesIndex, renames_.Release()); thread_->isolate_group()->object_store()->set_obfuscation_map(saved_state_); } void Obfuscator::ObfuscationState::PreventRenaming(const char* name) { string_ = Symbols::New(thread_, name); PreventRenaming(string_); } void Obfuscator::ObfuscationState::PreventRenaming(const String& name) { renames_.UpdateOrInsert(name, name); } void Obfuscator::ObfuscationState::NextName() { // We apply the following rules: // // inc(a) = b, ... , inc(z) = A, ..., inc(Z) = a & carry. // for (intptr_t i = 0;; i++) { const char digit = name_[i]; if (digit == '\0') { name_[i] = 'a'; } else if (digit < 'Z') { name_[i]++; } else if (digit == 'Z') { name_[i] = 'a'; continue; // Carry. } else if (digit < 'z') { name_[i]++; } else { name_[i] = 'A'; } break; } } StringPtr Obfuscator::ObfuscationState::NewAtomicRename( bool should_be_private) { do { NextName(); renamed_ = Symbols::NewFormatted(thread_, "%s%s", should_be_private ? "_" : "", name_); // Must check if our generated name clashes with something that will // have an identity renaming. } while (renames_.GetOrNull(renamed_) == renamed_.ptr()); return renamed_.ptr(); } StringPtr Obfuscator::ObfuscationState::BuildRename(const String& name, bool atomic) { // Do not rename record positional field names $1, $2 etc // in order to handle them properly during dynamic invocations. if (Record::GetPositionalFieldIndexFromFieldName(name) >= 0) { return name.ptr(); } if (atomic) { return NewAtomicRename(name.CharAt(0) == '_'); } intptr_t start = 0; intptr_t end = name.Length(); // Follow the rules: // // Rename(get:foo) = get:Rename(foo). // Rename(set:foo) = set:Rename(foo). // bool is_getter = false; bool is_setter = false; if (Field::IsGetterName(name)) { is_getter = true; start = kGetterPrefixLength; } else if (Field::IsSetterName(name)) { is_setter = true; start = kSetterPrefixLength; } // Follow the rule: // // Rename(_ident@key) = Rename(_ident)@private_key_. // const bool is_private = name.CharAt(start) == '_'; if (is_private) { // Find the first '@'. intptr_t i = start; while (i < name.Length() && name.CharAt(i) != '@') { i++; } end = i; } if (is_getter || is_setter || is_private) { string_ = Symbols::New(thread_, name, start, end - start); // It's OK to call RenameImpl() recursively because 'string_' is used // only if atomic == false. string_ = RenameImpl(string_, /*atomic=*/true); if (is_private && (end < name.Length())) { string_ = Symbols::FromConcat(thread_, string_, private_key_); } if (is_getter) { return Symbols::FromGet(thread_, string_); } else if (is_setter) { return Symbols::FromSet(thread_, string_); } return string_.ptr(); } else { return NewAtomicRename(is_private); } } void Obfuscator::Deobfuscate(Thread* thread, const GrowableObjectArray& pieces) { const Array& obfuscation_state = Array::Handle(thread->zone(), thread->isolate_group()->object_store()->obfuscation_map()); if (obfuscation_state.IsNull()) { return; } const Array& renames = Array::Handle( thread->zone(), GetRenamesFromSavedState(obfuscation_state)); ObfuscationMap renames_map(renames.ptr()); String& piece = String::Handle(); for (intptr_t i = 0; i < pieces.Length(); i++) { piece ^= pieces.At(i); ASSERT(piece.IsSymbol()); // Fast path: skip '.' if (piece.ptr() == Symbols::Dot().ptr()) { continue; } // Fast path: check if piece has an identity obfuscation. if (renames_map.GetOrNull(piece) == piece.ptr()) { continue; } // Search through the whole obfuscation map until matching value is found. // We are using linear search instead of generating a reverse mapping // because we assume that Deobfuscate() method is almost never called. ObfuscationMap::Iterator it(&renames_map); while (it.MoveNext()) { const intptr_t entry = it.Current(); if (renames_map.GetPayload(entry, 0) == piece.ptr()) { piece ^= renames_map.GetKey(entry); pieces.SetAt(i, piece); break; } } } renames_map.Release(); } static const char* StringToCString(const String& str) { const intptr_t len = Utf8::Length(str); char* result = new char[len + 1]; str.ToUTF8(reinterpret_cast(result), len); result[len] = 0; return result; } const char** Obfuscator::SerializeMap(Thread* thread) { const Array& obfuscation_state = Array::Handle(thread->zone(), thread->isolate_group()->object_store()->obfuscation_map()); if (obfuscation_state.IsNull()) { return nullptr; } const Array& renames = Array::Handle( thread->zone(), GetRenamesFromSavedState(obfuscation_state)); ObfuscationMap renames_map(renames.ptr()); const char** result = new const char*[renames_map.NumOccupied() * 2 + 1]; intptr_t idx = 0; String& str = String::Handle(); ObfuscationMap::Iterator it(&renames_map); while (it.MoveNext()) { const intptr_t entry = it.Current(); str ^= renames_map.GetKey(entry); result[idx++] = StringToCString(str); str ^= renames_map.GetPayload(entry, 0); result[idx++] = StringToCString(str); } result[idx++] = nullptr; renames_map.Release(); return result; } #endif // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32) } // namespace dart